目录
1、设置拦截器(ClientHttpRequestInterceptor)
一、RestTemplate方式发送HTTP请求代码示例
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
使用apache的HttpClient开发,代码复杂,还得操心资源回收等。代码很复杂,冗余代码多,稍微截个图,这是一个封装好的get请求工具:
public static String get(String url, String paramsStr) {
//创建一个默认的HttpClients对象
CloseableHttpClient createDefault = HttpClients.createDefault();
//创建一个get请求并发送参数
HttpGet httpGet = new HttpGet(url + paramsStr);
//设置http头部信息
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1;"
+ " Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0");
httpGet.setHeader("Accept", "application/json");
httpGet.setHeader("Accept-Encoding", "gzip, deflate");
httpGet.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
//RequestConfig.Builder 配置器 ,我们可以通过custom获取一个新的Builder对象
RequestConfig config = RequestConfig.custom()
//设置链接超时的时间1秒钟
.setConnectTimeout(90000)
//设置读取超时1秒钟
.setSocketTimeout(90000)
//RequestConfig静态方法 setProxy 设置代理
.build();
//设置头部信息
httpGet.setConfig(config);
//实例话对象赋值
CloseableHttpResponse execute = null;
String jsonStr = "";
try {
//执行HttpClient
execute = createDefault.execute(httpGet);
//转化json格式 并防止乱码
jsonStr = EntityUtils.toString(execute.getEntity(), "UTF-8");
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭HttpClient
createDefault.close();
//关闭执行
execute.close();
//销毁GET请求
httpGet.abort();
} catch (IOException e) {
e.printStackTrace();
}
}
return jsonStr;
}
下边贴下通过RestTemplate发送Http请求的代码:
(1)发送Get请求
代码示例:
/**
* RestTemplate 发送 HTTP GET请求 --- 测试
* @throws UnsupportedEncodingException
*/
@Test
public void doHttpGetTest() throws UnsupportedEncodingException {
// -------------------------------> 获取Rest客户端实例
RestTemplate restTemplate = new RestTemplate();
// -------------------------------> 解决(响应数据可能)中文乱码 的问题
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
converterList.remove(1); // 移除原来的转换器
// 设置字符编码为utf-8
HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converterList.add(1, converter); // 添加新的转换器(注:convert顺序错误会导致失败)
restTemplate.setMessageConverters(converterList);
// -------------------------------> (选择性设置)请求头信息
// HttpHeaders实现了MultiValueMap接口
HttpHeaders httpHeaders = new HttpHeaders();
// 给请求header中添加一些数据
httpHeaders.add("JustryDeng", "这是一个大帅哥!");
// -------------------------------> 注:GET请求 创建HttpEntity时,请求体传入null即可
// 请求体的类型任选即可;只要保证 请求体 的类型与HttpEntity类的泛型保持一致即可
String httpBody = null;
HttpEntity<String> httpEntity = new HttpEntity<String>(httpBody, httpHeaders);
// -------------------------------> URI
StringBuffer paramsURL = new StringBuffer("http://127.0.0.1:8080/http/doHttpTest");
// 字符数据最好encoding一下;这样一来,某些特殊字符才能传过去(如:flag的参数值就是“&”,不encoding的话,传不过去)
paramsURL.append("?flag=" + URLEncoder.encode("&&", "utf-8"));
URI uri = URI.create(paramsURL.toString());
// -------------------------------> 执行请求并返回结果
// 此处的泛型 对应 响应体数据 类型;即:这里指定响应体的数据装配为String
ResponseEntity<String> response =
restTemplate.exchange(uri, HttpMethod.GET, httpEntity, String.class);
// -------------------------------> 响应信息
//响应码,如:401、302、404、500、200等
System.err.println(response.getStatusCodeValue());
// 响应头
System.err.println(JSON.toJSON(response.getHeaders()));
// 响应体
if(response.hasBody()) {
System.err.println(response.getBody());
}
}
(2)发送Post请求
代码示例:
/**
* RestTemplate 发送 HTTP POST请求 --- 测试
* @throws UnsupportedEncodingException
*/
@Test
public void doHttpPostTest() throws UnsupportedEncodingException {
// -------------------------------> 获取Rest客户端实例
RestTemplate restTemplate = new RestTemplate();
// -------------------------------> (选择性设置)请求头信息
// HttpHeaders实现了MultiValueMap接口
HttpHeaders httpHeaders = new HttpHeaders();
// 设置contentType
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
// 给请求header中添加一些数据
httpHeaders.add("JustryDeng", "这是一个大帅哥!");
// ------------------------------->将请求头、请求体数据,放入HttpEntity中
// 请求体的类型任选即可;只要保证 请求体 的类型与HttpEntity类的泛型保持一致即可
// 这里手写了一个json串作为请求体 数据 (实际开发时,可使用fastjson、gson等工具将数据转化为json串)
// String httpBody = "{\"motto\":\"唉呀妈呀!脑瓜疼!\"}";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("name","是一个中文名");
paramMap.put("englishName","merry");
HttpEntity<Map<String,Object>> httpEntity = new HttpEntity<>(paramMap, httpHeaders);
// -------------------------------> URI
StringBuffer paramsURL = new StringBuffer("http://127.0.0.1:8080/http/doHttpTest");
// 字符数据最好encoding一下;这样一来,某些特殊字符才能传过去(如:flag的参数值就是“&”,不encoding的话,传不过去)
paramsURL.append("?flag=" + URLEncoder.encode("&&", "utf-8"));
URI uri = URI.create(paramsURL.toString());
// -------------------------------> 执行请求并返回结果
// 此处的泛型 对应 响应体数据 类型;即:这里指定响应体的数据装配为String
// 访问方式一:
// ResponseEntity<String> response =
// restTemplate.exchange(uri, HttpMethod.POST, httpEntity, String.class);
// 访问方式二:
ResponseEntity<String> response = restTemplate.postForEntity(uri,httpEntity,String.class);
// -------------------------------> 响应信息
//响应码,如:401、302、404、500、200等
System.err.println(response.getStatusCodeValue());
// 响应头
System.err.println(JSON.toJSON(response.getHeaders()));
// 响应体
if(response.hasBody()) {
System.err.println(response.getBody());
}
}
对应的响应端代码:
@RequestMapping("/doHttpTest")
public String doHttpGetTest(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) throws IOException {
System.out.println(request.getCharacterEncoding());
// 不是请求体中的中文数据都需要转码
String JustryDeng = new String(request.getHeader("JustryDeng").getBytes("ISO-8859-1"), "utf-8");
System.out.println("获取Header传参:" + JustryDeng);
// 获取URL上的参数
String flag = request.getParameter("flag");
System.out.println("获取URL传参:" + flag);
// 接收对应的Body参数
Map<String, Object> map = paramMap;
return "访问成功!!!";
}
注:HttpServletRequest 请求中的 body 内容仅能调用 request.getInputStream(), request.getReader()和request.getParameter("key") 方法读取一次,重复读取会报 java.io.IOException: Stream closed 异常。
request.getInputStream()只能读取一次,因此如果不对HttpServletRequest进行封装,在代码中直接通过流读取body中的内容就会抛出java.io.IOException: Stream closed 异常。
HttpEntity是请求体,可放置跟请求接口对应类型的数据。在示例中添加了许多格外的操作和代码注释,所以看起来比较臃肿,使用RestTemplate简洁的代码其实是这样的:
/**
* RestTemplate 发送 HTTP POST请求 --- 测试
* @throws UnsupportedEncodingException
*/
@Test
public void doHttpPostTest() throws UnsupportedEncodingException {
// 1-------------------------------> 获取Rest客户端实例
RestTemplate restTemplate = new RestTemplate();
// 2-------------------------------> (选择性设置)请求头信息
// HttpHeaders实现了MultiValueMap接口
HttpHeaders httpHeaders = new HttpHeaders();
// 3-------------------------------> 将请求头、请求体数据,放入HttpEntity中
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("name","是一个中文名");
HttpEntity<Map<String,Object>> httpEntity = new HttpEntity<>(paramMap, httpHeaders);
// 4-------------------------------> 执行请求并返回结果
ResponseEntity<String> response = restTemplate.postForEntity(
"http://127.0.0.1:8080/http/doHttpTest",httpEntity,String.class);
// 5-------------------------------> 响应信息
if(response.hasBody()) {
System.err.println(response.getBody());
}
}
当然你还可以去除注释和进行简单封装,发现它其实就几行代码,对于Get请求来说,就更加简单了,这个没什么好写的。
二、RestTemplate详解
1、RestTemplate简述
RestTemplate
是Spring的通过客户端访问RESTful服务端的核心类,和JdbcTemplate、JmsTemplate
概念相似,都是Spring提供的模板类。
RestTemplate
的行为可以通过callback回调方法和配置HttpMessageConverter
来定制,用来把对象封装到HTTP请求体,将响应信息放到一个对象中。
RestTemplate能大幅简化提交表单数据的难度,并且附带了自动转换JSON数据的功能,但只有理解了HttpEntity的组成结构(header与body),且理解了与uriVariables之间的差异,才能真正掌握其用法。这一点在Post请求更加突出。
2、RestTemplate解析
HttpMessageConverter
RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。其实这点在使用的时候是察觉不到的,很多方法有一个responseType 参数,它让你传入一个响应体所映射成的对象,然后底层用HttpMessageConverter将其做映射
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
HttpMessageConverter.java源码:
public interface HttpMessageConverter<T> {
//指示此转换器是否可以读取给定的类。
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
//指示此转换器是否可以写给定的类。
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
//返回List<MediaType>
List<MediaType> getSupportedMediaTypes();
//读取一个inputMessage
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//往output message写一个Object
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
创建RestTemplate
创建RestTemplate很简单,只需要new RestTemplate(),如果使用Spring架构,将创建的RestTemplate实例通过XML或注解的方式注册到Spring容器中即可
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
RestTemplate构造方法
RestTemplate有3个构造方法,一个无参构造,两个有参构造
RestTemplate无参构造
/**
* Create a new instance of the {@link RestTemplate} using default settings.
* Default {@link HttpMessageConverter}s are initialized.
* 使用默认配置创建一个RestTemplate实例
* 默认的HttpMessageConverter集合被初始化
*/
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
/**
* 如果类路径下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
* 使用jackson做http请求、响应的json转换
*/
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
}
参数为ClientHttpRequestFactory的构造
/**
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
* @param requestFactory HTTP request factory to use
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
* 使用指定的ClientHttpRequestFactory创建一个RestTemplate实例
* requestFactory是用于创建HTTP请求的工厂,默认的实现有
* SimpleClientHttpRequestFactory、HttpComponentsClientHttpRequestFactory
* 如果没有设置默认是SimpleClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this(); //也会调用无参构造初始化默认的messageConverters
setRequestFactory(requestFactory);
}
参数为messageConverters的构造
/**
* Create a new instance of the {@link RestTemplate} using the given list of
* {@link HttpMessageConverter} to use
* @param messageConverters the list of {@link HttpMessageConverter} to use
* @since 3.2.7
* 传入自定义的HttpMessageConverter集合,并赋值给messageConverters,之后使用自定义的HttpMessageConverter
*/
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
this.messageConverters.addAll(messageConverters);
}
3、RestTemplate API使用
RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型。
-
getForObject() -- 发送GET请求,将HTTP response转换成一个指定的object对象
-
postForEntity() -- 发送POST请求,将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象
每个HTTP方法对应的RestTemplate方法都有3种。其中2种的url参数为字符串,其他参数变量分别是Object数组和Map。第3种使用URI类型作为参数。
注意:使用字符串类型的url默认会对url进行转义,如http://example.com/hotel list
在执行时会转义为http://example.com/hotel%20list
,这样其实是没有问题的,但如果字符串类型的url本身已经转义过了,执行时就会再转义一次,变成http://example.com/hotel%2520list
。如果不需要这种隐式的转义,可以使用java.net.URI
参数的方法,这种方法不会在执行时存在隐式的url转义,可以在创建URI对象时自行决定是否转义,推荐使用UriComponentsBuilder
创建URI
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}")
.build() //build(true)就不会对url转义,但如果包含http://example.com/hotel list这种需要转义的url,会报错
.expand("42", "21")
.encode();
URI uri = uriComponents.toUri();
GET方法
getForEntity()
发送GET请求,返回ResponseEntity
/**
* 参数1: String类型 或 URI类型的请求地址
* 参数2: 指定返回的实体类型,class对象
* 参数3: uri参数,可以是变长数组或map
* 返回值:ResponseEntity<T>是Spring对HTTP响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、response header信息,response body信息等
*/
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
举例
ResponseEntity<Book> responseEntity =
restTemplate.getForEntity("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");
Book book = responseEntity.getBody(); //响应体转换为Book类型
int statusCodeValue = responseEntity.getStatusCodeValue(); //响应状态码
HttpHeaders headers = responseEntity.getHeaders(); //响应头信息
getForObject()
发送GET请求,返回指定的Object类型
/**
* 参数1: String类型 或 URI类型的请求地址
* 参数2: 指定返回的实体类型,class对象
* 参数3: uri参数,可以是变长数组或map
* 返回值:responseType指定的Object类型
*/
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
举例:
Book book = restTemplate.getForObject("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");
注:使用URI方式,可以在URI中直接封装对应的参数内容
POST方法
postForEntity()
发送POST请求,返回ResponseEntity
/**
* 参数1: String类型 或 URI类型的请求地址
* 参数2: 请求body,可以是HttpEntity类型(可设置request header),或其它Object类型
* 参数3: 指定返回的实体类型,class对象
* 参数4: uri参数,可以是变长数组或map
* 返回值:ResponseEntity<T>是Spring对HTTP响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、response header信息,response body信息等
*/
@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
举例
//参数是Book类型,返回值是ResponseEntity<Book>类型
ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://127.0.0.1:8080/updateBook", book, Book.class);
Book book = responseEntity.getBody(); //响应体转换为Book类型
int statusCodeValue = responseEntity.getStatusCodeValue(); //响应状态码
HttpHeaders headers = responseEntity.getHeaders(); //响应头信息
postForObject()
发送POST请求,返回指定的Object类型
/**
* 参数1: String类型 或 URI类型的请求地址
* 参数2: 请求body,可以是HttpEntity类型(可设置request header),或其它Object类型
* 参数3: 指定返回的实体类型,class对象
* 参数4: uri参数,可以是变长数组或map
* 返回值:responseType指定的Object类型
*/
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
举例
//参数是Book类型,返回值也是Book类型
Book book = restTemplate.postForObject("http://127.0.0.1:8080/updatebook", book, Book.class);
exchange方法
- 可以支持多种HTTP方法,在参数中指定
- 可以在请求中增加header和body信息,返回类型是ResponseEntity,可以从中获取响应的状态码,header和body等信息
三、RestTemplate扩展
1、设置拦截器(ClientHttpRequestInterceptor)
有时候我们需要对请求做一些通用的拦截设置,这就可以使用拦截器进行处理。拦截器需要我们实现org.springframework.http.client.ClientHttpRequestInterceptor
接口自己写。
举个简单的例子,写一个在header中根据请求内容和地址添加令牌的拦截器。
public class TokenInterceptor implements ClientHttpRequestInterceptor
{
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
{
//请求地址
String checkTokenUrl = request.getURI().getPath();
//token有效时间
int ttTime = (int) (System.currentTimeMillis() / 1000 + 1800);
//请求方法名 POST、GET等
String methodName = request.getMethod().name();
//请求内容
String requestBody = new String(body);
//生成令牌 此处调用一个自己写的方法,有兴趣的朋友可以自行google如何使用ak/sk生成token,此方法跟本教程无关,就不贴出来了
String token = TokenHelper.generateToken(checkTokenUrl, ttTime, methodName, requestBody);
//将令牌放入请求header中
request.getHeaders().add("X-Auth-Token",token);
return execution.execute(request, body);
}
}
创建RestTemplate
实例的时候可以这样向其中添加拦截器
RestTemplate restTemplate = new RestTemplate();
//向restTemplate中添加自定义的拦截器
restTemplate.getInterceptors().add(new TokenInterceptor());
2、RestTemplate配置
1、处理请求头和响应头
设置请求头信息
(1)如果是发送post、put请求,要设置请求头,可以在调用方法时的第二个参数传入HttpEntity对象,HttpEntity可以用于设置请求头信息,如
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);
Book book = restTemplate.postForObject("http://127.0.0.1:8080/getbook", requestEntity, Book.class);
以postForObject()方法举例,其第二个参数接收Object类型的数据,如传入的是HttpEntity,则使用它作为整个请求实体,如果传入的是其它Object类型,则将Object参数作为request body,新建一个HttpEntity作为请求实体
private HttpEntityRequestCallback(Object requestBody, Type responseType) {
super(responseType);
//如果是HttpEntity类型的,直接作为请求实体赋值给this.requestEntity
if (requestBody instanceof HttpEntity) {
this.requestEntity = (HttpEntity<?>) requestBody;
}
//如果requestBody不是HttpEntity类型,且不为空,以Object参数作为request body,并新建HttpEntity
else if (requestBody != null) {
this.requestEntity = new HttpEntity<Object>(requestBody);
}
else {
this.requestEntity = HttpEntity.EMPTY;
}
}
(2)如果是其它HTTP方法调用要设置请求头,可以使用exchange()方法,可以参考 官方示例
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);
HttpEntity<String> response = template.exchange(
"http://example.com/hotels/{hotel}",
HttpMethod.GET, requestEntity, String.class, "42");
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
处理响应头信息
使用RestTemplate中xxxForEntity()的方法,会返回ResponseEntity,可以从中获取到响应状态码,响应头和body等信息
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);
HttpEntity<String> response = template.exchange(
"http://example.com/hotels/{hotel}",
HttpMethod.GET, requestEntity, String.class, "42");
//response相关信息
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
2、ClientHttpRequestFactory
ClientHttpRequestFactory是Spring定义的一个接口,其用于生产org.springframework.http.client.ClientHttpRequest对象,RestTemplate只是模板类,抽象了很多调用方法,而底层真正使用何种框架发送HTTP请求是通过ClientHttpRequestFactory指定的
接口定义
/**
* Factory for {@link ClientHttpRequest} objects.
* Requests are created by the {@link #createRequest(URI, HttpMethod)} method.
* ClientHttpRequest对象的工厂
*
* @author Arjen Poutsma
* @since 3.0
*/
public interface ClientHttpRequestFactory {
/**
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
* <p>The returned request can be written to, and then executed by calling
* {@link ClientHttpRequest#execute()}.
* 使用指定的URI和HTTP方法新建一个ClientHttpRequest对象
* 可以修改返回的request,并通过ClientHttpRequest的execute()方法执行调用
* 即调用的逻辑也被Spring封装到ClientHttpRequest中
*
* @param uri the URI to create a request for
* @param httpMethod the HTTP method to execute
* @return the created request
* @throws IOException in case of I/O errors
*/
ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
}
RestTemplate可以在构造时设置ClientHttpRequestFactory,也可以通过setRequestFactory()方法设置
构造方法设置:
/**
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
* @param requestFactory HTTP request factory to use
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
可以看到上面注释中已经给出了Spring的两种ClientHttpRequestFactory的实现类SimpleClientHttpRequestFactory
和HttpComponentsClientHttpRequestFactory
SimpleClientHttpRequestFactory——这个有点危险
如果什么都不设置,RestTemplate默认使用的是SimpleClientHttpRequestFactory
,其内部使用的是jdk的java.net.HttpURLConnection
创建底层连接,默认是没有连接池的,connectTimeout
和readTimeout
都是 -1,即没有超时时间
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
。。。。。。
private int connectTimeout = -1;
private int readTimeout = -1;
//创建Request
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
//bufferRequestBody默认为true
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
/**
* Opens and returns a connection to the given URL.
* 打开并返回一个指定URL的连接
* <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} -
* if any - to open a connection.
* @param url the URL to open a connection to
* @param proxy the proxy to use, may be {@code null}
* @return the opened connection 返回类型为 java.net.HttpURLConnection
* @throws IOException in case of I/O errors
*/
protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
if (!HttpURLConnection.class.isInstance(urlConnection)) {
throw new IllegalStateException("HttpURLConnection required for [" + url + "] but got: " + urlConnection);
}
return (HttpURLConnection) urlConnection;
}
/**
* Template method for preparing the given {@link HttpURLConnection}.
* <p>The default implementation prepares the connection for input and output, and sets the HTTP method.
* @param connection the connection to prepare
* @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.)
* @throws IOException in case of I/O errors
*/
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
//如果connectTimeout大于等于0,设置连接超时时间
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
//如果readTimeout大于等于0,设置读超时时间
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
connection.setDoInput(true);
if ("GET".equals(httpMethod)) {
connection.setInstanceFollowRedirects(true);
}
else {
connection.setInstanceFollowRedirects(false);
}
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
connection.setRequestMethod(httpMethod);
}
。。。。。。
}
HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory底层使用Apache HttpClient创建请求,访问远程的Http服务,可以使用一个已经配置好的HttpClient实例创建HttpComponentsClientHttpRequestFactory请求工厂,HttpClient实例中可以配置连接池和证书等信息
添加HttpClient依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>x.x.x</version> <!-- springboot项目不用指定 -->
</dependency>
设置超时时间
设置超时时间,可以直接使用Spring的底层基于HttpClient的HttpComponentsClientHttpRequestFactory,此处设置的是ClientHttpRequestFactory级别的全局超时时间
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
@Bean
private ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(30 * 1000); //连接超时时间
factory.setReadTimeout(60 * 1000); //读超时时间
return factory;
}
}
注意:如果通过一个HttpClient实例创建HttpComponentsClientHttpRequestFactory,并通过HttpClient指定了DefaultRequestConfig,设置了connectTimeout、readTimeout等,在实际执行请求创建request时会与HttpComponentsClientHttpRequestFactory的配置合并,connectTimeout、socketTimeout、connectionRequestTimeout 以HttpComponentsClientHttpRequestFactory的配置为准
HttpComponentsClientHttpRequestFactory:
/**
* Merge the given {@link HttpClient}-level {@link RequestConfig} with
* the factory-level {@link RequestConfig}, if necessary.
* @param clientConfig the config held by the current httpClient级别的requestConfig配置
* @return the merged request config
* (may be {@code null} if the given client config is {@code null})
* @since 4.2
*/
protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
if (this.requestConfig == null) { // nothing to merge
return clientConfig;
}
RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
int connectTimeout = this.requestConfig.getConnectTimeout(); //HttpComponentsClientHttpRequestFactory级别的配置
if (connectTimeout >= 0) {
builder.setConnectTimeout(connectTimeout);
}
int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
if (connectionRequestTimeout >= 0) {
builder.setConnectionRequestTimeout(connectionRequestTimeout);
}
int socketTimeout = this.requestConfig.getSocketTimeout();
if (socketTimeout >= 0) {
builder.setSocketTimeout(socketTimeout);
}
return builder.build();
}
上例中虽然没有指定http连接池,但HttpComponentsClientHttpRequestFactory无参构造会创建一个HttpClient,并默认使用了连接池配置,MaxTotal=10,DefaultMaxPerRoute=5 ,具体如下:
HttpComponentsClientHttpRequestFactory:
/**
* Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
* with a default {@link HttpClient}.
*/
public HttpComponentsClientHttpRequestFactory() {
this(HttpClients.createSystem());
}
HttpClients:
/**
* Creates {@link CloseableHttpClient} instance with default
* configuration based on system properties.
* 创建CloseableHttpClient实例使用基于system properties的默认配置
*/
public static CloseableHttpClient createSystem() {
return HttpClientBuilder.create().useSystemProperties().build();
}
HttpClientBuilder:
/**
* Use system properties when creating and configuring default
* implementations.
*/
public final HttpClientBuilder useSystemProperties() {
this.systemProperties = true; //设置systemProperties为true
return this;
}
public CloseableHttpClient build() {
HttpClientConnectionManager connManagerCopy = this.connManager; //没有设置,为null
if (connManagerCopy == null) {
。。。。。。
//创建连接池管理器PoolingHttpClientConnectionManager
@SuppressWarnings("resource")
final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactoryCopy)
.build(),
null,
null,
dnsResolver,
connTimeToLive,
connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
if (defaultSocketConfig != null) {
poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
}
if (defaultConnectionConfig != null) {
poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
}
//由于是HttpClientBuilder.create().useSystemProperties().build(),systemProperties为true
if (systemProperties) {
String s = System.getProperty("http.keepAlive", "true"); //http.keepAlive默认值为true
if ("true".equalsIgnoreCase(s)) {
s = System.getProperty("http.maxConnections", "5"); //默认值为5
final int max = Integer.parseInt(s);
poolingmgr.setDefaultMaxPerRoute(max); //DefaultMaxPerRoute=5
poolingmgr.setMaxTotal(2 * max); //MaxTotal=10
}
}
if (maxConnTotal > 0) {
poolingmgr.setMaxTotal(maxConnTotal);
}
if (maxConnPerRoute > 0) {
poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
}
connManagerCopy = poolingmgr;
}
}
配置连接池
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
//开始设置连接池
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager
= new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(100); //最大连接数
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20); //同路由并发数
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
HttpClient httpClient = httpClientBuilder.build();
// httpClient连接配置
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
= new HttpComponentsClientHttpRequestFactory(httpClient);
clientHttpRequestFactory.setConnectTimeout(30 * 1000); //连接超时
clientHttpRequestFactory.setReadTimeout(60 * 1000); //数据读取超时时间
clientHttpRequestFactory.setConnectionRequestTimeout(30 * 1000); //连接不够用的等待时间
return clientHttpRequestFactory;
}
catch (Exception e) {
logger.error("初始化clientHttpRequestFactory出错", e);
}
return null;
}
}
3、自定义messageConverter
RestTemplate的无参构造中默认会初始化很多messageConverters,用于请求/响应中的消息转换
/**
* Create a new instance of the {@link RestTemplate} using default settings.
* Default {@link HttpMessageConverter}s are initialized.
* 使用默认配置创建一个RestTemplate实例
* 默认的HttpMessageConverter集合被初始化
*/
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
/**
* 如果类路径下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
* 使用jackson做http请求、响应的json转换
*/
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) { //类路径下包含 com.google.gson.Gson
this.messageConverters.add(new GsonHttpMessageConverter());
}
}
使用fastjson做json转换
springboot项目默认使用jackson做json转换
- 引入fastjson依赖
- 排除jackson的HttpMessageConverter转换器
- 添加fastjson的转换器
排除jackson的HttpMessageConverter转换器有两种方式:
(1)类路径下去掉jackson的支持
从RestTemplate的无参构造可以看出,需要判断类路径下是否有jackson的相关类,有才会添加MappingJackson2HttpMessageConverter,故可以在pom.xml中排除jackson的支持,以springboot项目举例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>jackson-databind</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
(2)在初始化配置RestTemplate时,去掉其默认的MappingJackson2HttpMessageConverter
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory());
//restTemplate默认的HttpMessageConverter
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
List<HttpMessageConverter<?>> messageConvertersNew = new ArrayList<HttpMessageConverter<?>>();
for(HttpMessageConverter httpMessageConverter : messageConverters){
//跳过MappingJackson2HttpMessageConverter
if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) continue;
messageConvertersNew.add(httpMessageConverter);
}
//添加fastjson转换器
messageConvertersNew.add(fastJsonHttpMessageConverter());
return restTemplate;
}
@Bean
public HttpMessageConverter fastJsonHttpMessageConverter() {
//MediaType
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//FastJsonConfig
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,
SerializerFeature.QuoteFieldNames);
//创建FastJsonHttpMessageConverter4 Spring 4.2后使用
FastJsonHttpMessageConverter4 fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter4();
fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
return fastJsonHttpMessageConverter;
}
四、SpringBoot中使用RestTemplate
SpringBoot项目可以通过上面的方式,@Bean往Spring容器中注册一个配置好的RestTemplate实例,也可以参考 SpringBoot官方 的方式自定义RestTemplate
由于RestTemplate实例在使用前经常需要自定义,SpringBoot没有提供自动配置好的RestTemplate,但是自动配置好了可以用于创建RestTemplate的RestTemplateBuilder
实例,可以按如下使用
@Service
public class MyBean {
private final RestTemplate restTemplate;
public MyBean(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
public Details someRestCall(String name) {
return this.restTemplate.getForObject("/{name}/details", Details.class, name);
}
}