传统服务端请求HTTP服务,一般使用Jdk自带HttpURLConnection 或者Apache工具类HttpClient,请求过于繁琐与复杂,还需要关注资源回收。
因此,Spring 提供RestTemplate 模板类简化操作。RestTemplate 属于HTTP请求同步阻塞式工具类,底层基于HTTP客户端库(例如JDK HttpURLConnection 、Apache HttpComponents 或者okHttp等)封装更加简单易用模板方法API,方便程序员利用模板方法发起网络请求和处理,很大程度上能够提升开发效率。
01 使用案例
1.1 非Spring项目
java
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
java
public class RestTemplateMain {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/youxinren/user";
String reponse = restTemplate.getForObject(url, String.class);
System.out.println(reponse);
}
}
1.2 Spring项目
gradle
implementation platform('org.springframework.boot:spring-boot-dependencies:2.3.10.RELEASE')
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'
java
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
注意,RestTemplate 默认使用JDK自带HttpURLConnection 作为底层客户端实现,不过也支持切换至高性能OkHttp 或者Apache的HttpClient 客户端。从性能方面对比,OkHttp 工具类性能明显高于HttpClient ,然而HttpClient 又高于HttpURLConnection。
java
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
// 使用HttpClient作为底层客户端
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int timeout = 5000;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
}
java
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
// 使用OkHttpClient作为底层客户端
private ClientHttpRequestFactory getClientHttpRequestFactory() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
}
1.3 拦截器
使用RestTemplate调用远程服务,可以使用拦截器添加请求头传递信息,比如添加TraceId便于日志串联完整请求链路,提升问题定位速读。
第一步,定义LogFilter拦截所有接口请求,使用MDC底层ThreadLocal变量保存请求TraceId。
java
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (StringUtils.isBlank(MDC.get("TRACE_ID"))) {
MDC.put(""TRACE_ID"", UUID.randomUUID().toString());
}
chain.doFilter(request, response);
}
}
第二步,实现ClientHttpRequestInterceptor接口,通过MDC获取当前请求TraceId,然后设置到请求头。
java
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().set("traceId", MDC.get("TRACE_ID"));
return execution.execute(request, body);
}
}
第三步,配置RestTemplate自定义RestTemplateInterceptor实现。
java
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor()));
return restTemplate;
}
@Bean
public RestTemplateInterceptor restTemplateInterceptor() {
return new RestTemplateInterceptor();
}
}
1.4 扩展请求类型
SpringBoot使用RestTemplate远程调用接口,不支持Content-Type=text/html;charset=UTF-8
类型,导致出现no suitable HttpMessageConverter
异常。
java
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML, MediaType.TEXT_PLAIN));
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
}
02 RestTemplate API
RestTemplate 包装各种网络请求方式(GET 、POST 、PUT 、DELETE和文件上传与下载),极大简化开发工作量。
2.1 GET
RestTemplate提供两种方式发送GET请求,getForObject仅返回响应体,getForEntity返回ResponseEntity封装响应体、请求头信息。
java
public class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
// 不带参get请求
@Test
public void testGetNoArgument(){
String url = "http://host:8080/getNoArgument";
// 发起请求, 直接返回对象
ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);
System.out.println(responseBean);
}
// 使用占位符号传参
@Test
public void testGetWithArgument(){
// 使用占位符号传参
String url = "http://host:8080/getWithArgument/{1}";
// 发起请求, 直接返回对象(Restful风格)
ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001");
System.out.println(responseBean);
}
// 带参请求
@Test
public void testGetForObject1(){
String url = "http://host:8080/getForObject?userName={userName}&password={password}";
// 请求参数
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("userName", "feiyu");
uriVariables.put("password", "123456");
// 发起请求, 直接返回对象(带参数请求)
ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, uriVariables);
System.out.println(responseBean);
}
@Test
public void testGetForObject2() {
// URL特殊符号进行转义查询, 如{、}
String param = "{"lat":" + latitude + ","lon":" + longitude + ","ver": 1}";
URI requestUrl = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("param", param)
.build().encode().toUri();
JSONObject result = restTemplate.getForObject(requestUrl, JSONObject.class);
}
}
RestTemplate工具类还提供exchange通用请求方法,用于封装GET、POST、DELETE、PUT、OPTIONS、PATCH等方法请求。
java
public class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
@Test
public void testGetForEntity(){
String url = "http://localhost:8080/getForEntity";
// 发起请求, 返回全部信息
ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class);
// 获取响应体
System.out.println("响应体=" + response.getBody().toString());
// 以下是getForEntity比getForObject多出来的内容
HttpStatus statusCode = response.getStatusCode();
int statusCodeValue = response.getStatusCodeValue();
HttpHeaders headers = response.getHeaders();
System.out.println("响应状态=" + statusCode);
System.out.println("响应状态码=" + statusCodeValue);
System.out.println("响应头信息=" + headers);
}
@Test
public void testExchange() {
String url = "http://localhost:8080/exchange";
// 请求头
HttpHeaders headers = new HttpHeaders();
headers.add("auth", "xuhx");
// 封装请求头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(headers);
ResponseEntity<Map<?, ?>> exchange = restTemplate.exchange(url, HttpMethod.GET, formEntity, Map.class);
}
}
2.2 POST
POST请求用于向服务器提交数据,属于非幂等操作。与GET请求不同,POST封装请求数据到请求体,而不是URL,适用于传输大量数据和敏感信息。
java
class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
// 模拟表单提交
@Test
public void testPostByForm() {
String url = "http://host:8080/testPostByForm";
// 请求头: Content-Type=x-www-form-urlencoded
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 提交参数设置
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("userName", "feiyu");
map.add("password", "123456");
// 组装请求体
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
// 发起请求
ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
System.out.println(responseBean.toString());
}
// 模拟JSON提交
@Test
public void testPostByJson() {
String url = "http://host:8080/testPostByJson";
// 入参
RequestBean request = new RequestBean();
request.setUserName("feiyu");
request.setPassword("123456789");
ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
System.out.println(responseBean.toString());
}
// 模拟重定向
@Test
public void testPostByLocation(){
String url = "http://host:8080/testPostByLocation";
// 入参
RequestBean request = new RequestBean();
request.setUserName("feiyu");
request.setPaaword("123456789");
// 接收服务器重定向URL, 服务器返回值=redirect:index.html
// 打印: http://host:8080/index.html
URI uri = restTemplate.postForLocation(url, request);
System.out.println(uri.toString());
}
}
2.3 PUT
HTTP PUT请求用于创建新资源或者替换目标资源。其次,PUT请求属于幂等操作,意味着无论调用多次效果都相同,不会产生副作用。
java
public class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
// 模拟PUT请求JSON提交
@Test
public void testPutByJson(){
String url = "http://host:8080/testPutByJson";
// 入参
RequestBean request = new RequestBean();
request.setUserName("feiyu");
request.setPassword("123456789");
// 模拟PUT请求JSON提交, 没有返回值
restTemplate.put(url, request);
}
}
2.4 DELETE
HTTP DELETE请求用于删除指定资源,广泛应用于数据库或者文件系统交互操作,例如删除用户、删除文件信息。
java
class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
// 模拟JSON提交, DELETE请求
@Test
public void testDeleteByJson(){
String url = "http://localhost:8080/testDeleteByJson";
// 提交DELETE请求, 没有返回值
restTemplate.delete(url);
}
}
2.5 文件上传与下载
大文件下载需要设置请求头ContentType=APPLICATION_OCTET_STREAM,表示以流的形式进行数据加载。RequestCallback结合File.copy保证接收到一部分文件内容,就向磁盘写入一部分内容,而不是全部加载到内存,最后写入磁盘文件。
java
public class TestRestTemplate {
@Autowired
private RestTemplate restTemplate;
// POST请求模拟文件上传
@Test
public void testUpload(){
String url = "http://host:8080/upload";
// 请求头: ContentType=multipart/form-data
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 提交参数设置
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("file", new FileSystemResource(new File("image1.jpg")));
paramMap.add("file", new FileSystemResource(new File("image2.jpg")));
// 服务端接受额外参数, 可以传递
paramMap.add("userName", "feiyu");
// 组装请求体
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(paramMap, headers);
// 发起请求
ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
System.out.println(responseBean.toString());
}
// POST请求模拟文件下载
@Test
public void downloadFile() throws IOException {
String url = "http://host:8080/downloadFile/{1}/{2}";
// 发起请求, 直接返回对象(restful风格)
ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class, userName,fileName);
System.out.println("文件下载请求结果状态码 = " + rsp.getStatusCode());
// 下载文件内容保存到本地
Files.write(Paths.get("image.jpg"), Objects.requireNonNull(rsp.getBody(), "未获取到下载文件"));
}
// 大文件下载
@Test
public void downloadBigFile() throws IOException {
String url = "http://host:8080/downloadFile/{1}";
// 定义请求头接收类型
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// 响应进行流式处理而不是全部加载到内存
String targetPath = "data.csv";
restTemplate.execute(url, HttpMethod.GET, requestCallback, response -> {
Files.copy(response.getBody(), Paths.get(targetPath));
return null;
}, userName);
}
}
03 总结
RestTemplate通过高度封装请求API、深度整合Spring生态以及灵活扩展机制,显著降低HTTP通信开发复杂度。尽管Spring5推荐使用WebClient作为响应式编程替代方案,但是RestTemplate在传统同步场景仍具有广泛应用价值。