zoukankan      html  css  js  c++  java
  • 详解SpringMVC中Controller的方法中参数的工作原理

    Spring MVC中Controller的处理方法的参数可以是Integer,String,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活。本文将分析SpringMVC是如何对这些参数进行处理的,使读者能够处理自定义的一些参数。

    先来看几个示例:

    @Controller
    public class UserController {
    
        @RequestMapping(value = "/user/testRequestBody")
        @ResponseBody
        public User testRequestBody(@RequestBody User user) {
            return user;
        }
    
        @RequestMapping(value = "/user/testCustomObj")
        @ResponseBody
        public User testCustomObj(User user) {
            return user;
        }
    
        @RequestMapping(value = "/user/testRequestParam")
        @ResponseBody
        public User testRequestParam(@RequestParam User user) {
            return user;
        }
    
        @RequestMapping("/user/testDate")
        @ResponseBody
        public Date testDate(Date date) {
            return date;
        }
    }

    首先这是一个Controller,有4个方法。他们对应的参数分别是带有@RequestBody的自定义对象、自定义对象、带有@RequestParam的自定义对象、日期对象。

    接下来我们一个一个方法进行访问看对应的现象是如何的。

    首先第一个testRequestBody:

    第二个testCustomObj:

     

    第三个testRequestParam:

     

    第四个testDate:

     

    为何User参数会被解析,带有@RequestParam的User参数不会被解析,甚至报错?

    为何日期类型不能被解析?

    SpringMVC到底是如何处理这些方法的参数的?

    @RequestBody、@RequestParam这两个注解有什么区别?

    带着这几个问题。我们开始进行分析。

    在分析源码之前,首先让我们来看下SpringMVC中两个重要的接口。

    两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler

    /**
     * Strategy interface for resolving method parameters into argument values in
     * the context of a given request.
     */
    public interface HandlerMethodArgumentResolver {
    
        /**
         * Whether the given {@linkplain MethodParameter method parameter} is
         * supported by this resolver.
         */
        boolean supportsParameter(MethodParameter parameter);
    
        /**
         * Resolves a method parameter into an argument value from a given request.
         */
        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
    
    }
    /**
     * Strategy interface to handle the value returned from the invocation of a
     * handler method .
     */
    public interface HandlerMethodReturnValueHandler {
    
        /**
         * Whether the given {@linkplain MethodParameter method return type} is
         * supported by this handler.
         */
        boolean supportsReturnType(MethodParameter returnType);
    
        /**
         * Handle the given return value by adding attributes to the model and
         * setting a view or setting the
         * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
         * to indicate the response has been handled directly.
         */
        void handleReturnValue(Object returnValue, MethodParameter returnType,
                ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
    
    }

    SpringMVC处理请求大致是这样的:

    首先被DispatcherServlet截获,DispatcherServlet通过handlerMapping获得HandlerExecutionChain,然后请求HandlerAdapter。

    HandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod进行处理,ServletInvocableHandlerMethod在进行处理的时候,会分两部分别对请求跟响应进行处理

    之后HandlerAdapter得到ModelAndView,然后做相应的处理。

    本文将重点介绍ServletInvocableHandlerMethod对请求以及响应的处理。

    /**
     * Invokes the method and handles the return value through one of the
     * configured {@link HandlerMethodReturnValueHandler}s.
     */
    public void invokeAndHandle(ServletWebRequest webRequest,
            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        /**
        * Invoke the method after resolving its argument values in the context of the given request.
        * Argument values are commonly resolved through HandlerMethodArgumentResolvers.
        */
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);
    
        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(this.responseReason)) {
            mavContainer.setRequestHandled(true);
            return;
        }
    
        mavContainer.setRequestHandled(false);
        try {
            /**
            * Handle the given return value by adding attributes to the model and
            * setting a view or setting the ModelAndViewContainer#setRequestHandled flag to true
            * to indicate the response has been handled directly.
             */
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }

    1. 处理请求的时候,会根据ServletInvocableHandlerMethod的属性argumentResolvers(这个属性是它的父类InvocableHandlerMethod中定义的)进行处理,其中argumentResolvers属性是一个HandlerMethodArgumentResolverComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodArgumentResolver接口的类,里面有各种实现了HandlerMethodArgumentResolver的List集合。

    public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private final List<HandlerMethodArgumentResolver> argumentResolvers =
                new LinkedList<HandlerMethodArgumentResolver>();
    
        private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
                new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
    
    }

    2. 处理响应的时候,会根据ServletInvocableHandlerMethod的属性returnValueHandlers(自身属性)进行处理,returnValueHandlers属性是一个HandlerMethodReturnValueHandlerComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodReturnValueHandler接口的类,里面有各种实现了HandlerMethodReturnValueHandler的List集合。

    public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private final List<HandlerMethodReturnValueHandler> returnValueHandlers =
            new ArrayList<HandlerMethodReturnValueHandler>();
    }

    ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers这两个属性都是在ServletInvocableHandlerMethod进行实例化的时候被赋值的(使用RequestMappingHandlerAdapter的属性进行赋值)。

    private ServletInvocableHandlerMethod createRequestMappingMethod(
            HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        ServletInvocableHandlerMethod requestMethod;
        requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
        requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        requestMethod.setDataBinderFactory(binderFactory);
        requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        return requestMethod;
    }

    RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers这两个属性是在RequestMappingHandlerAdapter进行实例化的时候被Spring容器注入的。

    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    其中默认的ArgumentResolvers:

    /**
     * Return the list of argument resolvers to use including built-in resolvers
     * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
     */
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
        return resolvers;
    }

    默认的returnValueHandlers:

    /**
     * Return the list of return value handlers to use including built-in and
     * custom handlers provided via {@link #setReturnValueHandlers}.
     */
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
        // Single-purpose return value types
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new HttpEntityMethodProcessor(
                getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
        handlers.add(new HttpHeadersReturnValueHandler());
        handlers.add(new CallableMethodReturnValueHandler());
        handlers.add(new DeferredResultMethodReturnValueHandler());
        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
        handlers.add(new ListenableFutureReturnValueHandler());
        // Annotation-based return value types
        handlers.add(new ModelAttributeMethodProcessor(false));
        handlers.add(new RequestResponseBodyMethodProcessor(
                getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
        // Multi-purpose return value types
        handlers.add(new ViewNameMethodReturnValueHandler());
        handlers.add(new MapMethodProcessor());
        // Custom return value types
        if (getCustomReturnValueHandlers() != null) {
            handlers.addAll(getCustomReturnValueHandlers());
        }
        // Catch-all
        if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
        }
        else {
            handlers.add(new ModelAttributeMethodProcessor(true));
        }
        return handlers;
    }

    使用@ResponseBody注解的话最终返回值会被RequestResponseBodyMethodProcessor这个HandlerMethodReturnValueHandler实现类处理。

    我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。

    public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(RequestBody.class);
        }
    
        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
                    returnType.getMethodAnnotation(ResponseBody.class) != null);
        }
    }

    RequestResponseBodyMethodProcessor支持的请求类型是Controller方法参数中带有@RequestBody注解,支持的响应类型是Controller方法带有@ResponseBody注解。 

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException {
        mavContainer.setRequestHandled(true);
        // Try even with null return value. ResponseBodyAdvice could get involved.
        writeWithMessageConverters(returnValue, returnType, webRequest);
    }

    RequestResponseBodyMethodProcessor响应的具体处理是使用消息转换器。

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);
        WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
        if (argument != null) {
            validate(binder, parameter);
        }
        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
        return argument;
    }

    处理请求的时候使用内部的readWithMessageConverters方法。

    @Override
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest,
            MethodParameter methodParam,  Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
        final HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
        InputStream inputStream = inputMessage.getBody();
        if (inputStream == null) {
            return handleEmptyBody(methodParam);
        }
        else if (inputStream.markSupported()) {
            inputStream.mark(1);
            if (inputStream.read() == -1) {
                return handleEmptyBody(methodParam);
            }
            inputStream.reset();
        }
        else {
            final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
            int b = pushbackInputStream.read();
            if (b == -1) {
                return handleEmptyBody(methodParam);
            }
            else {
                pushbackInputStream.unread(b);
            }
            inputMessage = new ServletServerHttpRequest(servletRequest) {
                @Override
                public InputStream getBody() throws IOException {
                    // Form POST should not get here
                    return pushbackInputStream;
                }
            };
        }
        return super.readWithMessageConverters(inputMessage, methodParam, paramType);
    }
    private Object handleEmptyBody(MethodParameter param) {
        if (param.getParameterAnnotation(RequestBody.class).required()) {
            throw new HttpMessageNotReadableException("Required request body content is missing: " + param);
        }
        return null;
    }

    然后会执行父类(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

    /**
     * Create the method argument value of the expected parameter type by reading
     * from the given HttpInputMessage.*/
    @SuppressWarnings("unchecked")
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
            MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
        MediaType contentType;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        if (contentType == null) {
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }
        Class<?> contextClass = methodParam.getContainingClass();
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            if (converter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                if (genericConverter.canRead(targetType, contextClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reading [" + targetType + "] as "" +
                                contentType + "" using [" + converter + "]");
                    }
                    return genericConverter.read(targetType, contextClass, inputMessage);
                }
            }
            Class<T> targetClass = (Class<T>)
                    ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
            if (converter.canRead(targetClass, contentType)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Reading [" + targetClass.getName() + "] as "" +
                            contentType + "" using [" + converter + "]");
                }
                return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
            }
        }
        throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
    }

    下面来我们来看看常用的HandlerMethodArgumentResolver实现类(本文粗略讲下,有兴趣的读者可自行研究)。

    1. RequestParamMethodArgumentResolver

     支持带有@RequestParam注解的参数或带有MultipartFile类型的参数

    2. RequestParamMapMethodArgumentResolver

      支持带有@RequestParam注解的参数 && @RequestParam注解的属性value存在 && 参数类型是实现Map接口的属性

    3. PathVariableMethodArgumentResolver

    支持带有@PathVariable注解的参数 且如果参数实现了Map接口,@PathVariable注解需带有value属性

    4. MatrixVariableMethodArgumentResolver

    支持带有@MatrixVariable注解的参数 且如果参数实现了Map接口,@MatrixVariable注解需带有value属性 

    5. RequestResponseBodyMethodProcessor

     本文已分析过

    6. ServletRequestMethodArgumentResolver

     参数类型是实现或继承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod这些类。

    (这就是为何我们在Controller中的方法里添加一个HttpServletRequest参数,Spring会为我们自动获得HttpServletRequest对象的原因)

    7. ServletResponseMethodArgumentResolver

     参数类型是实现或继承或是ServletResponse、OutputStream、Writer这些类

    8. RedirectAttributesMethodArgumentResolver

     参数是实现了RedirectAttributes接口的类

    9. HttpEntityMethodProcessor

     参数类型是HttpEntity

    从名字我们也看的出来, 以Resolver结尾的是实现了HandlerMethodArgumentResolver接口的类,以Processor结尾的是实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的类。

     

    下面来我们来看看常用的HandlerMethodReturnValueHandler实现类。

    1. ModelAndViewMethodReturnValueHandler

    返回值类型是ModelAndView或其子类

    2. ModelMethodProcessor

    返回值类型是Model或其子类

    3. ViewMethodReturnValueHandler

    返回值类型是View或其子类 

    4. HttpHeadersReturnValueHandler

    返回值类型是HttpHeaders或其子类  

    5. ModelAttributeMethodProcessor

    返回值有@ModelAttribute注解

    6. ViewNameMethodReturnValueHandler

    返回值是void或String

    其余没讲过的读者可自行查看源码。

    下面开始解释为何本文开头出现那些现象的原因:

    1. 第一个方法testRequestBody以及地址 http://localhost:8080/user/testRequestBody?name=zhangsan&age=10

    这个方法的参数使用了@RequestBody,被RequestResponseBodyMethodProcessor进行处理。之后根据http请求头部的contentType然后选择合适的消息转换器进行读取。

    很明显,我们的消息转换器只有默认的那些跟部分json以及xml转换器,且传递的参数name=1&age=3,传递的头部中没有content-type,默认使用了application/octet-stream,因此触发了HttpMediaTypeNotSupportedException异常

    解放方案: 我们将传递数据改成json,同时http请求的Content-Type改成application/json即可(post请求?)。

    2. testCustomObj方法以及地址 http://localhost:8080/user/testCustomObj?name=zhangsan&age=10

    这个请求会找到ServletModelAttributeMethodProcessor这个resolver。默认的resolver中有两个ServletModelAttributeMethodProcessor,只不过实例化的时候属性annotationNotRequired一个为true,1个为false。这个ServletModelAttributeMethodProcessor处理参数支持@ModelAttribute注解,annotationNotRequired属性为true的话,参数不是简单类型就通过,因此选择了ServletModelAttributeMethodProcessor,最终通过DataBinder实例化User对象,并写入对应的属性。

    3 testRequestParam方法以及地址 http://localhost:8080/user/testRequestParam?name=zhangsan&age=10

    这个请求会找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在处理参数的时候使用request.getParameter(参数名)即request.getParameter("user")得到,很明显我们的参数传的是name=zhangsan&age=10。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。 

    解决方案:去掉@RequestParam注解,让ServletModelAttributeMethodProcessor来处理。

    4. testDate方法以及地址 http://localhost:8080/user/testDate?date=2014-05-15

    这个请求会找到RequestParamMethodArgumentResolver。因为这个方法与第二个方法一样,有两个RequestParamMethodArgumentResolver,属性useDefaultResolution不同。RequestParamMethodArgumentResolver支持简单类型,ServletModelAttributeMethodProcessor是支持非简单类型。最终步骤跟第三个方法一样,我们的参数名是date,于是通过request.getParameter("date")找到date字符串(这里参数名如果不是date,那么最终页面是空白的,因为没有@RequestParam注解,参数不是必须的,RequestParamMethodArgumentResolver处理null值返回null)。最后通过DataBinder找到合适的属性编辑器进行类型转换。最终找到java.util.Date对象的构造函数 public Date(String s),由于我们传递的格式不是标准的UTC时间格式,因此最终触发了IllegalArgumentException异常。

     Ref:

    https://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html

  • 相关阅读:
    消息中间件 kafka rabbitmq 选型差异
    kafka生产部署
    logback不同业务的日志打印到不同文件
    Java并发编程核心概念一览
    大数据分析常用去重算法分析『Bitmap 篇』
    java 堆调优
    大规模使用 Apache Kafka 的20个最佳实践
    es定期删除数据
    HTTP协议中源端口和目标端口的问题
    How to duplicate the records in a MongoDB collection
  • 原文地址:https://www.cnblogs.com/winner-0715/p/9748897.html
Copyright © 2011-2022 走看看