zoukankan      html  css  js  c++  java
  • springmvc源码解析(三)-----@RequestBody和@ResponseBody原理解析

    本文转载自:Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

    概述

    在SpringMVC的使用时,往往会用到@RequestBody和@ResponseBody两个注解,尤其是处理ajax请求必然要使用@ResponseBody注解。这两个注解对应着Controller方法的参数解析和返回值处理,开始时都是只知其用,不知原理。我们来看个例子。

    @RequestMapping("/requestBody")
    public void requestBody(@RequestBody String body, Writer writer) throws IOException{
        writer.write(body);
    }
    
    @RequestMapping(value="/responseBody", produces="application/json")
    @ResponseBody
    public Map<String, Object> responseBody(){
        Map<String, Object> retMap = new HashMap<>();
        retMap.put("param1", "abc");
        return retMap;
    }

    第一个requestBody请求,使用@RequestBody将HTTP请求体转换成String类型,第二个responseBody请求,将Map对象转换成json格式输出到HTTP响应中。这两个请求方法没有什么特殊,就是一个在参数前加了@RequestBody注解,一个在方法上加了@ResponseBody注解。而这两个注解是怎么完成HTTP报文信息同Controller方法中对象的转换的呢?

    SpringMVC处理请求和响应时,支持多种类型的请求参数和返回类型,而此种功能的实现就需要对HTTP消息体和参数及返回值进行转换,为此SpringMVC提供了大量的转换类,所有转换类都实现了HttpMessageConverter接口。

    public interface HttpMessageConverter<T> {
    
        // 当前转换器是否能将HTTP报文转换为对象类型
        boolean canRead(Class<?> clazz, MediaType mediaType);
    
        // 当前转换器是否能将对象类型转换为HTTP报文
        boolean canWrite(Class<?> clazz, MediaType mediaType);
    
        // 转换器能支持的HTTP媒体类型
        List<MediaType> getSupportedMediaTypes();
    
        // 转换HTTP报文为特定类型
        T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
                throws IOException, HttpMessageNotReadableException;
    
        // 将特定类型对象转换为HTTP报文
        void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException;
    
    }

    HttpMessageConverter接口定义了5个方法,用于将HTTP请求报文转换为java对象,以及将java对象转换为HTTP响应报文。

    对应到SpringMVC的Controller方法,read方法即是读取HTTP请求转换为参数对象,write方法即是将返回值对象转换为HTTP响应报文。SpringMVC定义了两个接口来操作这两个过程:参数解析器HandlerMethodArgumentResolver和返回值处理器HandlerMethodReturnValueHandler。

    // 参数解析器接口
    public interface HandlerMethodArgumentResolver {
    
        // 解析器是否支持方法参数
        boolean supportsParameter(MethodParameter parameter);
    
        // 解析HTTP报文中对应的方法参数
        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
    
    }
    
    // 返回值处理器接口
    public interface HandlerMethodReturnValueHandler {
    
        // 处理器是否支持返回值类型
        boolean supportsReturnType(MethodParameter returnType);
    
        // 将返回值解析为HTTP响应报文
        void handleReturnValue(Object returnValue, MethodParameter returnType,
                ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
    
    }

    参数解析器和返回值处理器在底层处理时,都是通过HttpMessageConverter进行转换。流程如下:

    SpringMVC为@RequestBody和@ResponseBody两个注解实现了统一处理类RequestResponseBodyMethodProcessor,实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。

    由上一篇文章我们可以知道,Controller方法被封装成ServletInvocableHandlerMethod类,并且由invokeAndHandle方法完成请求处理。

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        // 执行请求
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        
        // 返回值处理
        try {
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {throw ex;
        }
    }
    
    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        // 参数解析
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        // invoke Controller方法
        Object returnValue = doInvoke(args);
        return returnValue;
    }

    在invoke Controller方法的前后分别执行了方法参数的解析和返回值的处理,我们分别来看。

    参数解析

    private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
    
        // 遍历所有参数,逐个解析
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
    
            // 参数解析器解析HTTP报文到参数
            if (this.argumentResolvers.supportsParameter(parameter)) {
                args[i] = this.argumentResolvers.resolveArgument(
                        parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            }
        }
        return args;
    }

    getMethodArgumentValues方法中的argumentResolvers就是多个HandlerMethodArgumentResolver的集合体,supportsParameter方法寻找参数合适的解析器,resolveArgument调用具体解析器的resolveArgument方法执行。

    我们从RequestResponseBodyMethodProcessor看看@RequestBody的解析过程。RequestResponseBodyMethodProcessor的supportsParameter定义了它支持的参数类型,即必须有@RequestBody注解。

    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    再来看resolveArgument方法

    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
        parameter = parameter.nestedIfOptional();
        // 通过HttpMessageConverter读取HTTP报文
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);
    
        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
        if (arg != null) {
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    
        return adaptArgumentIfNecessary(arg, parameter);
    }

    具体实现由HttpMessageConverter来完成

    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
    
        ....
    
        try {
            inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                ....
                // 判断转换器是否支持参数类型
                if (converter.canRead(targetClass, contentType)) {
                    if (inputMessage.getBody() != null) {
                        inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                        // read方法执行HTTP报文到参数的转换
                        body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
                        body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                    }
                    break;
                }
                ...
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }
    
        ....
    
        return body;
    }

    代码部分省略了,关键部分即是遍历所有的HttpMessageConverter,通过canRead方法判断转换器是否支持对参数的转换,然后执行read方法完成转换。

    返回值处理

    完成Controller方法的调用后,在ServletInvocableHandlerMethod的invokeAndHandle中,使用返回值处理器对返回值进行转换。 

    this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

    这里的returnValueHandlers也是HandlerMethodReturnValueHandler的集合体HandlerMethodReturnValueHandlerComposite

    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
        // 选择合适的HandlerMethodReturnValueHandler,如果没有用@ResposeBody注解和用了注解其返回值处理器肯定不同
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        }
        // 执行返回值处理
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

    selectHandler方法遍历所有HandlerMethodReturnValueHandler,调用其supportsReturnType方法选择合适的HandlerMethodReturnValueHandler,然后调用其handleReturnValue方法完成处理。

    这里还是以RequestResponseBodyMethodProcessor来分析下@ResponseBody的处理,它的具体实现在AbstractMessageConverterMethodProcessor抽象基类中。

    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }

    RequestResponseBodyMethodProcessor要求方法上有@ResponseBody注解或者方法所在的Controller类上有@ResponseBody的注解。这就是常常用@RestController注解代替@Controller注解的原因,因为@RestController注解自带@ResponseBody。

    handleReturnValue方法实际也是调用HttpMessageConverter来完成转换处理

    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
        // 调用HttpMessageConverter执行
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
    
    protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
        ....
    
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                // 判断是否支持返回值类型,返回值类型很有可能不同,如String,Map,List等
                if (messageConverter.canWrite(valueType, selectedMediaType)) {
                    outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                            inputMessage, outputMessage);
                    if (outputValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        // 执行返回值转换
                        ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
                        ...
                    }
                    return;
                }
            }
        }
        ....
    }

    使用canWrite方法选择合适的HttpMessageConverter,然后调用write方法完成转换。

    我们看看传入的参数 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); 

    protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
        //获取HttpServletResponse
        HttpServletResponse response = (HttpServletResponse)webRequest.getNativeResponse(HttpServletResponse.class);
        Assert.state(response != null, "No HttpServletResponse");
        return new ServletServerHttpResponse(response);
    }
    
    public class ServletServerHttpResponse implements ServerHttpResponse {
        private final HttpServletResponse servletResponse;
        private final HttpHeaders headers;
        private boolean headersWritten = false;
        private boolean bodyUsed = false;
    
        public ServletServerHttpResponse(HttpServletResponse servletResponse) {
            Assert.notNull(servletResponse, "HttpServletResponse must not be null");
            //将获取的HttpServletResponse作为ServletServerHttpResponse的属性值
            this.servletResponse = servletResponse;
            this.headers = new ServletServerHttpResponse.ServletResponseHttpHeaders();
        }
    }
    
    public interface ServletResponse {
        String getCharacterEncoding();
    
        String getContentType();
        
        //ServletResponse有一个输出流对象,保存需要相应客户端的字节流
        ServletOutputStream getOutputStream() throws IOException;
    
        PrintWriter getWriter() throws IOException;
    
        void setCharacterEncoding(String var1);
    
        void setContentLength(int var1);
    
        void setContentLengthLong(long var1);
    
        void setContentType(String var1);
    
        void setBufferSize(int var1);
    
        int getBufferSize();
    
        void flushBuffer() throws IOException;
    
        void resetBuffer();
    
        boolean isCommitted();
    
        void reset();
    
        void setLocale(Locale var1);
    
        Locale getLocale();
    }

    我们具体看看  ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);

    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        HttpHeaders headers = outputMessage.getHeaders();
        //创建一个数组字节流缓冲对象
        ByteArrayOutputStream outnew = new ByteArrayOutputStream();
        //将obj对象转换成JSON并写入ByteArrayOutputStream中
        int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), obj, this.fastJsonConfig.getSerializeConfig(), this.fastJsonConfig.getSerializeFilters(), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
        headers.setContentLength((long)len);
        //获取ServletResponse的输出流对象    
        OutputStream out = outputMessage.getBody();
        //将转换后的outnew写入ServletResponse的输出流对象,这样就可以给客户端响应数据了
        outnew.writeTo(out);
        outnew.close();
    }
    
    public OutputStream getBody() throws IOException {
        this.bodyUsed = true;
        this.writeHeaders();
        //获取ServletResponse的输出流对象    
        //ServletOutputStream getOutputStream() throws IOException;
        return this.servletResponse.getOutputStream();
    }

    最后我们看看JSON是怎么将obj对象转换成JSON对象的流 

    就是做一些循环拼接。

     至此我们基本走完了一个HTTP请求报文经过处理后到HTTP响应报文的转换过程。现在你可能有个疑惑,SpringMVC我们都是开箱即用,这些参数解析器和返回值处理器在哪里定义的呢?在核心的HandlerAdapter实现类RequestMappingHandlerAdapter的初始化方法中定义的。

    而在RequestMappingHandlerAdapter构造时,也同时初始化了众多的HttpMessageConverter,以支持多样的转换需求。

    WebMvcConfigurationSupport.java
    
    protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setWriteAcceptCharset(false);
    
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new SourceHttpMessageConverter<Source>());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    
        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }
    
        if (jackson2XmlPresent) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }
    
        if (jackson2Present) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
    }

    对于json或xml的转换方式,只要引入了jackson的依赖,即可自动发现,并注册相关的转换器。

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.0</version>
    </dependency>
  • 相关阅读:
    PHP操作数据库
    ANE打包
    我的C++笔记(数据的共享与保护)
    js 正计时和倒计时
    JAVA 日期 一个时间段总共几周,每一天星期几
    mybatis 处理in
    freemarker
    正则手机验证,身份证验证
    签到
    spring定时器
  • 原文地址:https://www.cnblogs.com/alimayun/p/12932500.html
Copyright © 2011-2022 走看看