文章目录
RestTemplate简介
RestTemplate是Spring3.0后开始提供的用于访问 Rest 服务的轻量级客户端,相较于传统的HttpURLConnection、Apache HttpClient、OkHttp
等框架,RestTemplate大大简化了发起HTTP请求以及处理响应的过程。
RestTemplate只是对其它Rest客户端的一个封装,本身并没有自己的实现。
RestTemplate默认实现是HttpURLConnection,这是JDK自带的REST客户端实现。
引入哪种Http客户端的Jar包,就会使用哪种Http客户端的实现。
相关Http客户端Maven依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.11</version>
</dependency>
RestTemplate配置
RestTemplate包含以下几个部分:
HttpMessageConverter
对象转换器ClientHttpRequestFactory
默认是JDK的HttpURLConnection
ResponseErrorHandler
异常处理ClientHttpRequestInterceptor
请求拦截器
使用HttpClient作为实现
首先需要引入HttpClient的Maven依赖。
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@Configuration
public class RestClientConfig {
private static final Logger logger = LoggerFactory.getLogger(RestClientConfig.class);
// 连接池的最大连接数默认为0
@Value("${remote.maxTotalConnect:0}")
private int maxTotalConnect;
// 单个主机的最大连接数
@Value("${remote.maxConnectPerRoute:200}")
private int maxConnectPerRoute;
// 连接超时默认2s
@Value("${remote.connectTimeout:2000}")
private int connectTimeout;
// 读取超时默认30s
@Value("${remote.readTimeout:30000}")
private int readTimeout;
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory());
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
// 设置编码格式为UTF-8
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (item.getClass() == StringHttpMessageConverter.class) {
converterTarget = item;
break;
}
}
if (converterTarget != null) {
converterList.remove(converterTarget);
}
HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converterList.add(1, converter);
logger.info("-----restTemplate-----初始化完成");
return restTemplate;
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
httpClientBuilder.setSSLContext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
// 注册http和https请求
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
// 开始设置连接池
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// 最大连接数
poolingHttpClientConnectionManager.setMaxTotal(maxTotalConnect);
// 同路由的并发数,路由是对maxTotal的细分
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute);
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
HttpClient httpClient = httpClientBuilder.build();
// 数据读取超时时间
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient连接配置
// 连接超时
clientHttpRequestFactory.setConnectTimeout(connectTimeout);
// 读取超时
clientHttpRequestFactory.setReadTimeout(readTimeout);
// 连接不够用的等待时间
clientHttpRequestFactory.setConnectionRequestTimeout(20000);
return clientHttpRequestFactory;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
logger.error("初始化HTTP连接池出错", e);
}
return null;
}
}
使用OkHttp作为实现
首先需要引入OkHttp的Maven依赖。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory(){
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
// 单位:毫秒
factory.setConnectTimeout(10000);
factory.setReadTimeout(10000);
factory.setWriteTimeout(10000);
return factory;
}
}
发送GET请求
// 1-getForObject()
User user1 = this.restTemplate.getForObject(uri, User.class);
// 2-getForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class);
HttpStatus statusCode = responseEntity1.getStatusCode();
HttpHeaders header = responseEntity1.getHeaders();
User user2 = responseEntity1.getBody();
// 3-exchange()
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build();
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
User user3 = responseEntity2.getBody();
发送POST请求
// 1-postForObject()
User user1 = this.restTemplate.postForObject(uri, user, User.class);
// 2-postForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);
// 3-exchange()
RequestEntity<User> requestEntity = RequestEntity.post(new URI(uri)).body(user);
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
设置HTTP Header
// 1-Content-Type
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.contentType(MediaType.APPLICATION_JSON)
.body(user);
// 2-Accept
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.accept(MediaType.APPLICATION_JSON)
.body(user);
// 3-Other
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.header("Authorization", "Basic " + base64Credentials)
.body(user);
发送文件
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add("file", new ClassPathResource("/tmp/user.txt"));
RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(multiPartBody);
下载文件
// 小文件
RequestEntity requestEntity = RequestEntity.get(uri).build();
ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);
byte[] downloadContent = responseEntity.getBody();
// 大文件
ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {
@Override
public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {
File rcvFile = File.createTempFile("rcvFile", "zip");
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);
}
};
File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor);
构建测试用的Http服务
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private Integer age;
}
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/")
@Slf4j
public class DemoController {
@GetMapping("demo/{id}/{userName}")
public User get(@PathVariable Long id, @PathVariable String userName, Integer age){
log.info("id:{}, userName:{}, age:{}", id, userName, age);
User user = new User();
user.setId(id);
user.setUsername(userName);
user.setAge(age);
return user;
}
@PostMapping("demo")
public User post(@RequestBody String json){
log.info("json:{}", json);
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
return user;
}
}
RestTemplate使用
GET请求
在RestTemplate中,发送一个GET请求,有两种方式:getForObject()
和getForEntity()
。
getForEntity
getForEntity
方法的返回值是一个ResponseEntity<T>
,ResponseEntity<T>
是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体
等。
示例:
String url = "http://127.0.0.1:8080/demo/{id}/{userName}?age=18";
// 方式一
ResponseEntity<User> userResponseEntity = restTemplate.getForEntity(url, User.class, 1001L, "Tom");
User user = userResponseEntity.getBody();
HttpStatus statusCode = userResponseEntity.getStatusCode();
int statusCodeValue = userResponseEntity.getStatusCodeValue();
HttpHeaders headers = userResponseEntity.getHeaders();
System.out.println("user = " + user + "; statusCode = " + statusCode + "; statusCodeValue = " + statusCodeValue + "; headers = " + headers);
// 方式二
Map<String, Object> urlParams = new HashMap<>();
urlParams.put("id", 1001L);
urlParams.put("userName", "James");
ResponseEntity<User> userResponseEntity2 = restTemplate.getForEntity(url, User.class, urlParams);
// 方式三
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build().expand(1001L, "James").encode();
URI uri = uriComponents.toUri();
ResponseEntity<User> entity = restTemplate.getForEntity(uri, User.class);
getForObject
getForObject
函数实际上是对getForEntity
函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject
。
示例:
//URL中的{id}占位符最终将会用方法的id参数来填充
String url = "http://127.0.0.1:8083/demo/{id}/{userName}?age=18";
Long id = 1001L;
String userName = "Tom";
//方式一:最后一个参数是大小可变的参数列表,每个参数都会按出现顺序插入到指定URL的占位符中
User user = restTemplate.getForObject(url, User.class, id, userName);
//方式二:将id参数放到Map中,并以id作为key,然后将这个Map作为最后一个参数
Map<String, Object> urlParams = new HashMap<>();
urlParams.put("id", id);
urlParams.put("userName", userName);
User user2 = restTemplate.getForObject(url, User.class, urlParams);
//方式二:参考getForEntity案例
POST请求
POST请求对应三个方法,postForObject()、postForEntity()和postForLocation()
,每个方法同样对应有三个具体的重载方法。postForObject()、postForEntity()
类似于getForObject()和postForEntity(),postForLocation()
返回的是一个URI对象。
postForEntity
String url = "http://localhost:8080/demo2/{address}";
// 方式一
User user = new User();
user.setAge(25);
user.setUsername("王五");
// 第4个参数可以是Object... uriVariables 或者 Map<String, ?> uriVariables
ResponseEntity<User> userResponseEntity = restTemplate.postForEntity(url, user, User.class, "SH");
User userBody = userResponseEntity.getBody();
HttpStatus statusCode = userResponseEntity.getStatusCode();
int statusCodeValue = userResponseEntity.getStatusCodeValue();
HttpHeaders headers = userResponseEntity.getHeaders();
System.out.println("user = " + userBody + "; statusCode = " + statusCode + "; statusCodeValue = " + statusCodeValue + "; headers = " + headers);
postForObject
如果你只关注,返回的消息体,可以直接使用postForObject。用法和getForObject一致。
exchange通用请求
String getUrl = "http://127.0.0.1:8083/demo/{id}/{userName}?age=18";
//GET资源
//参数3是请求头部分;参数4是响应数据要转成对象;最后一个参数用于替换URL中的占位符
ResponseEntity<User> userResponseEntity = restTemplate.exchange(getUrl, HttpMethod.GET, null, User.class, 1001L, "Tom");
System.out.println("exchange = " + userResponseEntity + "; response body = " + userResponseEntity.getBody());
String postUrl = "http://localhost:8083/demo2";
//POST资源
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String jsonParams = "{\"username\":\"123\",\"age\":23}";
HttpEntity<User> httpEntity = new HttpEntity(jsonParams, headers);
ResponseEntity<User> responseEntity = restTemplate.exchange(postUrl, HttpMethod.POST, httpEntity, User.class);
System.out.println("exchange = " + responseEntity + "; response body = " + responseEntity.getBody());
POST请求,from-data传参
String url = "http://localhost:8083/demo2";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap map = new LinkedMultiValueMap();
map.add("id",1001L);
HttpEntity httpEntity = new HttpEntity(map, headers);
ResponseEntity<User> responseEntity = restTemplate.postForEntity(url, httpEntity, User.class);
System.out.println(responseEntity.getBody());
RestTemplate携带参数和header头信息
String url="http://www.baidu.com";
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("id",id);
HttpHeaders header = new HttpHeaders();
// 需求需要传参为form-data格式
header.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(map, header);
JSONObject response = restTemplate.postForObject(url, httpEntity, JSONObject.class);
URI 使用
URI uri = new URIBuilder().setScheme("http").setHost(ip).setPort(port).setPath("/platform/service")
.addParameter("user", "")
.addParameter("service", "")
.build();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build().expand(1001L, "James").encode();
URI uri = uriComponents.toUri();
RestTemplate封装
package com.demo.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
@Component
public class RestUtil {
@Autowired
private RestTemplate restTemplate;
//一些自定义的请求头参数
public static final String supplierID = "";
public static final String interfacekey = "";
/**
* DLT专用执行方法
*
* @param param 请求参数:可以添加一些常量请求值
* @param url 访问的url
* @param method 请求的方法
* @return
*/
public String execute(Map<String, Object> param, String url, HttpMethod method) {
HttpHeaders headers = this.getDefaultHeader();
Map<String, Object> requestor = this.getDefaultParam();
param.put("requestor", requestor);
param.put("supplierID", supplierID);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(param, headers);
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
return response.getBody();
}
/**
* 获取默认的头请求信息
*
* @return
*/
public HttpHeaders getDefaultHeader() {
String timestamp = "" + System.currentTimeMillis();
String signature = EncoderByMd5(supplierID + timestamp + interfacekey);
HttpHeaders headers = new HttpHeaders();
headers.add("signature", signature);
headers.add("timestamp", timestamp);
return headers;
}
/**
* 获取默认的参数
*
* @return
*/
public Map<String, Object> getDefaultParam() {
Map<String, Object> defParam = new HashMap<>();
defParam.put("invoker", "xx");
defParam.put("operatorName", "xx");
return defParam;
}
/**
* 通过MD5加密
*
* @param str
* @return
*/
public static String EncoderByMd5(String str) {
if (str == null) {
return null;
}
try {
// 确定计算方法
MessageDigest md5 = MessageDigest.getInstance("MD5");
BASE64Encoder base64en = new BASE64Encoder();
// 加密后的字符串
return base64en.encode(md5.digest(str.getBytes("utf-8"))).toUpperCase();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
return null;
}
}
/**
* get请求
*
* @param url 请求的url
* @param jsonData 请求的json
* @return
*/
public String restGet(String url, String jsonData) {
return request(url, jsonData, HttpMethod.GET);
}
/**
* @param url 请求的url
* @param jsonData json数据
* @param httpMethod
* @return
*/
private String request(String url, String jsonData, HttpMethod httpMethod) {
ResponseEntity<String> response = null;
try {
if (StringUtils.isEmpty(url)) {
throw new IllegalArgumentException();
}
HttpEntity<String> requestEntity = new HttpEntity<String>(jsonData);
response = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
return response.getBody();
}
/**
* Get请求获取实体类
*
* @param url 请求的url
* @param responseType 返回的类型
* @param parms 不限定个数的参数
* @param <T> 泛型
* @return
*/
public <T> T getForEntity(String url, Class<T> responseType, Object... parms) {
return (T) restTemplate.getForEntity(url, responseType, parms);
}
/**
* Get请求
*
* @param url
* @param parm
* @return
*/
public String get(String url, Map<String, Object> parm) {
return restTemplate.getForEntity(url, String.class, parm).getBody();
}
}
手动指定转换器(HttpMessageConverter)
调用reseful接口传递的数据内容是json格式的字符串,返回的响应也是json格式的字符串。然而restTemplate.postForObject
方法的请求参数RequestBean
和返回参数ResponseBean
却都是java类。是RestTemplate
通过HttpMessageConverter
自动帮我们做了转换的操作。
默认情况下RestTemplate
自动帮我们注册了一组HttpMessageConverter
用来处理一些不同的contentType
的请求。
StringHttpMessageConverter
来处理text/plain
MappingJackson2HttpMessageConverter
来处理application/json
MappingJackson2XmlHttpMessageConverter
来处理application/xml
可以在org.springframework.http.converter
包下找到所有spring帮我们实现好的转换器。
如果现有的转换器不能满足我们的需求,我们还可以实现org.springframework.http.converter.HttpMessageConverter
接口自己写一个。
选好一个HttpMessageConverter
后并注册到我们的RestTemplate中:
RestTemplate restTemplate = new RestTemplate();
//获取RestTemplate默认配置好的所有转换器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
//默认的MappingJackson2HttpMessageConverter在第7个 先把它移除掉
messageConverters.remove(6);
//添加上GSON的转换器
messageConverters.add(6, new GsonHttpMessageConverter());
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
上面的例子使用GsonHttpMessageConverter
替换掉默认用来处理application/json
的MappingJackson2HttpMessageConverter
。
设置底层连接方式
创建一个RestTemplate的实例,可以像上述例子中简单地调用默认的无参数构造函数。这将使用java.net包中的标准Java类作为底层实现来创建HTTP请求。
但很多时候我们需要像传统的HttpClient那样设置HTTP请求的一些属性。RestTemplate使用了一种很偷懒的方式实现了这个需求,那就是直接使用一个HttpClient作为底层实现。
//生成一个设置了连接超时时间、请求超时时间、异常最大重试次数的httpClient
RequestConfig config = RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000)
.setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create()
.setDefaultRequestConfig(config)
.setRetryHandler(new DefaultHttpRequestRetryHandler(5, false));
HttpClient httpClient = builder.build();
//使用httpClient创建一个ClientHttpRequestFactory的实现
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
//ClientHttpRequestFactory作为参数构造一个使用作为底层的RestTemplate
RestTemplate restTemplate = new RestTemplate(requestFactory);
设置拦截器(ClientHttpRequestInterceptor)
有时候我们需要对请求做一些通用的拦截设置,这就可以使用拦截器进行处理。拦截器需要我们实现org.springframework.http.client.ClientHttpRequestInterceptor
接口自己写。
举个简单的例子,写一个在header中根据请求内容和地址添加令牌的拦截器。
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
public class TokenInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
//请求地址
String checkTokenUrl = request.getURI().getPath();
//请求内容
String requestBody = new String(body);
//生成令牌
String token = "token";
//将令牌放入请求header中
request.getHeaders().add("X-Auth-Token",token);
request.getHeaders().add("X-Auth-Token","yq");
return execution.execute(request, body);
}
}
RestTemplate原理
参考
Spring RestTemplate中几种常见的请求方式
RestTemplate原理与使用
springboot集成restTemplate,http连接池
如何使用RestTemplate访问restful服务
spring的RestTemplate使用指南