本文讲解和演示了几款主流的Http Client,以及如何使用spring-web中RestTemplate,结合生产故障说明了设置请求超时时间的重要性。阅读本文将提升你的Http客户端编码能力。
在分布式架构Java项目中,注册到同一注册中心的服务,相互调用时通常会使用Feign组件。当你需要在项目中对接第三方Http接口时,就需要一款能够发送Http请求的工具,即Http Client。今天我们一起来看看几款常用的Http Client:
- Java 8中的
HttpUrlConnection
Apache HttpComponents
项目中的HttpClient或httpclient5- Squareup的
okhttp
或okhttp3
- spring-web的
RestTemplate
或WebClient
本文示例代码gitHub地址,欢迎下载:github.com/kqcaihong/m...
一、准备web服务
1.1 创建项目
使用Java 8,创建一个Spring Boot的web项目作为服务端,pom依赖如下。
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learn</groupId>
<artifactId>more</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>more</name>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在application.properties中添加配置(不引入数据库)
ini
spring.application.name=learn-more
server.port=8010
创建启动类
typescript
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MoreApplication {
public static void main(String[] args) {
SpringApplication.run(MoreApplication.class, args);
}
}
创建UserController模拟用户管理功能,提供http接口。此处将记录保存在Map中,模拟数据库操作。
kotlin
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"id", "name", "age"})
public class User {
private Long id;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
}
java
import com.learn.more.entiry.User;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController implements InitializingBean {
// 生成ID
private static final AtomicLong ID_GENERATOR = new AtomicLong(0);
// 模拟数据库保存记录
private static final Map<Long, User> USER_MAP = new ConcurrentHashMap<>();
@GetMapping("/queryById")
public User queryById(@RequestParam Long id) {
return USER_MAP.get(id);
}
@GetMapping("/queryAll")
public List<User> queryAll() {
return USER_MAP.values().stream().sorted(Comparator.comparingLong(User::getId)).collect(Collectors.toList());
}
@PostMapping("/add")
public User add(@RequestBody User user) {
user.setId(ID_GENERATOR.incrementAndGet());
USER_MAP.put(user.getId(), user);
return user;
}
@PostMapping("/addByParam")
public User addByParam(@RequestParam String name, @RequestParam int age) {
User user = new User(name, age);
user.setId(ID_GENERATOR.incrementAndGet());
USER_MAP.put(user.getId(), user);
return user;
}
// 初始化一条记录
@Override
public void afterPropertiesSet() {
User bob = new User(ID_GENERATOR.incrementAndGet(), "Bob", 33);
USER_MAP.put(bob.getId(), bob);
}
}
1.2 测试
启动项目,用postman来测试接口,然后我们用Http Client调用接口。
二、Http Client演示
2.1 HttpURLConnection
HttpURLConnection
是JDK中的标准类,是一种比较原生的实现。Java11中用HTTPClient
取代了HttpUrlConnection
类。
通过下面示例可以看出,HttpURLConnection
几乎没有封装,每次请求时需要自己创建连接、处理IO流等。除非你不想在项目中额外引入其他依赖时,否则不建议使用它。
ini
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class HttpURLClient {
public static void main(String[] args) {
String result = doGet("http://localhost:8010/user/queryAll");
System.out.println(result);
String param = "{"name":"Andy","age":18}";
result = doPost("http://localhost:8010/user/add", param);
System.out.println(result);
}
public static String doGet(String httpUrl) {
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
String result = null;
try {
// 创建连接对象
connection = (HttpURLConnection) new URL(httpUrl).openConnection();
connection.setRequestMethod("GET");
// 设置连接超时时间:15秒
connection.setConnectTimeout(15000);
// 设置读取超时时间:30秒
connection.setReadTimeout(30000);
// 建立连接,发送请求
connection.connect();
// 获取输入流
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
StringBuilder sbf = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sbf.append(line);
sbf.append(System.lineSeparator());
}
result = sbf.toString();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (Objects.nonNull(br)) {
br.close();
}
if (Objects.nonNull(is)) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (Objects.nonNull(connection)) {
connection.disconnect();
}
}
return result;
}
public static String doPost(String httpUrl, String param) {
HttpURLConnection connection = null;
InputStream is = null;
OutputStream os = null;
BufferedReader br = null;
String result = null;
try {
URL url = new URL(httpUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(15000);
connection.setReadTimeout(30000);
// 默认值为:false,当向远程服务器写数据时,需要设置为true
connection.setDoOutput(true);
// 默认值为:true,当前向远程服务读取数据时,设置为true
connection.setDoInput(true);
// 设置入参、响应格式
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept-Type", "application/json");
connection.connect();
// 向输出流写出入参
os = connection.getOutputStream();
os.write(param.getBytes());
// 通过输入流读取响应
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
StringBuilder sbf = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sbf.append(line);
}
result = sbf.toString();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (Objects.nonNull(br)) {
br.close();
}
if (Objects.nonNull(is)) {
is.close();
}
if (Objects.nonNull(os)) {
os.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (Objects.nonNull(connection)) {
connection.disconnect();
}
}
return result;
}
}
目前,我仅看到分布式定时任务调度框架xxl-job(2.2.0版本),以及spring-web中RestTemplate的SimpleClientHttpRequestFactory实现,使用了它。
2.2 Apache httpcomponents
httpcomponents是Apache的一个开源项目,功能强大,使用率也很高,基本上是 Java 平台中事实上的标准HTTP客户端。该项目下的组件分为以下两部分,还提供了客户端身份验证、HTTP状态管理和HTTP连接管理等组件:
HttpCore
:一组低级HTTP传输组件,可用于构建自定义客户端和服务器端HTTP服务;HttpClient
:基于HttpCore的符合HTTP协议的HTTP客户端实现。
引入依赖,目前最新版本是org.apache.httpcomponents.client5。我们先用4.5版本演示。
xml
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
HttpClient支持对某个请求设置超时时间等配置。
ini
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class ApacheHttpClient {
public static void main(String[] args) {
String result = doGet("http://localhost:8010/user/queryAll");
System.out.println(result);
String param = "{"name":"Andy","age":18}";
result = doPost("http://localhost:8010/user/add", param);
System.out.println(result);
}
public static String doGet(String url) {
// 创建一个httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建httpGet连接实例
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept-Type", "application/json");
RequestConfig requestConfig = RequestConfig.custom()
// 连接超时时间
.setConnectTimeout(10000)
// 请求超时时间
.setConnectionRequestTimeout(30000)
// 读取超时时间
.setSocketTimeout(60000)
.build();
// 为请求设置配置
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = null;
String result = "";
try {
response = httpClient.execute(httpGet);
// 通过返回对象获取返回数据
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (Objects.nonNull(response)) {
response.close();
}
if (Objects.nonNull(httpClient)) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static String doPost(String url, String param) {
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建httpPost远程连接实例
HttpPost httpPost = new HttpPost(url);
// 配置请求参数实例
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(10000)
.setConnectionRequestTimeout(30000)
.setSocketTimeout(60000)
.build();
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8));
CloseableHttpResponse response = null;
String result = "";
try {
response = httpClient.execute(httpPost);
// 从响应对象中获取响应内容
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (Objects.nonNull(response)) {
response.close();
}
if (Objects.nonNull(httpClient)) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
再用HttpClient5演示,引入下面依赖
xml
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1.1</version>
</dependency>
java
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Future;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.util.Timeout;
public class ApacheHttpClient5 {
public static void main(String[] args) {
doGet("http://localhost:8010/user/queryAll");
String param = "{"name":"Andy","age":18}";
doPost("http://localhost:8010/user/add", param);
}
// 异步get
public static void doGet(String url) {
try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
client.start();
// 支持request设置超时
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(30))
.setResponseTimeout(Timeout.ofSeconds(30))
.build();
SimpleHttpRequest request = SimpleRequestBuilder.get(url)
.setRequestConfig(config)
.build();
Future<SimpleHttpResponse> future = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse simpleHttpResponse) {
System.out.println(simpleHttpResponse.getBodyText());
}
@Override
public void failed(Exception e) {
}
@Override
public void cancelled() {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
// 同步post
public static void doPost(String url, String param) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8));
httpPost.setHeader("Accept", ContentType.APPLICATION_JSON.toString());
httpPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
try (CloseableHttpClient client = HttpClients.createDefault()) {
CloseableHttpResponse response = client.execute(httpPost);
HttpEntity entity = response.getEntity();
System.out.println(EntityUtils.toString(entity));
response.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 OkHttpClient
OkHttpClient
是由美国Square公司提供的开源库,支持链接复用、响应缓存、请求失败自动重连等。从Android4.4开始HttpURLConnection的底层实现采用的是okHttp。 我们同时引入okhttp和okhttp3依赖,实际使用中选择一个合适版本即可。
xml
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<!-- 因为下方引入了okhttp3,避免冲突-->
<exclusions>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.8.0</version>
</dependency>
从demo可以看出,OkHttpClient支持客户端复用,针对client设置超时时间。
java
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
public class OkHttpClientTest {
public static void main(String[] args) throws IOException {
doAsyncGet("http://localhost:8010/user/queryAll");
String param = "{"name":"Andy","age":18}";
String result = doPostByOkhttp3("http://localhost:8010/user/add", param);
System.out.println(result);
}
private static void doAsyncGet(String url) {
try {
// client复用,对客户端设置超时
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(30L, TimeUnit.SECONDS);
client.setReadTimeout(30L, TimeUnit.SECONDS);
client.setWriteTimeout(30L, TimeUnit.SECONDS);
Request request = new Request.Builder()
.get()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
System.out.println(response.body().string());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private static String doPostByOkhttp3(String url, String param) throws IOException {
okhttp3.OkHttpClient client = new okhttp3.OkHttpClient.Builder()
.connectTimeout(30L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.build();
RequestBody requestBody = RequestBody.create(param, MediaType.parse("application/json"));
okhttp3.Request request = new okhttp3.Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Accept-Type", "application/json")
.build();
okhttp3.Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
ResponseBody body = response.body();
return Objects.nonNull(body) ? body.string() : "";
}
return "";
}
}
三、spring-web中RestTemplate
RestTemplate
是Spring封装的处理同步HTTP请求的模版类。从官方文档可知,在Spring 5.0之后,官方推荐一种非阻塞响应式的HTTP 请求处理方案WebClient
,支持同步和异步以及流场景。RestTemplate将在未来的版本中弃用,并且不会添加主要的新特性。
Spring官方文档地址:docs.spring.io/spring-fram...
今天,我们来简单了解一下如何使用RestTemplate。
3.1 创建RestTemplate
RestTemplate
在spring-web.jar中。当我们引入spring-boot-starter-web依赖时,间接引入了spring-web。如文档所说,RestTemplate
作为模板类,底层支持多种Http Client,可根据项目情况选择其一,默认采用HttpURLConnection
。 在启动类中创建ClientHttpRequestFactory
、RestTemplate
实例。
java
@Bean
public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// HttpURLConnection只支持设置这两个超时时间
factory.setConnectTimeout(10000);
factory.setReadTimeout(30000);
return factory;
}
// 当使用okHttp3作为RestTemplate底层Http Client时
@Bean
public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory() {
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
factory.setConnectTimeout(10000);
factory.setReadTimeout(30000);
factory.setWriteTimeout(30000);
return factory;
}
当使用无参构造器创建RestTemplate
时,默认使用SimpleClientHttpRequestFactory
,它使用了JDK中HttpUrlConnection
作为Http Client。
typescript
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
3.2 GET请求
创建测试类,用getForObject()
调用/user/queryAll
接口,响应类型声明为List.class时,集合中元素类型不是User而是LinkedHashMap
。另外,我们可以将响应类型声明为User[]
。
java
import com.learn.more.entiry.User;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MoreApplication.class)
class RestTemplateTest {
public static final String BASE_URL = "http://localhost:8010";
@Autowired
private RestTemplate restTemplate;
@Test
void get() {
String url = BASE_URL + "/user/queryAll";
// restTemplate 会把复杂的对象转换成 LinkedHashMap
//
List allUser = restTemplate.getForObject(url, List.class);
System.out.println(allUser);
}
@Test
void getAsArray() {
String url = BASE_URL + "/user/queryAll";
User[] allUser = restTemplate.getForObject(url, User[].class);
System.out.println(allUser);
}
}
为了从响应中直接获得List<User>
,可以使用抽象类ParameterizedTypeReference
,创建它的匿名子类。此时需要使用exchange()
或execute()
方法发送请求,更细致地控制请求行为,如HttpMethod、Header信息、响应类型等。
Java
@Test
void getAsList() {
String url = BASE_URL + "/user/queryAll";
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
header.put(HttpHeaders.ACCEPT, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
HttpEntity<User> entity = new HttpEntity<>(header);
// 匿名子类
ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {
};
ResponseEntity<List<User>> allUser = restTemplate.exchange(url, HttpMethod.GET, entity, type);
System.out.println(allUser.getBody());
}
3.3 POST请求
我们来看看Content-Type
分别为application/x-www-form-urlencoded
和application/json
两种情况。
Java
@Test
void postByParam() {
String url = BASE_URL + "/user/addByParam";
// form表单传参
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("name", "Cathy");
map.add("age", 23);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, header);
ResponseEntity<User> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, User.class);
System.out.println(response.getBody());
}
@Test
void postByBody() {
String url = BASE_URL + "/user/add";
User tom = new User("Cathy", 23);
// body体传参,json格式
User user = restTemplate.postForObject(url, tom, User.class);
System.out.println(user);
}
此外,form表单传参时,还可以将请求参数以K=V
方式用&
拼接来构建HttpEntity,如下。
java
@Test
void postByParam() {
String url = BASE_URL + "/user/addByParam";
// 设置请求的 Content-Type
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
String param = new StringBuilder().append("name=Judy").append("&").append("age=18").toString();
HttpEntity<String> httpEntity = new HttpEntity<>(param, header);
ResponseEntity<User> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, User.class);
System.out.println(response.getBody());
}
使用RestTemplate执行DELETE、PUT等请求,以及文件上传功能,本文将不做演示。
3.4 请求超时设置
同步请求Http接口时,如果不设置超时时间,对方服务故障或接口响应时间异常,就会阻塞当前线程。
生产故障
我曾经遇到这样的生产故障:在公司将机房迁移到海外后,原本正常的某个定时任务,执行中经常出现阻塞:业务中请求某个国内第三方系统接口时,一直没有响应。 经过排查,发现使用SimpleClientHttpRequestFactory
创建RestTemplate
时,并没有主动设置超时时间:默认超时时间为-1,即永不超时。 因此,调用外部Http接口时,应该设置超时时间,避免因为第三方接口故障拖累我方服务。
故障处理
做如下修改之后,定时任务仍会偶尔某次执行失败,但是不会阻塞后续正常运行。
四、总结
各个工具的特点简单总结如下。在实际开发中,按照需求场景选择合适的即可。
- 如果不想添加任何外部库,那么使用原生
HttpURLConnection
或HTTPClient
(JDK11及之后版本)即可; Apache HttpClient
灵活性更高,有更多的参考文档;OkHttpClient
性能最佳,客户端对象可重复使用,功能丰富,高度可配置;- 使用
Spring
或Spring Boot
开发应用时,使用RestTemplate
或WebClient
即可。