当前位置: 首页 > news >正文

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,并支持同步、异步和流式处理方案,支持更复杂、更丰富、更灵活的应用场景。我们这里暂且不讲。

参考文档:

  • 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的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
ByteArrayHttpMessageConverterbyte[]supports all media types (*/*) and writes with a Content-Type of application/octet-stream (字节数组 http消息转换器)
StringHttpMessageConverterStringsupports all text media types (text/*) and writes with a Content-Type of text/plain. (string http消息转换器)
ResourceHttpMessageConverterResource/ (读写Resource的 http消息转换器 比如读取media、file之类)
SourceHttpMessageConverterSourceapplication/xml, text/xml, application/*+xml (Source http消息转换器 用于转换Source类型对象)
AllEncompassingFormHttpMessageConverterMap<K, List<?>>application/x-www-form-urlencoded, multipart/form-data (所有通用消息转换器)
MappingJackson2HttpMessageConverterObjectapplication/json, application/*+json (jackson消息转换器 可以将json和Java对象进行相互转换)
Jaxb2RootElementHttpMessageConverterObjectapplication/xml, text/xml, application/*+xml (JAXB 可以将java对象与xml进行相互转换)
JavaSerializationConverterSerializablex-java-serialization;charset=UTF-8 (序列化转换器)
FastJsonHttpMessageConverterObject/ (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 groupDescription
getForObjectRetrieves a representation via GET.
getForEntityRetrieves a ResponseEntity (that is, status, headers, and body) by using GET.
headForHeadersRetrieves all headers for a resource by using HEAD.
postForLocationCreates a new resource by using POST and returns the Location header from the response.
postForObjectCreates a new resource by using POST and returns the representation from the response.
postForEntityCreates a new resource by using POST and returns the representation from the response.
putCreates or updates a resource by using PUT.
patchForObjectUpdates 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.
deleteDeletes the resources at the specified URI by using DELETE.
optionsForAllowRetrieves allowed HTTP methods for a resource by using ALLOW.
exchangeMore 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.
executeThe 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);
        }
    }
}

相关文章:

  • letax环境过程记录 20220831
  • fastapi+mongo+qlib:体系化构建AI量化投研平台
  • ClickHouse学习笔记之表引擎
  • 智能家居生态:华为、小米各异
  • arx 读入块表
  • 避孕套、安全套分类|常识|图文指南(杜蕾斯、冈本、杰士邦)
  • postgres源码解析 SysLogger辅助进程
  • MySql ocp认证之数据导入导出(五)
  • 论文阅读(10) 基于吸力的推进是动物高效游泳的基础(2015)
  • 《大数据之路:阿里巴巴大数据实践》-第2篇 数据模型篇 -第10章 维度设计
  • 【java】基础内容(4)
  • Perfetto分析进阶
  • Framework入门のPiex 6P源码(下载/编译/刷机)
  • 高端手机市场的诸神之战,vivo举起一把“雷神之锤”
  • 简单的ajax任务:get和post方式提交前端用户输入信息给服务器
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • CSS居中完全指南——构建CSS居中决策树
  • Magento 1.x 中文订单打印乱码
  • React-flux杂记
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 搞机器学习要哪些技能
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 浏览器缓存机制分析
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 由插件封装引出的一丢丢思考
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • 仓管云——企业云erp功能有哪些?
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (C语言)字符分类函数
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (初研) Sentence-embedding fine-tune notebook
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (数据结构)顺序表的定义
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • *2 echo、printf、mkdir命令的应用
  • .apk文件,IIS不支持下载解决
  • .form文件_SSM框架文件上传篇
  • .Net MVC4 上传大文件,并保存表单
  • .NET Standard 的管理策略
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .Net的C#语言取月份数值对应的MonthName值
  • .net连接MySQL的方法
  • /etc/sudoers (root权限管理)
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)
  • [ vulhub漏洞复现篇 ] JBOSS AS 5.x/6.x反序列化远程代码执行漏洞CVE-2017-12149
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell