Spring-Web (1) RestTemplate usage and source code analysis

Spring RestTemplate usage and source code analysis

1. RestTemplate overview

​ RestTemplate is a tool that provides HTTP request services based on the Rest specification encapsulated by the Spring Web module, and is used to access third-party Rest interfaces. Traditionally, when the server accesses HTTPthe service, it generally uses JDKor HttpURLConnection, but these methods and tools are cumbersome, overly complicated, and require manual resource recovery Apache. In this context, RestTemplate simplifies communication with HTTP services and meets the RestFul principle. We only need to focus on sending requests and obtaining results. It is a synchronous blocking tool class for executing requests . It only encapsulates underlying operations such as request construction, resource recycling, and error handling on the basis of client libraries (for example , , etc. HTTP service sources), providing a simpler and easier-to-use The template method API has greatly improved our development efficiency.HttpClientAPIRestTemplateHTTPHTTPJDK HttpURLConnectionApache HttpComponentsokHttp

Since Spring 5.0, RestTemplate is in maintenance mode. It is replaced by WebClient, which provides a more modern API and supports synchronous, asynchronous, and streaming solutions, supporting more complex, richer, and more flexible application scenarios. We will not talk about it here.

Reference documents:

Two. RestTemplate use

1. RestTemplate introduction

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. RestTemplate Placement

2.1 Configure HTTP source

(1) HTTP client introduction

​ RestTemplate inherits from HttpAccessor. This class can be understood as an abstract base class for contacting/accessing HTTP underlying clients. Its source code is as follows. It can be seen that the ClientHttpRequestFactory factory (or client request library ) attribute in this class is specially used to construct a request request through the corresponding underlying HTTP connection client, and provide HTTP request access services upwards, and its default assignment is SimpleClientHttpRequestFactory. To switch between different HTTP sources, we need to assign other clients to ClientHttpRequestFactory through the setRequestFactory method.

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;
    }
    
    ... ...
}

​ The underlying HTTP client is used to establish an HTTP connection, the Factory is used to construct a Request from the corresponding HTTP connection to initiate a request, and the RestTemplate is used to encapsulate the above process and provide a simpler and more convenient template API to the outside world. The HTTP client connection library that supports RestTemplate is implemented from the ClientHttpRequestFactory interface. Common client libraries and their corresponding HTTP clients are introduced as follows:

  • SimpleClientHttpRequestFactory: the default client library of RestTemplate, and its corresponding HTTP connection client type is the HttpURLConnection that comes with java JDK as the underlying HTTPclient implementation.
  • HttpComponentsAsyncClientHttpRequestFactory: The corresponding HTTP connection client type is Apache's HttpClient as the underlying HTTPclient implementation.
  • OkHttp3ClientHttpRequestFactory: The corresponding HTTP connection client type is OkHttpClient as the underlying HTTPclient implementation.

​ Judging from the feedback from developers and various HTTPclient performance and ease-of-use evaluations on the Internet, OkHttpClientit is superior Apache的HttpClientand Apache的HttpClientsuperior HttpURLConnection. And it should be noted that the HttpURLConnection that comes with java JDK does not support the Patch method of the HTTP protocol. We can switch the underlying HTTP client implementation class library of the RestTemplate by setting the setRequestFactory method.

(2) Switch HTTP client

  • Default SimpleClientHttpRequestFactory configuration
@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 configuration

​ HttpComponentsAsyncClientHttpRequestFactory uses the HttpClient client of Apache HttpComponents to create Request requests, which expands functions such as authentication and HTTP connection pooling. The configuration steps mainly include the following:

  • Introduce the HttpClient dependency package (otherwise you cannot configure the connection pool, RequestConfig, etc., and you can only use the default configuration)
  • Http connection pool configuration (connection mode, maximum number of connections, etc.), Resquest request configuration (connection time, reading time, etc.), HttpClient client configuration
  • HttpComponentsAsyncClientHttpRequestFactory configuration
<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 configuration

​ OkHttp is an efficient HTTP client:

  • Allow all requests from the same host address to share the same socket connection;
  • Connection pool can reduce request delay;
  • Transparent GZIP compression reduces response data size;
  • Caching responses can completely avoid some exact duplicate network requests

​ When there is a problem with the network, OkHttp still sticks to its responsibilities, and it will automatically restore the general connection problem; if your service has multiple IP addresses, when the first IP request fails, OkHttp will alternately try other configurations you configured IP. Using OkHttp is easy, its request/response API has fluent builders and immutability. It supports synchronous blocking calls and asynchronous calls with callbacks.

<!--引入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 Configure Interceptor

​ Sometimes we need to make some general interception settings for requests, such as printing request logs, adding Token verification, default Header information, etc. At this time, we can add interceptors to process RestTemplate requests. RestTemplate can set the internal interceptor chain List through the setInterceptors method to intercept and process requests. Each custom interceptor should implement the ClientHttpRequestInterceptor interface.

insert image description here

/**
 * 记录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 Configuring the Converter

​ The data content passed by the Reseful interface is a string in json format, but RestTemplatethe encapsulation method is to directly pass the java class as the data object, which HttpMessageConverterautomatically converts it for us at the bottom layer. Before the POST request is sent/after the response is received, RestTemplate will traverse its internal messageConverters List, match the set return type responseType and the request contentType parameter, and select the first appropriate MessageConverter to match and process the request Body data.

(1) The converter corresponds to the data relationship

​ By default, RestTemplatea set of self-contained ones are automatically registered for us HttpMessageConverterto handle some different types contentTypeof requests during initialization. The corresponding relationship between different Convert and MediaType is as follows:

class name Supported JavaTypes Supported MediaTypes
ByteArrayHttpMessageConverter byte[] supports all media types ( */*) and writes with a Content-Typeof application/octet-stream(byte array http message converter)
StringHttpMessageConverter String supports all text media types ( text/*) and writes with a Content-Typeof text/plain. (string http message converter)
ResourceHttpMessageConverter Resource / (The http message converter for reading and writing Resource, such as reading media, file, etc.)
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml (Source http message converter is used to convert Source type objects)
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data (all generic message converters)
MappingJackson2HttpMessageConverter Object application/json, application/*+json (jackson message converter can convert json and Java objects)
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml (JAXB can convert java objects and xml)
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8 (serialization converter)
FastJsonHttpMessageConverter Object / (FastJson message converter can convert between json and Java objects)

insert image description here
(2) Register FastJsonHttpMessageConvert converter

<!-- 引入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 access request

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 request

There are two commonly used method types by RestTemplatesending HTTP GETprotocol requests:

  • getForObject(): The return value corresponds to HTTPthe response body of the protocol, which is returned after the HttpMessageConverter automatically performs type conversion and encapsulates the object
  • getForEntity(): What is returned is ResponseEntityan object, ResponseEntitywhich is HTTPthe encapsulation of the response. In addition to the response body, it also includes HTTPthe status code and contentType、contentLength、Headerother Response information
- 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 request

​ By RestTemplatesending HTTP PSOTprotocol requests, there are also two commonly used method types (only one more Object request parameter is used to transfer Post data):

  • postForObject(): The return value corresponds to HTTPthe response body of the protocol, which is returned after the HttpMessageConverter automatically performs type conversion and encapsulates the object
  • postForEntity(): What is returned is ResponseEntityan object, ResponseEntitywhich is HTTPthe encapsulation of the response. In addition to the response body, it also includes HTTPthe status code and contentType、contentLength、Headerother Response information
- 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);
    }

}

3. RestTemplate source code analysis (take POST as an example)

The execution process of the POST request is as follows:

1. Call the postForObject() method, and call execute() in the method

2. The execute() method will eventually call the doExecute() method

  • Get connection createRequest(): Get the request connection object from the underlying HTTP client according to the url and request method
  • Append/convert transfer data doWithRequest(): attach conversion data to request body (object -> Converters List -> JSON String)
  • Execution request execute(): interceptor processing, get response
  • Response exception handling handleResponse(): call ErrorHandle
  • Response data conversion extractData(): convert the response Body data into data corresponding to the responseType and return (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);
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_40772692/article/details/126675996