Spring RestTemplate使用与源码浅析
一.RestTemplate 概述
RestTemplate 是 Spring Web 模块封装的一个基于Rest规范提供HTTP请求服务的工具,用于访问第三方的Rest接口。在传统情况下服务端访问
HTTP
服务时,一般会使用JDK
的HttpURLConnection
或者Apache
的HttpClient
,不过这些方法工具使用比较繁琐、API
过于复杂、还要手动进行资源回收。在此背景下,RestTemplate 简化了与HTTP服务的通信,并满足RestFul原则,我们只需要专注于请求的发送与结果的获取即可。RestTemplate
是一个执行HTTP
请求的同步阻塞式工具类,它仅仅只是在HTTP
客户端库(例如JDK HttpURLConnection
,Apache HttpComponents
,okHttp
等HTTP服务源)基础上,封装了请求构造、资源回收、错误处理等底层操作,提供了更加简单易用的模板方法 API,极大程度上提升了我们的开发效率。 从Spring 5.0开始,RestTemplate 处于维护模式。取而代之的是WebClient,它提供了更现代的 API,并支持同步、异步和流式处理方案,支持更复杂、更丰富、更灵活的应用场景。我们这里暂且不讲。
参考文档:
- Overview (Spring Framework 5.3.22 API)
- Web on Servlet Stack (spring.io)
- Spring - RestTemplate执行原理分析 - 歪头儿在帝都 - 博客园 (cnblogs.com)
- 微服务中如何使用RestTemplate优雅调用API(拦截器、异常处理、消息转换) - ccww_的个人空间 - OSCHINA - 中文开源技术交流社区
二.RestTemplate 使用
1.RestTemplate 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.RestTemplate 配置
2.1 配置HTTP源
(1)HTTP客户端介绍
RestTemplate 继承自 HttpAccessor,该类可以理解为一个用于接触/访问HTTP底层客户端的抽象基类,其源码如下。可以看出,该类中的 ClientHttpRequestFactory 工厂(或者说客户端请求库)属性专门通过底层相应的HTTP连接客户端来构造请求Request,向上提供HTTP请求访问服务,而其默认赋值为 SimpleClientHttpRequestFactory。要想切换不同的HTTP源,我们就需要通过 setRequestFactory 方法给 ClientHttpRequestFactory 进行其他客户端的赋值。
public abstract class HttpAccessor {
protected final Log logger = HttpLogging.forLogName(this.getClass());
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList();
public HttpAccessor() {
}
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
this.requestFactory = requestFactory;
}
... ...
}
底层HTTP客户端用于建立HTTP连接,Factory用于从相应的HTTP连接中构造Request发起请求,而RestTemplate用于将上述流程进行封装,对外提供更简单、便捷的模板API。RestTemplate 的支持HTTP客户端连接库均实现自 ClientHttpRequestFactory 接口,常见的客户端库及其对应的HTTP客户端介绍如下:
- SimpleClientHttpRequestFactory:RestTemplate默认的客户端库,其对应的HTTP连接客户端类型是java JDK自带的HttpURLConnection 作为底层
HTTP
客户端实现。- HttpComponentsAsyncClientHttpRequestFactory:其对应的HTTP连接客户端类型是Apache的HttpClient作为底层
HTTP
客户端实现。- OkHttp3ClientHttpRequestFactory:其对应的HTTP连接客户端类型是OkHttpClient作为底层
HTTP
客户端实现。 从开发人员的反馈以及网上的各种
HTTP
客户端性能以及易用程度评测来看,OkHttpClient
优于Apache的HttpClient
、Apache的HttpClient
优于HttpURLConnection
。且需要注意的是 java JDK自带的HttpURLConnection并不支持HTTP协议的Patch方法,我们可以通过设置 setRequestFactory方法,来切换RestTemplate的底层HTTP客户端实现类库。
(2)切换HTTP客户端
- 默认 SimpleClientHttpRequestFactory 配置
@Configuration
public class RestTemplateConfig {
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(getClientHttpRequestFactory());
}
private ClientHttpRequestFactory getClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(150000); //设置传输/读取数据的超时时间(以毫秒为单位)
factory.setConnectTimeout(150000); //设置Connection的连接超时时间(以毫秒为单位)
//factory.setBufferRequestBody(true); //设置是否应在factory内部缓冲/缓存请求正文body数据(传输大数据时应设置为true,默认为true)
return factory;
}
}
- HttpComponentsAsyncClientHttpRequestFactory 配置
HttpComponentsAsyncClientHttpRequestFactory 使用 Apache HttpComponents的HttpClient客户端创建Request请求,其拓展了身份验证、HTTP连接池等功能。其配置步骤主要包括以下:
- 引入 HttpClient 依赖包(否则不能进行连接池、RequestConfig等配置,只能使用默认配置)
- Http 链接池配置(连接方式、最大连接数等)、Resquest请求配置(连接时间、读取时间等)、HttpClient 客户端配置
- HttpComponentsAsyncClientHttpRequestFactory 配置
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
private ClientHttpRequestFactory getClientHttpRequestFactory(){
/**
factory.setHttpClient(); //设置HttpClient客户端
factory.setConnectionRequestTimeout(); //等价于设置其RequestConfig的ConnectionRequestTimeout
factory.setConnectTimeout(); //等价于设置其RequestConfig的ConnectTimeout
factory.setReadTimeout(); //等价于设置其RequestConfig的SocketTimeout
**/
return new HttpComponentsClientHttpRequestFactory(getHttpClient());
}
private HttpClient getHttpClient(){
//1.注册HTTP和HTTPS请求服务(ConnectionManager初始化默认值已经注册完毕)
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
//2.声明连接池配置
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(1000);//设置连接池最大连接数
connectionManager.setDefaultMaxPerRoute(500); // 单个连接路由(单个主机)的最大并发连接数
connectionManager.setValidateAfterInactivity(3000); //最大连接空闲时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过则释放socket重新建立
//3.请求配置RequestConfig
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(2000) //从连接池中获取连接的最大超时时间,超时未拿到可用连接则会抛出异常
.setConnectTimeout(1000) //建立连接(握手成功)的最大超时时间
.setSocketTimeout(1000) //等待服务器返回响应(response)的最大超时时间
.build();
//4.设置默认请求头 headers
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
//5.配置HttpClient客户端
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig) //引入RequestConfig请求配置
.setConnectionManager(connectionManager) //引入PoolingHttpClientConnectionManager连接池配置
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //保持长连接配置,需要在头添加 Keep-Alive(默认策略,返回此连接可以安全保持空闲状态的持续时间。如果连接处于空闲状态的时间超过此时间段,则不得重复使用。)
.setDefaultHeaders(headers) //设置默认请求头
.setRetryHandler(new DefaultHttpRequestRetryHandler(3,true)) //设置异常重试次数、是否开启重试(默认为3次,false不开启)
.build();
}
}
- OkHttp3ClientHttpRequestFactory 配置
OkHttp是一个高效的HTTP客户端:
- 允许所有同一个主机地址的请求共享同一个socket连接;
- 连接池可减少请求延时;
- 透明的GZIP压缩减少响应数据的大小;
- 缓存响应可以完全避免一些完全重复的网络请求
当网络出现问题的时候OkHttp依然坚守自己的职责,它会自动恢复一般的连接问题;如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP。使用OkHttp很容易,它的请求/响应API具有流畅的构建器和不变性。它支持同步阻塞调用和带有回调的异步调用。
<!--引入okhttp依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用OkHttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory(){
OkHttpClient okHttpClient = new OkHttpClient.newBuilder()
.connectionPool(pool())
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
/**
* 连接池配置 ConnectionPool
* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that share the same [Address] may share a [Connection].
* @return
*/
private ConnectionPool pool() {
//maxIdleConnections 最大空闲连接数(默认5 min)
//keepAliveDuration 空闲连接存活时间(默认5 min)
return new ConnectionPool(200, 10, TimeUnit.SECONDS);
}
}
2.2 配置拦截器
有时我们需要对请求做一些通用的拦截设置,比如打印请求日志、添加Token校验、默认Header信息等,这时就可以添加拦截器进行RestTemplate请求处理。RestTemplate 可以通过 setInterceptors 方法设置内部的拦截器链 List ,用于对请求进行拦截处理,每个自定义拦截器都应该实现 ClientHttpRequestInterceptor 接口。
/**
* 记录RestTemplate访问信息日志
*/
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
/**
* intercept : 拦截 RestTemplate 请求处理,并返回响应Response
* @param request the request, containing method, URI, and headers
* @param body the body of the request
* @param execution the request execution 请求上下文
* - 若当前拦截器非链上最后一个拦截器:用于调用拦截器链chain中的下一个拦截器,将请求信息向链后传递
* - 若当前拦截器为最后一个拦截器:用于执行request请求本身,并将响应向链前返回
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
trackRequest(request,body);
//调用execution传递request
ClientHttpResponse httpResponse = execution.execute(request, body);
trackResponse(httpResponse);
return httpResponse;
}
//记录响应responset日志
private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
System.out.println("============================response begin==========================================");
System.out.println("Status code : "+httpResponse.getStatusCode());
System.out.println("Status text : "+httpResponse.getStatusText());
System.out.println("Headers : "+httpResponse.getHeaders());
System.out.println("=======================response end=================================================");
}
//记录请求request日志
private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
System.out.println("======= request begin ========");
System.out.println("uri : "+request.getURI());
System.out.println("method : "+request.getMethod());
System.out.println("headers : "+request.getHeaders());
System.out.println("request body : "+new String(body, "UTF-8"));
System.out.println("======= request end ========");
}
}
/**
* 请求头部添加Token信息
*/
public class TokenClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
//生成令牌 此处调用一个自己写的方法,有兴趣的朋友可以自行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);
}
}
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
//配置自定义拦截器
List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new TokenClientHttpRequestInterceptor());
interceptors.add(new TrackLogClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
2.3 配置转换器
Reseful接口传递的数据内容是json格式的字符串,然而
RestTemplate
的封装方法都是直接传递java类作为数据对象,其在底层是通过HttpMessageConverter
自动帮我们做了转换操作。在POST请求发送之前/收到响应之后,RestTemplate会遍历其内部的messageConverters List,根据设置的返回类型responseType和请求的contentType参数进行匹配,选择首个合适的MessageConverter匹配处理该请求Body的数据。
(1)转换器对应数据关系
默认情况下
RestTemplate
在初始化时自动帮我们注册了一组自带的HttpMessageConverter
用来处理一些不同类型的contentType
的请求。不同Convert与MediaType的对应关系如下:
类名 | 支持的 JavaType | 支持的 MediaType |
---|---|---|
ByteArrayHttpMessageConverter | byte[] | supports all media types (*/* ) and writes with a Content-Type of application/octet-stream (字节数组 http消息转换器) |
StringHttpMessageConverter | String | supports all text media types (text/* ) and writes with a Content-Type of text/plain . (string http消息转换器) |
ResourceHttpMessageConverter | Resource | / (读写Resource的 http消息转换器 比如读取media、file之类) |
SourceHttpMessageConverter | Source | application/xml, text/xml, application/*+xml (Source http消息转换器 用于转换Source类型对象) |
AllEncompassingFormHttpMessageConverter | Map<K, List<?>> | application/x-www-form-urlencoded, multipart/form-data (所有通用消息转换器) |
MappingJackson2HttpMessageConverter | Object | application/json, application/*+json (jackson消息转换器 可以将json和Java对象进行相互转换) |
Jaxb2RootElementHttpMessageConverter | Object | application/xml, text/xml, application/*+xml (JAXB 可以将java对象与xml进行相互转换) |
JavaSerializationConverter | Serializable | x-java-serialization;charset=UTF-8 (序列化转换器) |
FastJsonHttpMessageConverter | Object | / (FastJson消息转换器 可以将json和Java对象进行相互转换) |
(2)注册FastJsonHttpMessageConvert转换器
<!-- 引入alibaba fastjson 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
//1.获取restTemplate的MessageConverters List
List<HttpMessageConverter<?>> messageConverters= restTemplate.getMessageConverters();
//2.手动创建并配置FastJsonConverter
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
// 添加fastJson的配置信息;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.WriteMapNullValue, // 是否输出值为null的字段,默认为false,我们将它打开
SerializerFeature.WriteNullListAsEmpty, // 将Collection类型字段的字段空值输出为[]
SerializerFeature.WriteNullStringAsEmpty, // 将字符串类型字段的空值输出为空字符串""
SerializerFeature.WriteNullNumberAsZero // 将数值类型字段的空值输出为0
);
fastJsonConverter.setFastJsonConfig(fastJsonConfig);
// 配置支持的数据类型,解决中文乱码:设置响应的content-type为application/json;charset=UTF-8
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonConverter.setSupportedMediaTypes(fastMediaTypes);
//3.添加FastJsonConverter到restTemplate
messageConverters.add(0,fastJsonConverter);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
3.RestTemplate 访问请求
Method group | Description |
---|---|
getForObject | Retrieves a representation via GET. |
getForEntity | Retrieves a ResponseEntity (that is, status, headers, and body) by using GET. |
headForHeaders | Retrieves all headers for a resource by using HEAD. |
postForLocation | Creates a new resource by using POST and returns the Location header from the response. |
postForObject | Creates a new resource by using POST and returns the representation from the response. |
postForEntity | Creates a new resource by using POST and returns the representation from the response. |
put | Creates or updates a resource by using PUT. |
patchForObject | Updates a resource by using PATCH and returns the representation from the response. Note that the JDK HttpURLConnection does not support PATCH , but Apache HttpComponents and others do. |
delete | Deletes the resources at the specified URI by using DELETE. |
optionsForAllow | Retrieves allowed HTTP methods for a resource by using ALLOW. |
exchange | More generalized (and less opinionated) version of the preceding methods that provides extra flexibility when needed. It accepts a RequestEntity (including HTTP method, URL, headers, and body as input) and returns a ResponseEntity .These methods allow the use of ParameterizedTypeReference instead of Class to specify a response type with generics. |
execute | The most generalized way to perform a request, with full control over request preparation and response extraction through callback interfaces. |
3.1 GET请求
通过
RestTemplate
发送HTTP GET
协议请求,常用的方法类型有两个:
getForObject()
: 返回值对应HTTP
协议的响应体,由HttpMessageConverter自动进行类型转换封装对象后返回getForEntity()
: 返回的是ResponseEntity
对象,ResponseEntity
是对HTTP
响应的封装,除了包含响应体外,还包含HTTP
状态码、contentType、contentLength、Header
等Response信息
- getForEntity():
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- responseType: the type of the return value(ResponseEntity内的ResponseBody也自动由HttpMessageConverter进行了转换并封装进ResponseEntity,转换类型由responseType指定<T>)
- uriVariables: the map containing variables for the URI template
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)
- url: the URL
- responseType: the type of the return value
- getForObject():
<T> T getForObject(String url, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the map containing variables for the URI template
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> T getForObject(URI url, Class<T> responseType)
- url: the URL
- responseType: the type of the return value
@SpringBootTest
class ServerLinkerApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void contextLoads() {
//1.无参GET请求(或直接在URL字符串中拼接参数):直接返回对象
String url1 = "http://localhost:8080/testGet";
ResponseBean responseBean1 = restTemplate.getForObject(url1, ResponseBean.class);
//2.带参GET请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
String url2 = "http://localhost:8080/testGet/{1}/{2}";
//String url2 = "http://localhost:8080/testGet?userId={1}&startTime={2}";
ResponseBean responseBean2 = restTemplate.getForObject(url2,ResponseBean.class,"001","2022-09-02");
//3.带参GET请求:使用Map传参
String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
Map<String, String> params = new HashMap<>();
params.put("user_Id", "001");
params.put("start_Time", "2022-09-02");
ResponseBean responseBean3 = restTemplate.getForObject(url3,ResponseBean.class,params);
//4.getForEntity使用
String url4 = "http://localhost:8080/testGet";
ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url4, ResponseBean.class);
// (1)获取响应体转换对象
ResponseBean responseBean = response.getBody();
// (2)获取response额外信息
HttpStatus statusCode = response.getStatusCode();
int statusCodeValue = response.getStatusCodeValue();
HttpHeaders headers = response.getHeaders();
System.out.println("HTTP 响应状态:" + statusCode);
System.out.println("HTTP 响应状态码:" + statusCodeValue);
System.out.println("HTTP Headers信息:" + headers);
}
}
3.2 POST请求
通过
RestTemplate
发送HTTP PSOT
协议请求,常用的方法类型也有两个(只是多了一个 Object request 参数,用于传递Post数据):
postForObject()
: 返回值对应HTTP
协议的响应体body,由HttpMessageConverter自动进行类型转换封装对象后返回postForEntity()
: 返回的是ResponseEntity
对象,ResponseEntity
是对HTTP
响应的封装,除了包含响应体外,还包含HTTP
状态码、contentType、contentLength、Header
等Response信息
- postForEntity():
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- request: the Object to be POSTed (may be null)(request传输对象会自动通过转换器Converter转换为JSON格式字符串传输)
- responseType: the type of the return value
- uriVariables: the variables to expand the URI template using the given map
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
- url: the URL
- request: the Object to be POSTed (may be null)
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType)
- url: the URL
- request: the Object to be POSTed (may be null)
- responseType: the type of the return value
- postForObject():
<T> T postForObject(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
- url - the URL
- request - the Object to be POSTed (may be null)(The request parameter can be a HttpEntity in order to add additional HTTP headers to the request.)
- responseType - the type of the return value
- uriVariables - the variables to expand the template(URI Template variables are expanded using the given map.)
<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
- url - the URL
- request - the Object to be POSTed (may be null)
- responseType - the type of the return value
- uriVariables - the variables to expand the template
<T> T postForObject(URI url, Object request, Class<T> responseType)
- url - the URL
- request - the Object to be POSTed (may be null)
- responseType - the type of the return value
@SpringBootTest
class WeChatPusherApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void PostTest(){
//1.简单POST请求(或直接在URL字符串中拼接参数):传递DataItem数据(body),直接返回对象
String url1 = "http://localhost:8080/testGet";
DataItem dataItem = new DataItem("Number","Red");
ResponseBean responseBean1 = restTemplate.postForObject(url1, dataItem,ResponseBean.class);
//2.带路径参数POST请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
String url2 = "http://localhost:8080/testGet/{1}/{2}";
ResponseBean responseBean2 = restTemplate.postForObject(url2,dataItem,ResponseBean.class,"001","2022-09-02");
//3.带路径参数POST请求:使用Map传参
String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
Map<String, String> params = new HashMap<>();
params.put("user_Id", "001");
params.put("start_Time", "2022-09-02");
ResponseBean responseBean3 = restTemplate.postForObject(url3,dataItem,ResponseBean.class,params);
//4.postForEntity使用
String url4 = "http://localhost:8080/testGet";
ResponseEntity<ResponseBean> response = restTemplate.postForEntity(url4, dataItem,ResponseBean.class);
// (1)获取响应体转换对象
ResponseBean responseBean = response.getBody();
// (2)获取response额外信息
HttpStatus statusCode = response.getStatusCode();
int statusCodeValue = response.getStatusCodeValue();
HttpHeaders headers = response.getHeaders();
System.out.println("HTTP 响应状态:" + statusCode);
System.out.println("HTTP 响应状态码:" + statusCodeValue);
System.out.println("HTTP Headers信息:" + headers);
}
}
三.RestTemplate 源码分析(以POST为例)
POST请求的执行过程如下:
1.调用postForObject()方法,方法内调用execute()
2.execute()方法最终会调用doExecute()方法
- 获取连接createRequest():根据url和请求方式从底层HTTP客户端中获取request连接对象
- 附加/转换传递数据doWithRequest():给请求body附加转换数据(object -> Converters List -> JSON String)
- 执行请求execute():拦截器处理、获取响应
- 响应异常处理handleResponse():调用 ErrorHandle
- 响应数据转换extractData():将response Body数据转换为对应responseType的数据并返回(JSON String -> Converters List -> )
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
//...
//1.调用postForObject()方法:
// - 将传递body数据对象object request封装为一个RequestCallback对象
// - 调用execute方法执行post request的处理逻辑
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
//2.execute()方法最终会调用doExecute()方法:这里才是真正的整个post处理流程
// - 获取连接:根据url和请求方式从底层HTTP客户端中获取request连接对象
// - 附加/转换传递数据:执行doWithRequest()方法给请求body附加数据(object -> Converters List -> JSON String)
// - 执行请求:拦截器处理、获取响应
// - 响应异常处理:调用 ErrorHandle
// - 响应数据转换:将response Body数据转换为对应responseType的数据并返回(JSON String -> Converters List -> <T>)
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
//(1)根据url和请求方式,从HTTP客户端(连接池)中获取连接对象request
ClientHttpRequest request = createRequest(url, method);
//(2)判断:如果post传递的body数据(一般为JSON)不为空,则执行doWithRequest()方法给请求附加参数(遍历HttpMessageConverters List,将传递的object request数据转换为JSON String字符串)
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
//(3)执行request请求(经过拦截器),并获取响应response
response = request.execute();
//(4)进行响应的后续处理:状态码判断、异常/错误处理(errorHandle)
handleResponse(url, method, response);
//(5)响应的数据处理与转换:调用responseExtractor.extractData()方法,遍历HttpMessageConverters List,将Body数据转换为对应responseType的数据并返回
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
//3.doWithRequest()方法:转换/附加POST请求的Body数据
// - 获取object body数据对象
// - 遍历 messageConverter List,转换为JSON String数据
@Override
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
//(1)获取传递的object body数据对象
Object requestBody = this.requestEntity.getBody();
//(2)判断requestbody是否为空,若为空则处理头header信息
if (requestBody == null) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
if (httpHeaders.getContentLength() < 0) {
httpHeaders.setContentLength(0L);
}
}
//(3)若requestbody不为空,则遍历 messageConverter List,将Object数据对象转换为JSON String数据(GenericHttpMessageConverter的子类)
else {
Class<?> requestBodyClass = requestBody.getClass();
Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<Object> genericConverter =
(GenericHttpMessageConverter<Object>) messageConverter;
if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
logBody(requestBody, requestContentType, genericConverter);
genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
return;
}
}
else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
logBody(requestBody, requestContentType, messageConverter);
((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
return;
}
}
String message = "No HttpMessageConverter for " + requestBodyClass.getName();
if (requestContentType != null) {
message += " and content type \"" + requestContentType + "\"";
}
throw new RestClientException(message);
}
}
}