前言
在Springboot中,我们需要给第三方服务需要发送请求,并接受处理请求的处理结果。这时候我们就需要自带的请求工具,或者依赖第三方的服务来实现请求的发送和处理。
Spring内置的使用
Http请求信息常量
Spring框架中已经有很多有关Http请求和响应的相关信息的封装常量信息。
HttpMethod
: 定义了常见的 HTTP 请求方法。包括GET
、POST
、PUT
、DELETE
、HEAD
、OPTIONS
、TRACE
等
HttpStatus
: 定义了 HTTP 响应状态码及其相关的描述信息 , 例如 OK
(200) , NOT_FOUND
(404), INTERNAL_SERVER_ERROR
(500)
MediaType
: : 定义了HTTP常用的ContentType的信息, 如 application/json
, application/x-www-form-urlencoded
等信息
HttpHeaders
: 定义了HTTP常用的请求头的信息方法,如可以设置一些常用的请求头 setContentType
等
Http相关的工具类
Spring框架内置了好用的工具类
UriComponentsBuilder
: 用于构建组装操作的请求路径及其参数的工具类。
注意: 会自动对路径和查询参数进行编码,确保特殊字符(如空格、中文等)在 URL 中能够正确表示
// fromUriString : 完整的路径字符串
UriComponentsBuilder.fromUriString(uriString).build().toUriString()
// queryParam : 增加参数
UriComponentsBuilder.fromUriString(uriString).queryParam("param","123").build().toUriString()
MultiValueMap
: 用于构建multipart/form-data请求的参数,用于传递文件和参数。
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
// 增加文件的参数
ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename(); // 确保在 Content-Disposition 中包含文件名
}
};
formData.add("file", fileResource);
// 增加普通参数
formData.add("param", "paramResult");
RestTemplate 发送请求
RestTemplate
是 Spring 框架提供的用于访问 RESTful 服务的客户端工具。它简化了在 Java 应用中发送 HTTP 请求和处理响应的过程。
在构建发送请求的过程中,会使用 HttpEntity
,它封装了请求的头信息和请求体等所有元素 。 在接受请求的时候,会使用ResponseEntity
,它封装包含了响应状态码、响应头和响应体等信息。
发送简单的Get请求
getForEntity
: 获取ResponseEntity格式数据getForObject
: 直接获取响应的body数据
// 使用getForEntity方法
ResponseEntity<String> forEntity = restTemplate
.getForEntity("http://localhost:8080/hello", String.class);
// 使用getForObject方法
String forObject = resTemplate.getForObject("http://localhost:8080/hello", String.class);
如果请求需要携带对应的参数, url则需要提供对应参数值的占位符,或者使用UriComponentsBuilder
来统一构建路径和参数
// 使用占位符参数赋值
ResponseEntity<String> forEntity = restTemplate
.getForEntity("http://localhost:8080/hello?param={param}", String.class,"123");
// 使用UriComponentsBuilder来统一构建
String url = UriComponentsBuilder.fromUriString("http://localhost:8080/hello")
.queryParam("param", "123").build().toUriString();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class,"123");
简单的发送Post请求
postForObject
: 直接获取响应的body数据postForEntity
: 获取ResponseEntity格式数据
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 构建请求参数
TestParam testParam = new Testparam();
testParam.setName("gjc");
// 封装到请求HttpEntity
HttpEntity<User> requestEntity = new HttpEntity<>(testParam, headers);
// 发送请求
String response = restTemplate.postForObject(url, requestEntity, String.class);
使用formData格式发送文件
使用MultiValueMap
来构造参数
注意: 我这边的file是使用的controller层接受到的 MultipartFile
类型的文件
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 使用MultiValueMap构造formdata的参数
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename(); // 确保在 Content-Disposition 中包含文件名
}
};
formData.add("file", fileResource);
// 发送请求
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(formData, headers);
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
自定义使用exchange方法
使用exchange提供了更强大的灵活性来发送 HTTP 请求并处理响应。不过也是同样使用HttpHeaders
构建请求头,使用HttpEntity
组装构建发送的相关信息, exchange
方法提供组装url,请求方法,请求体内容等内容。
String url = "http://example.com/api/create";
// 创建请求实体
TestParam testParam = new Testparam();
testParam.setName("gjc");
// 请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<MyRequestObject> requestEntity = new HttpEntity<>(testParam, headers);
// 可以额外的指定请求方法
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
配置超时等综合信息
通过RestTemplate
的setRequestFactory
方法设置请求超时等参数,手动构建SimpleClientHttpRequestFactory
来设置连接超时和读取超时时间
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// 设置连接超时时间为3秒(单位:毫秒)
requestFactory.setConnectTimeout(3000);
// 设置读取超时时间为5秒(单位:毫秒)
requestFactory.setReadTimeout(5000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
RestTemplate 默认使用的是 HttpsURLConnection
, 如果配置更换RequestFactory为HttpComponentsClientHttpRequestFactory
则可以使用Apache HttpComponents这个Http请求客户端库。
针对Https请求: 默认使用的是java 的标准安全机制来建立 SSL/TLS 连接。 如果要修改取消验证,就需要更换HttpClient的 RequestFactory进行配置。
WebClient的发送
Spring 5 引入了新的 WebClientAPI,取代了现有的 RestTemplate 客户端。ResTemplate是基于传统的同步阻塞式 I/O 模型。当使用RestTemplate
发送请求时,线程会被阻塞,直到服务器返回响应。而WebClient是反应式编程模型,基于 Reactor 库构建,是非阻塞式的。
综合来讲,WebClient就是为了异步和反应式编程而设计的。
基本
retrieve
: 用于指定如何提取响应bodyToMono
: 表示将响应体转换为一个Mono<>对象bodyToFlux
: 将响应结果处理为 Flux 对象
subscribe
: 订阅这个对象,异步获取信息block
: 同步获取这个信息
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
发送Get请求
使用我们前面提到的UriComponentsBuilder
来定义路径,能更方便的拼接路径。然后使用构建webClient使用get方法请求数据。
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("/hello")
.queryParam("param", "test");
WebClient webClient = WebClient.create("http://localhost:8080");
// 发送get请求,获取responseMono对象来获取异步响应结果
Mono<String> responseMono = webClient.get()
.uri(builder.toUriString())
.retrieve()
.bodyToMono(String.class);
responseMono.subscribe(System.out::println);
发送Post请求
- 针对
application/json
或者Text_Plian
等body数据时
使用 BodyInserters.fromValue
: 直接传输json字符串数据来填充body数据
Mono<String> responseMono = webClient.post()
....省略...
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(JSONObject.toJsonString(user)))....
- 针对表单数据
applicaiton/x-www-form-urlencoded
BodyInserters.fromFormData
方法提供了一种方便的方式来构建和插入表单数据。可以通过链式调用with
方法来添加多个键值对
Mono<String> responseMono = webClient.post()
....省略...
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("param1", "value1")
.with("param2", "value2"))
- 针对文件等表单数据 multipart/form-data
BodyInserters.fromMultipartData
方法将MultiValueMap
插入到body中。构造数据的方法类似于RestTemplate构造form-data请求内容数据。
// 创建请求内容的参数
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename(); // 确保在 Content-Disposition 中包含文件名
}
};
formData.add("file", fileResource);
Mono<String> responseMono = webClient.post()
....省略...
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(formData))
OpenFeign
Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由社区进行维护,更名为 Openfeign , 且在Feign的基础上支持了SpringMVC的注解。
前置配置
在 Spring Boot 的主应用类上添加@EnableFeignClients
注解,这样 Spring 会扫描并创建 Feign 客户端接口的代理实现,用于发送请求。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 配合使用 -->
<dependencyManagement>
<dependencies>
<!-- SpringCloud netflix微服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意:上面SpringCloud的版本与Springboot的对应 ,我当前springboot的版本是3.3.4,所以选择对应的2023.0.3版本。
如果不确定对应的版本可以去官网查看spring.io/projects/sp… 或者start.spring.io/actuator/in… 都可以找到对应的版本
发送请求
具体使用和Spring MVC类似,对@GetMapping,@PostMapping等注解的使用
声明@FeignClient
的name名称和url地址,指定访问请求的基础配置。最后再具体使用的位置上注入该bean,调用指定的方法,就能发送对应的请求信息。
@FeignClient(name = "user-service", url = "http://localhost:8080")
public interface UserServiceClient {
// get请求
@GetMapping("/get")
String get(@RequestParam("param") String param);
// post请求体的json内容
@PostMapping("/post")
String post(@RequestBody User user);
// 请求文件
@PostMapping(value = "/upload")
String uploadFile(@RequestPart("file") MultipartFile file);
// 获取请求响应和结果
@GetMapping(value = "/responseFile")
feign.Response responseFile();
}
设置请求超时时间
通过设置配置文件超时的时间,可以指定全局或者局部。
spring:
cloud:
openfeign:
client:
config:
// default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
// 为serviceC这个服务单独配置超时时间, 单个配置的超时时间将会覆盖全局配置。
serviceC:
connectTimeout: 30000
readTimeout: 30000
OkHttp3
OkHttp3 是一个用于 Android 和 Java 应用程序的开源 HTTP 客户端库。它由 Square 公司开发,旨在使 HTTP 通信变得简单、高效且功能丰富。在现代软件开发中,无论是移动应用还是后端服务,与网络资源进行交互是必不可少的,OkHttp3 提供了一种强大的方式来处理 HTTP 请求和响应。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
基本使用
构建OkHttpClient
,设置请求信息Request
,发送请求newCall
调用方法
// 构建
OkHttpClient client = new OkHttpClient();
// 请求信息
Request request = new Request.Builder()
.url("http://www.baidu.com")//请求接口。
.build();//创建Request 对象
// 发送请求获取响应
Response response = client.newCall(request).execute();
注意: reponse.body().string() ,获取流的读操作, 所以只能执行一次
Basic Authentication
通过OkHttpClient.Buider 构建 authenticator , 往请求头上塞Authorization , 有关credential 可以通过 Credentials.basic
方法来构建
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) {
String credential = Credentials.basic("username", "password");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
同步/异步请求
execute()
方法直接执行就是同步请求,enqueue()
方法执行接受回调信息
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//默认就是GET请求,可以不写
.build();
// 同步请求
Response response = okHttpClient.newCall(request).execute();
String string = response.body().string();
// 异步请求
okHttpClient.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 {
log.info(TAG, "okHttpGet enqueue: onResponse:"+ response.body().string());
ResponseBody body = response.body();
String string = body.string();
byte[] bytes = body.bytes();
InputStream inputStream = body.byteStream();
}
})
post json请求
使用 RequestBody
来创建参数
// 构造contentType 和 json的body
MediaType contentType = MediaType.parse("application/json; charset=utf-8");
String json = "jsonString";
RequestBody body = RequestBody.create(contentType, json );
post form请求
使用 muletipartBody.Builder
来构建参数,可以传递普通字符串参数,也可以传递文件内容。 主要用于构建multipart/form - data
类型的请求体
MultipartBody.Builder muletipartBodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);
// 1. 添加key/value
muletipartBodyBuilder.addFormDataPart(key, value);
//文件 MultipartFile类型的fileData
byte[] data = fileData.getBytes();
String name = fileData.getOriginalFilename();
String contentType = fileData.getContentType();
// 2. 添加key/name/文件内容
RequestBody fileBody = RequestBody.create(data, MediaType.parse(contentType));
muletipartBodyBuilder.addFormDataPart(key, name, fileBody);
post wwwform 请求
FormBody.Builder
用于构建application/x - www - form - urlencoded
类型的请求体。这种类型的请求体主要用于发送简单的表单数据,即键值对形式的数据,其中值是简单的文本内容,没有文件等复杂的数据类型
FormBody.Builder formBuilder = new FormBody.Builder();
formBuilder.addEncoded(key, value); // 指定默认URL编码的value值
formBuilder.add(key, value); // 如果value前面已经被编码过了或者value是json数据,就直接add
获取响应体数据
可以通过Response
的body
内容中获取bytes/string
内容或者byteStream
Response response = okHttpClient.newCall(request).execute()
response.body().byteStream() // 获取字节流,需要手动close关闭流数据(流式)
response.body().source() // 流式处理器,需要手动关闭流数据,高效一块一块的读取
// 配合exhausted判断和read/readUtf8Line读取实现
byte[] res = response.body.bytes(); //获取字节数组内容, 同时触发关闭响应体
String res = reponse.body().string() //获取字符串内容,同时触发关闭响应体
注意:数据只能获取读取一次,不能重复读取
流式的参考案例:获取流式处理器,来实现处理分块读取,
try (BufferedSource source = response.body().source()) {
Buffer buffer = new Buffer();
while (!source.exhausted()) {
// 第一种:每次读取 8KB 数据
long bytesRead = source.read(buffer, 8192);
if (bytesRead == -1) break;
// 第二种:按行读取
// String line = source.readUtf8Line(); // 按行读取
// if (line == null) break; // 数据流结束
// 处理读取的数据
System.out.println("Read " + bytesRead + " bytes");
}
}
忽略SSL证书
X509TrustManager
用于管理和验证服务器证书,HostnameVerifier
用于验证服务器的主机名是否与证书中的主机名匹配。
所以我们可以配置一个不验证证书的TrustManager
,空实现方法内容。和配置一个HostnameVerifier
的verify返回true的方法
public X509TrustManager x509TrustManager() {
// 配置一个不验证证书的TrustManager
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
public SSLSocketFactory sslSocketFactory() {
try {
// 信任任何链接
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
}
return null;
}
public static HostnameVerifier getUnsafeHostnameVerifier() {
// 返回 true ,不验证主机名与证书是否匹配
return (hostname, session) -> true;
}
构建请求头
使用Request.Builder的方法来构建请求头
Request.Builder requestBuilder = builder.url(urlBuilder.build()).post(requestBody);
if (header != null) {
for (String key : header.keySet()) {
requestBuilder.addHeader(key, header.get(key));
}
}
配置连接池
配置连接数目和连接空闲时间
public ConnectionPool pool() {
// 最大空闲连接数 200个
int maxIdleConnections = 200;
// 连接空闲时间最多为300秒
long keepAliveDuration = 300L;
return new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS);
}
综合配置应用上Client
对OkHttpClient的综合配置,例如忽略SSL证书,连接池,拦截器,代理缓存等。
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
// 配置忽略SSL证书
.sslSocketFactory(sslSocketFactory(), x509TrustManager())
.hostnameVerifier(getUnsafeHostnameVerifier())
// 是否开启缓存
.retryOnConnectionFailure(false)
// 连接池
.connectionPool(pool())
.connectTimeout(connectTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout,TimeUnit.SECONDS)
// 设置代理
// .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888)))
// 拦截器
// .addInterceptor()
.build();
}
总结
RestTemplate:
优点:简单易用,集成Spring生态系统。 缺点:不支持异步,需要自定义配合Future实现。性能高并发场景下比较局限。没有响应式的支持
WebClient :
优点: 支持响应式编程,适合高并发。灵活的配置 缺点: 学习成本高,需要对响应式编程的理解比较深刻。生态集成传统框架不友好,可能存在兼容问题。
OpenFeign :
优点: 接口式的编程,熟悉springmvc的很容易上手,集成SpringCloud生态,配置服务发现和负载均衡方便。 缺点: 因代理机制性能要求比较高。需要依赖SpringCloud环境,配置相对有点麻烦。
OkHttpClient :
优点: 高性能高并发,连接池复用连接。支持请求的请求形式丰富。 缺点: 与Spring生态集成不够前面的那些好。缺少响应式编程支持。配置相对也有点复杂。