美文网首页
RestTemplate分析

RestTemplate分析

作者: 冰火人生 | 来源:发表于2018-04-27 14:39 被阅读0次

    RestTemplate

    示例

    服务端
    @RequestMapping("test")
        public String test(@RequestParam("uid") int uid) {
            logger.info("uid = {}", uid);
            return "test";
        }
    
    客户端

    代码一

    public void send() {
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("uid", 123);
            restTemplate.postForObject("http://localhost:8080/test", params, String.class, new HashMap<String, Object>());
        }
    

    代码二

    public void send() {
            MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();
            params.add("uid", 123);
            restTemplate.postForObject("http://localhost:8080/test", params, String.class, new HashMap<String, Object>());
        }
    

    经过测试发现代码二的请求能被服务端正常处理,而代码一则提示uid参数未能提供。

    分析

    客户端
    public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
                throws RestClientException {
    
            RequestCallback requestCallback = httpEntityCallback(request, responseType);
            HttpMessageConverterExtractor<T> responseExtractor =
                    new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
            return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
        }
    

    可以看到request和responseType被封装成了一个HttpEntityRequestCallback对象,继续跟HttpEntityRequestCallback源码

    private HttpEntityRequestCallback(Object requestBody, Type responseType) {
                super(responseType);
                if (requestBody instanceof HttpEntity) {
                    this.requestEntity = (HttpEntity<?>) requestBody;
                }
                else if (requestBody != null) {
                    this.requestEntity = new HttpEntity<Object>(requestBody);
                }
                else {
                    this.requestEntity = HttpEntity.EMPTY;
                }
            }
    

    可以看到不管是HashMap还是MultiValueMap,request部分最终都被第二个if处理,即放在了HttpEntity的body中。

    接下来看HttpEntityRequestCallback的剩余部分代码

    public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
                super.doWithRequest(httpRequest);
                if (!this.requestEntity.hasBody()) {
                    HttpHeaders httpHeaders = httpRequest.getHeaders();
                    HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                    if (!requestHeaders.isEmpty()) {
                        httpHeaders.putAll(requestHeaders);
                    }
                    if (httpHeaders.getContentLength() < 0) {
                        httpHeaders.setContentLength(0L);
                    }
                }
                else {
                    Object requestBody = this.requestEntity.getBody();
                    Class<?> requestBodyClass = requestBody.getClass();
                    Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
                            ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
                    HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                    MediaType requestContentType = requestHeaders.getContentType();
                    for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
                        if (messageConverter instanceof GenericHttpMessageConverter) {
                            GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
                            if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
                                if (!requestHeaders.isEmpty()) {
                                    httpRequest.getHeaders().putAll(requestHeaders);
                                }
                                if (logger.isDebugEnabled()) {
                                    if (requestContentType != null) {
                                        logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
                                                "\" using [" + messageConverter + "]");
                                    }
                                    else {
                                        logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                    }
    
                                }
                                genericMessageConverter.write(
                                        requestBody, requestBodyType, requestContentType, httpRequest);
                                return;
                            }
                        }
                        else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
                            if (!requestHeaders.isEmpty()) {
                                httpRequest.getHeaders().putAll(requestHeaders);
                            }
                            if (logger.isDebugEnabled()) {
                                if (requestContentType != null) {
                                    logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
                                            "\" using [" + messageConverter + "]");
                                }
                                else {
                                    logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                }
    
                            }
                            ((HttpMessageConverter<Object>) messageConverter).write(
                                    requestBody, requestContentType, httpRequest);
                            return;
                        }
                    }
                    String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
                            requestBodyClass.getName() + "]";
                    if (requestContentType != null) {
                        message += " and content type [" + requestContentType + "]";
                    }
                    throw new RestClientException(message);
                }
            }
    

    很明显,程序会执行第一个else中的逻辑,我们分解来看

    Object requestBody = this.requestEntity.getBody();
                    Class<?> requestBodyClass = requestBody.getClass();
                    Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
                            ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
                    HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                    MediaType requestContentType = requestHeaders.getContentType();
    

    requestBody拿到的就是我们的request部分,即HashMap或MultiValueMap。requestBodyClass和requestBodyType都取决于我们传递的request。MediaType为null。

    继续分析,接下来会遍历所有的HttpMessageConverter,这些对象在RestTemplate的构造函数中被初始化

    public RestTemplate() {
            this.messageConverters.add(new ByteArrayHttpMessageConverter());
            this.messageConverters.add(new StringHttpMessageConverter());
            this.messageConverters.add(new ResourceHttpMessageConverter());
            this.messageConverters.add(new SourceHttpMessageConverter<Source>());
            this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    
            if (romePresent) {
                this.messageConverters.add(new AtomFeedHttpMessageConverter());
                this.messageConverters.add(new RssChannelHttpMessageConverter());
            }
    
            if (jackson2XmlPresent) {
                this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
            }
            else if (jaxb2Present) {
                this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
            }
    
            if (jackson2Present) {
                this.messageConverters.add(new MappingJackson2HttpMessageConverter());
            }
            else if (gsonPresent) {
                this.messageConverters.add(new GsonHttpMessageConverter());
            }
        }
    

    实际测试中,我们加入了Gson的依赖,实际的MessageConvertor如下

    messageconvertors.png

    在遍历过程中会先检查MessageConvertor是否为GenericHttpMessageConverter的子类,如果是则判断是否可以写入,如果能写入则执行写入操作并返回,否则直接判断能否写入,能则执行写入操作

    GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
                            if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
                                if (!requestHeaders.isEmpty()) {
                                    httpRequest.getHeaders().putAll(requestHeaders);
                                }
                                genericMessageConverter.write(
                                        requestBody, requestBodyType, requestContentType, httpRequest);
                                return;
                            }
    }
    else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
                            if (!requestHeaders.isEmpty()) {
                                httpRequest.getHeaders().putAll(requestHeaders);
                            }
                            if (logger.isDebugEnabled()) {
                                if (requestContentType != null) {
                                    logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
                                            "\" using [" + messageConverter + "]");
                                }
                                else {
                                    logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                }
    
                            }
                            ((HttpMessageConverter<Object>) messageConverter).write(
                                    requestBody, requestContentType, httpRequest);
                            return;
                        }
    

    在这些MessageConvertor中只有GsonHttpMessageConverter是GenericHttpMessageConverter的子类,且排在最后。因此,遍历过程中会先判断前六个convertor,能写入则执行写入,最后才是GsonHttpMessageConvertor。接下来,我们依次分析这些HTTPMessageConvertor。

    HttpMessageConvertor data type media type
    ByteArrayHttpMessageConverter byte[] Null,application/octet-stream
    StringHttpMessageConverter String Null,MediaType.TEXT_PLAIN, MediaType.ALL
    ResourceHttpMessageConverter Resource Null,MediaType.ALL
    Jaxb2RootElementHttpMessageConverter 被XmlRootElement注解标识 Null,MediaType.ALL
    AllEncompassingFormHttpMessageConverter MultiValueMap子类 Null,MediaType.ALL,MediaType.APPLICATION_FORM_URLENCODED,MediaType.MULTIPART_FORM_DATA
    GsonHttpMessageConverter all Null,MediaType.ALL,MediaType.APPLICATION_JSON_UTF8, new MediaType("application", "*+json", DEFAULT_CHARSET)

    可以看到MultiValueMap子类的数据会被AllEncompassingFormHttpMessageConverter处理,而HashMap类型的数据会被最后的GsonHTTPMessageConvertor处理。

    接下来看看AllEncompassingFormHttpMessageConverter和GsonHttpMessageConverter的write方法中分别写入了什么。

    AllEncompassingFormHttpMessageConverter

    FormHttpMessageConvertor.png

    做了两件事情

    1. 将MediaType置为application/x-www-form-urlencoded(示例程序中)
    2. 将request中的key value通过&=拼接并写入到body中

    GsonHttpMessageConverter就不截图了,也做了两件事情

    1. 将MediaType置为application/json;charset=UTF-8(示例程序中)
    2. 将request转成json并写入到body中

    至此,我们知道了传MultiValueMap和HashMap的区别

    request type media type body
    MultiValueMap application/x-www-form-urlencoded &=拼接的字符串
    HashMap application/json;charset=UTF-8 json字符串

    自此,客户端的区别分析完毕。接下来看服务端。

    服务端

    先来看看RequestParam和RequestBody两个注解的区别

    @RequestParam

    用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容。(Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)

    RequestParam可以接受简单类型的属性,也可以接受对象类型。
    实质是将Request.getParameter() 中的Key-Value参数Map利用Spring的转化机制ConversionService配置,转化成参数接收对象或字段。

    tip

    Content-Type: application/x-www-form-urlencoded的请求中,
    get 方式中queryString的值,和post方式中 body data的值都会被Servlet接受到并转化到Request.getParameter()参数集中,所以@RequestParam可以获取的到。

    以tomcat为例,org.apache.catalina.connector.Request#parseParameters方法中部分代码如下

    tomcat-request.png

    Content-Type如果不是application/x-www-form-urlencoded,则直接返回。否则就会将body中的参数解析到HttpServletRequest的Parameters中,这样我们通过getParameter方法就可以拿到。

    @RequestBody

    处理HttpEntity传递过来的数据,一般用来处理非Content-Type: application/x-www-form-urlencoded编码格式的数据。

    • GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。
    • POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。

    关键点在于Content_Type,即在客户端分析中提到的MediaType。对于HashMap的request来说,其MediaType是json(GsonHTTPMessageConvertor处理),因此会被RequestBody所处理,RequestParam拿不到参数。而对于MultiValueMap的request来说,其MediaType是application/x-www-form-urlencoded(AllEncompassingFormHttpMessageConverter处理),因此会被RequestParam处理,RequestBody无法处理。

    相关文章

      网友评论

          本文标题:RestTemplate分析

          本文链接:https://www.haomeiwen.com/subject/kdwblftx.html