RestTemplate分析

RestTemplate

示例

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

代码一

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

代码二

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

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

分析

客户端
public  T postForObject(String url, Object request, Class responseType, Map uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor responseExtractor =
                new HttpMessageConverterExtractor(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(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 genericMessageConverter = (GenericHttpMessageConverter) 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) 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());
        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 genericMessageConverter = (GenericHttpMessageConverter) 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) 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分析)