Spring-Web(一) RestTemplate使用与源码浅析

Spring RestTemplate使用与源码浅析

一.RestTemplate 概述

​ RestTemplate 是 Spring Web 模块封装的一个基于Rest规范提供HTTP请求服务的工具,用于访问第三方的Rest接口。在传统情况下服务端访问 HTTP 服务时,一般会使用 JDKHttpURLConnection 或者 ApacheHttpClient,不过这些方法工具使用比较繁琐、 API 过于复杂、还要手动进行资源回收。在此背景下,RestTemplate 简化了与HTTP服务的通信,并满足RestFul原则,我们只需要专注于请求的发送与结果的获取即可。RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnectionApache HttpComponentsokHttp 等HTTP服务源)基础上,封装了请求构造、资源回收、错误处理等底层操作,提供了更加简单易用的模板方法 API,极大程度上提升了我们的开发效率。

​ 从Spring 5.0开始,RestTemplate 处于维护模式。取而代之的是WebClient,它提供了更现代的 API,并支持同步、异步和流式处理方案,支持更复杂、更丰富、更灵活的应用场景。我们这里暂且不讲。

参考文档:

二.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的HttpClientApache的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);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/126675996