springboot中发送http
# SpringBoot中发送http
# 前言
我们编写的应用,时不时的会跟外部系统进行交互(某些应用经常要与其他系统进行交互),目前主要的方式有http,或者rpc...,所以学会在应用中发送http请求,或rpc请求是十分重要的。
而http,在其中又是最最常用的,所以熟练掌握它,对一个开发者来说,可以说是必修课了。下面就一起来看下,在SpringBoot应用中如何发送http请求,以及一些注意事项,还有跟外部系统进行交互时,要注意的一些点。
# SpringBoot中使用http
在 Spring Boot 应用中,发送 HTTP 请求有多种方式,以下是常见的几种方法及其使用流程和示例:
# HttpClient
(Apache HttpClient)
HttpClient
是 Apache 提供的一个强大的 HTTP 客户端库,支持同步和异步请求,功能丰富。
# 引入坐标
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
2
3
4
5
HttpClient的核心API:
- HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。
- HttpClients:可认为是构建器,可创建HttpClient对象。
- CloseableHttpClient:实现类,实现了HttpClient接口。
- HttpGet:Get方式请求类型。
- HttpPost:Post方式请求类型。
HttpClient发送请求步骤:
- 创建HttpClient对象
- 创建Http请求对象
- 调用HttpClient的execute方法发送请求
# 发送Get请求
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
// 创建 HttpClient 实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 创建 GET 请求
HttpGet request = new HttpGet("https://api.example.com/data");
// 发送请求并获取响应
ClassicHttpResponse response = httpClient.execute(request);
// 处理响应
System.out.println("Status Code: " + response.getCode());
System.out.println("Response Body: " + EntityUtils.toString(response.getEntity()));
//获取服务端返回的状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("服务端返回的状态码为:" + statusCode);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 另一个Get请求例子:
@Test
public void testGET() throws Exception{
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//发送请求,接受响应结果
CloseableHttpResponse response = httpClient.execute(httpGet);
//获取服务端返回的状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("服务端返回的状态码为:" + statusCode);
HttpEntity entity = response.getEntity();
String body = EntityUtils.toString(entity);
System.out.println("服务端返回的数据为:" + body);
//关闭资源
response.close();
httpClient.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 资源的关闭
上述2个例子,存在一个很大的不同,就是————对资源的关闭
。
在 Apache HttpClient 中,未正确关闭 CloseableHttpResponse
和 CloseableHttpClient
会导致严重的资源泄漏问题,具体原因如下:
# 必须关闭 CloseableHttpResponse
为什么必须关闭——CloseableHttpResponse,主要是因为以下2个原因:
- 连接池泄漏:
CloseableHttpResponse
的close()
方法会将当前使用的 HTTP 连接释放回连接池(如果使用了连接池,如PoolingHttpClientConnectionManager
)。如果不关闭response
,连接池中的连接会被一直占用,最终导致连接池耗尽,后续请求无法获取连接,抛出类似ConnectionPoolTimeoutException
的错误。 - 资源释放:
close()
方法会关闭底层的网络 socket 和输入流,释放操作系统资源(如文件句柄、内存)。如果不关闭,会导致内存泄漏和系统资源耗尽。
# 关闭 CloseableHttpClient
- 连接池关闭:
CloseableHttpClient
的close()
方法会关闭连接池中的所有连接,并释放相关资源。如果HttpClient
是全局单例的(推荐方式),通常不需要手动关闭它,但如果是临时创建的客户端,不关闭会导致连接池无法释放,占用内存和网络资源。 - 避免重复关闭:
如果
HttpClient
是单例的,频繁调用close()
会导致异常(例如IllegalStateException: Connection pool shut down
)。
# 不关闭资源的后果
- 连接池耗尽: 所有连接都被占用,后续请求阻塞或失败。
- 内存泄漏: 未关闭的 socket 和流会导致内存占用持续增长。
- 系统资源耗尽: 文件句柄、线程等资源被耗尽,最终导致应用崩溃。
# 关闭资源与被动关闭
了解资源关闭的必要性后,我们继续看上述2个例子中的最大不同————资源的关闭。
第一个是被动关闭,但它没关闭httpclient。
第二个,则是主动关闭的,且它Response和httpclient都关闭的。
第一个的主动关闭,是通过jdk7推出的————try-with-resources
。以下是一个代码示例:
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
// 创建临时 HttpClient 实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("https://api.example.com/data");
// 使用 try-with-resources 自动关闭 response
try (ClassicHttpResponse response = httpClient.execute(request)) {
// 处理响应
System.out.println("Status Code: " + response.getCode());
System.out.println("Response Body: " + EntityUtils.toString(response.getEntity()));
} // response 在此处自动关闭
} // httpClient 在此处自动关闭
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上述例子中的 try (xxx) { ... }
语法就是 Java 7 引入的 try-with-resources
模式(也称为自动资源管理)。它的核心功能是 自动关闭在 try()
括号中声明的资源,前提是这些资源实现了 AutoCloseable
接口(或其子接口 Closeable
)。
try-with-resources
的工作原理
- 资源声明:在
try()
括号中声明并初始化一个或多个资源(如文件流、数据库连接、网络连接等)。 - 自动关闭:无论
try
块是否正常执行完毕,还是抛出异常,Java 会 自动按声明的逆序关闭资源。 - 无需手动调用
close()
:资源的close()
方法会在try
块结束后由 JVM 自动调用。
注意事项:
不仅Get形式的请求需要关闭资源,Post形式的请求也要关闭资源。
资源必须实现
AutoCloseable
:否则无法使用try-with-resources
。
# 发送Post请求
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import java.util.ArrayList;
import java.util.List;
public class HttpClientPostExample {
public static void main(String[] args) throws Exception {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
URIBuilder uriBuilder = new URIBuilder("https://api.example.com/submit");
// 添加查询参数
uriBuilder.addParameter("param1", "value1");
HttpPost request = new HttpPost(uriBuilder.build());
// 设置请求体(表单数据)
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("username", "user123"));
params.add(new BasicNameValuePair("password", "pass123"));
request.setEntity(new UrlEncodedFormEntity(params));
// 发送请求
ClassicHttpResponse response = httpClient.execute(request);
// 处理响应
System.out.println("Status Code: " + response.getCode());
System.out.println("Response Body: " + EntityUtils.toString(response.getEntity()));
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 总结与补充
优点:
- 功能强大,支持复杂的请求配置(如超时、代理、重试等)。
- 社区成熟,文档丰富。
缺点:
- API 相对繁琐,需要手动处理更多细节。
上述的几个例子都是同步请求,如果使用httpclient发送异步请求,如何实现呢?
# OKHttp
OkHttp
是 Square 开发的现代 HTTP 客户端,API 更简洁,支持同步和异步请求。
# 发送Get请求
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpExample {
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Status Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 发送Post请求
import okhttp3.*;
import java.io.IOException;
public class OkHttpPostExample {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String jsonBody = "{\"username\":\"user123\",\"password\":\"pass123\"}";
RequestBody body = RequestBody.create(jsonBody, JSON);
Request request = new Request.Builder()
.url("https://api.example.com/submit")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Status Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 总结与补充
优点:
- API 简洁,代码量少。
- 支持异步请求和连接池管理。
- 自动处理 GZIP 压缩、缓存等。
缺点:
- 需要额外引入依赖。
- 对于复杂请求(如文件上传)需要更多配置。
# 异步请求
之前介绍了httpclient和okhttp,但都只介绍了同步请求,也就是请求后,需要等对方反馈后,即拿到响应后,才继续执行后续的代码。
既然它们2个都支持这种异步请求,那么该如何发送呢?
再介绍之前,再次说明下——异步请求。
异步请求指的是客户端发送请求后,不必等待服务器响应就可以继续执行其他任务。一旦服务器返回结果,会通过回调或者Future等方式通知客户端处理结果。这种方式特别适合于需要长时间等待的服务调用场景,可以有效提高程序的效率和响应速度。
# Apache HttpClient 实现异步请求
从 Apache HttpClient 5.x 开始,官方支持了异步请求的功能。它使用 HttpAsyncClient
来实现异步请求,并依赖于回调机制来处理响应。
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
public class AsyncHttpClientExample {
public static void main(String[] args) throws Exception {
// 创建异步HTTP客户端实例
try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
// 启动客户端
// 启动后,客户端会初始化工作线程(IOReactor),用于处理异步请求和响应。
client.start();
// 创建GET请求
SimpleHttpRequest request = SimpleHttpRequest.get("https://api.example.com/data");
// 执行请求并设置回调
// 提交异步请求,并注册一个回调对象 FutureCallback
client.execute(request, new FutureCallback<SimpleHttpResponse>() {
// 请求成功时调用,参数 response 是响应对象
@Override
public void completed(SimpleHttpResponse response) {
System.out.println("Request completed: " + response);
}
@Override
public void failed(Exception ex) {
System.err.println("Request failed: " + ex.getMessage());
}
// 请求被取消时调用。
@Override
public void cancelled() {
System.out.println("Request cancelled");
}
// 调用 .get() 会阻塞当前线程,直到请求完成,违背了异步请求的初衷。
}).get(); // 使用 .get() 等待请求完成(仅用于演示)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
注意:在实际应用中,你可能不需要调用 .get()
方法,因为它会使异步调用变成同步。这里只是为了演示目的而添加。
此外,你还要注意,main
方法的局限性:
方法是静态的,如果直接注册回调,主线程可能在回调触发前退出。解决方法:
- 在回调中启动新线程或等待(例如
Thread.sleep(...)
)。 - 将异步逻辑封装到非静态方法中。
# OkHttp 实现异步请求
OkHttp 原生支持异步请求,使用 enqueue
方法进行异步调用,并且通过 Callback
接口处理响应。
import okhttp3.*;
import java.io.IOException;
public class AsyncOkHttpExample {
OkHttpClient client = new OkHttpClient();
public static void main(String[] args) {
AsyncOkHttpExample example = new AsyncOkHttpExample();
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
example.run(request);
}
public void run(Request request) {
// 以 异步方式 提交请求,并注册回调处理响应。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
enqueue(Callback callback)
:以异步方式提交请求,并注册回调处理响应。- 异步特性:请求在后台线程执行,不会阻塞主线程。
- 回调机制:通过
Callback
接口的两个方法处理响应结果。
核心流程如下:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://api.example.com/data").build();
client.newCall(request).enqueue(new Callback() { ... });
2
3
- OkHttp 对异步请求的支持更加直接,利用
enqueue
方法配合Callback
接口即可轻松实现异步请求处理。
在选择时,可以根据项目需求以及对库的熟悉程度来决定使用哪一个。如果项目中已经大量使用了某个库,通常建议保持一致性以减少维护成本。
# WebClient
WebClient
是 Spring 5 推出的非阻塞、响应式 HTTP 客户端,适合高并发场景。
# 发送Get请求
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientExample {
public static void main(String[] args) {
WebClient webClient = WebClient.create();
Mono<String> responseMono = webClient.get()
.uri("https://api.example.com/data")
.retrieve()
.bodyToMono(String.class);
String response = responseMono.block(); // 阻塞获取结果
System.out.println("Response: " + response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 发送Post请求
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientPostExample {
public static void main(String[] args) {
WebClient webClient = WebClient.create();
String jsonBody = "{\"username\":\"user123\",\"password\":\"pass123\"}";
Mono<String> responseMono = webClient.post()
.uri("https://api.example.com/submit")
.header("Content-Type", "application/json")
.bodyValue(jsonBody)
.retrieve()
.bodyToMono(String.class);
String response = responseMono.block();
System.out.println("Response: " + response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 总结与补充
优点:
- 非阻塞式,适合高并发场景。
- 支持响应式编程(Reactive Streams)。
缺点:
- 学习曲线较高(需要了解响应式编程概念)。
# 方案对比
工具 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Apache HttpClient | 功能全面、稳定 | API 较繁琐 | 复杂请求场景(如自定义重试、拦截器) |
OkHttp | 高性能、API 简洁 | 高级功能需自定义 | 常规 HTTP 请求、Android 开发 |
WebClient | 响应式、非阻塞、Spring 生态集成 | 学习曲线稍陡 | Spring 5+ 项目、响应式编程 |
RestTemplate | 简单易用 | 同步阻塞、已过时 | 旧 Spring 项目维护 |
HttpURLConnection | 无需额外依赖 | 原始 API 难用 | 简单请求、避免引入新依赖 |