美文网首页我爱编程
Spring MVC中组件Controller参数

Spring MVC中组件Controller参数

作者: 小螺钉12138 | 来源:发表于2018-04-06 21:58 被阅读0次

    1、概念

    HandlerMapping通过request找到了Handler,HandlerAdapter是具体用来干活的,每个HandlerAdapter封装了一种Handler的具体使用方法。

    2、HandlerAdapter基本结构

    image

    HandlerAdapter是一个接口实现这个接口的有四个类,其中实现该接口的AbstractHandlerMethodAdapter的子类结构最为复杂,同时也是由于处理的Handler不是固定的格式。下面主要来介绍RequestMappingHandlerAdapter。

    具体的请求处理过程可以分为三步

    • 准备好处理器所需要的参数
    • 使用处理器处理请求
    • 处理返回值,也就是将不同类型的返回值统一处理成ModelAndView类型

    理解参数的绑定之前需要先弄清楚三个问题

    • 都有哪些参数需要绑定(请求方法时,参数的格式多种多样)
    • 参数的值的来源
    • 具体进行绑定的方法

    参数的来源一共有6种

    • request中相关的参数,主要包括url中的参数、post过来的参数以及请求头所包含的值
    • cookie中的参数
    • session中的参数
    • 设置到FlashMap中的参数,这种参数主要用于redirect的参数传递
    • SessionAttribute传递的参数,这类参数通过@SessionAttribute注释传递
    • 通过相应的注释@ModelAttribute的方法进行设置的参数

    参数的具体解析是使用HandlerMethodArgumentResolver类型的组件完成的,不同类型的参数使用不同的ArgumentResolver来解析。

    3、RequestMappingHandlerAdapter自身结构

    3.1 RequestMappingHandlerAdapter创建

    RequestMappingHandlerAdapter的创建是在afterPropertiesSet方法中实现,其中主要内容就是初始化argumentResolver、initBinderArgumentResolver、returnValueHandler以及
    @ControllerAdvice注释的类相关的modelAttributeAdviceCache、initBinderAdviceCache和responseBodyAdvice这6个属性

    • argumentResolver:用于给处理器方法和注释了@ModelAttribute的方法设置参数
    • initBinderArgumentResolver:用于给注释了@initBinder的方法设置参数
    • returnValueHandlers:用于将处理器的返回值处理成ModelAndView的类型
    • modelAttributeAdviceCache和initBinderAdviceCache:分别用于缓存@ControllerAdvice注释的类里面注释了@ModelAttribute和@InitBinder的方法
    • responseBodyAdvice:用来保存前面介绍过的实现了@ResponseBodyAdvice接口、可以修改返回的ResponseBody的类
    
    public void afterPropertiesSet() {
            // Do this first, it may add ResponseBody advice beans
            //初始化注注释了@ControllerAdvice的类的相关属性
            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);
            }
        }
        
    
    
    private void initControllerAdviceCache() {
            if (getApplicationContext() == null) {
                return;
            }
            if (logger.isInfoEnabled()) {
                logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
            }
    
            //获取到所有注释了@ControllerAdvice的bean
            List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
            
            //根据Order排序
            Collections.sort(beans, new OrderComparator());
    
            List<Object> responseBodyAdviceBeans = new ArrayList<Object>();
    
            for (ControllerAdviceBean bean : beans) {
                //查找了注释了@ModelAttribute而且没有注释@RequestMapping的方法
                Set<Method> attrMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
                if (!attrMethods.isEmpty()) {
                    this.modelAttributeAdviceCache.put(bean, attrMethods);
                    logger.info("Detected @ModelAttribute methods in " + bean);
                }
                //查找注释了@InitBinder的方法
                Set<Method> binderMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS);
                if (!binderMethods.isEmpty()) {
                    this.initBinderAdviceCache.put(bean, binderMethods);
                    logger.info("Detected @InitBinder methods in " + bean);
                }
                //查找实现了ResponseBodyAdvice接口的类
                if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
                    responseBodyAdviceBeans.add(bean);
                    logger.info("Detected ResponseBodyAdvice bean in " + bean);
                }
            }
    
            if (!responseBodyAdviceBeans.isEmpty()) {
                this.responseBodyAdvice.addAll(0, responseBodyAdviceBeans);
            }
        }
    

    查看后面三个属性属性的初始化中getDefaultXXX方法

    /**
         * 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;
        }
        //方法中将默认的参数处理器全部添加进来
    
    //注:在添加参数解析器时,顺序是无法进行改变的
    
    3.2.RequestMappingHandlerAdapter使用

    RequestMappingHandlerAdapter处理请求的入口方法是handlerInternal

    @Override
        protected ModelAndView handleInternal(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        //判断Handler是否有@SessionAttribute注释的参数
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                // Always prevent caching in case of session attribute management.
                checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
            }
            else {
                // Uses configured default cacheSeconds setting.
                checkAndPrepare(request, response, true);
            }
    
            // Execute invokeHandlerMethod in synchronized block if required.
            if (this.synchronizeOnSession) {
                HttpSession session = request.getSession(false);
                if (session != null) {
                    Object mutex = WebUtils.getSessionMutex(session);
                    synchronized (mutex) {
                        return invokeHandleMethod(request, response, handlerMethod);
                    }
                }
            }
    
            return invokeHandleMethod(request, response, handlerMethod);
        }
        
    
    /**
         * Check and prepare the given request and response according to the settings 
         * of this generator. Checks for supported methods and a required session,(根据给定的请求属性设置,检查方法和session)
         * and applies the given number of cache seconds.(请求一定时间的缓存时间)
         * @param request current HTTP request
         * @param response current HTTP response
         * @param cacheSeconds positive number of seconds into the future that the
         * response should be cacheable for, 0 to prevent caching
         * @param lastModified if the mapped handler provides Last-Modified support
         * @throws ServletException if the request cannot be handled because a check failed
         */
        protected final void checkAndPrepare(
                HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
                throws ServletException {
    
            // Check whether we should support the request method.
            String method = request.getMethod();
            if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
                throw new HttpRequestMethodNotSupportedException(
                        method, StringUtils.toStringArray(this.supportedMethods));
            }
    
            // Check whether a session is required.
            if (this.requireSession) {
                if (request.getSession(false) == null) {
                    throw new HttpSessionRequiredException("Pre-existing session required but none found");
                }
            }
    
            // Do declarative cache control.
            // Revalidate if the controller supports last-modified.
            applyCacheSeconds(response, cacheSeconds, lastModified);
        }
    
    /**
         * Apply the given cache seconds and generate respective HTTP headers.
         * <p>That is, allow caching for the given number of seconds in the
         * case of a positive value, prevent caching if given a 0 value, else
         * do nothing (i.e. leave caching to the client).
         * @param response the current HTTP response
         * @param seconds the (positive) number of seconds into the future that
         * the response should be cacheable for; 0 to prevent caching; and
         * a negative value to leave caching to the client.
         * @param mustRevalidate whether the client should revalidate the resource
         * (typically only necessary for controllers with last-modified support)
         */
        protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
            if (seconds > 0) {
            //给缓存设置过期时间
                cacheForSeconds(response, seconds, mustRevalidate);
            }
            else if (seconds == 0) {
            //阻止使用缓存
                preventCaching(response);
            }
            // Leave caching to the client otherwise.
        }
    
    protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
            if (this.useExpiresHeader) {
                // HTTP 1.0 header
                response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
            }
            if (this.useCacheControlHeader) {
                // HTTP 1.1 header
                String headerValue = "max-age=" + seconds;
                if (mustRevalidate || this.alwaysMustRevalidate) {
                    headerValue += ", must-revalidate";
                }
                response.setHeader(HEADER_CACHE_CONTROL, headerValue);
            }
        }
        
    protected final void preventCaching(HttpServletResponse response) {
        response.setHeader(HEADER_PRAGMA, "no-cache");
        if (this.useExpiresHeader) {
            // HTTP 1.0 header
            response.setDateHeader(HEADER_EXPIRES, 1L);
        }
        if (this.useCacheControlHeader) {
            // HTTP 1.1 header: "no-cache" is the standard value,
            // "no-store" is necessary to prevent caching on FireFox.
            response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
            if (this.useCacheControlNoStore) {
                response.addHeader(HEADER_CACHE_CONTROL, "no-store");
            }
        }
    }
    

    缓存的设置其实就是对Response的Header进行了相应的设置,上面的代码实际含义是,如果有@SessionAttribute注释则阻止使用缓存,否则就什么也不做

    invokeHandlerMethod方法初始化了三个类型的变量分别是

    • WebDataBinderFactory
    • ModelFactory
    • ServletInvocableHandlerMethod
    /**
         * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
         * if view resolution is required.
         */
        private ModelAndView invokeHandleMethod(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
            ServletWebRequest webRequest = new ServletWebRequest(request, response);
        
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
    
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
            final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
    
            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
    
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
            }
    
            requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
    
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }
    
            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
    

    WebDataBinderFactory主要是用来创建WebDataBinder,它的功能是实现参数和String之间的类型转换,WebDataBinderFactory就是将符合条件的的注释了@InitBinder的方法找出来,这里的InitBinder方法包括两部分:1、注释了@ControllerAdvice的并且符合要求的全局处理器里面的InitBinder方法。2、处理器自身的InitBinder方法

    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
            Class<?> handlerType = handlerMethod.getBeanType();
            //检查当前Handler中InitBinder方法是否已经存在缓存中
            Set<Method> methods = this.initBinderCache.get(handlerType);
            if (methods == null) {
            //如果没有则查找并且设置到缓存中
                methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
                this.initBinderCache.put(handlerType, methods);
            }
            //定义保存InitBinder方法的临时变量
            List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
            // Global methods first(首先添加全局变量到initBinderMethods)
            for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache .entrySet()) {
                if (entry.getKey().isApplicableToBeanType(handlerType)) {
                    Object bean = entry.getKey().resolveBean();
                    for (Method method : entry.getValue()) {
                        initBinderMethods.add(createInitBinderMethod(bean, method));
                    }
                }
            }
            //将当前Handler中的InitBinder方法添加到initBinderMethos
            for (Method method : methods) {
                Object bean = handlerMethod.getBean();
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
            return createDataBinderFactory(initBinderMethods);
        }
    

    ModelFactoryz是用来处理Model的,主要包含两个功能:1、在处理器具体处理之前对Model进行初始化2、在处理完请求后对Model参数进行更新。
    Model初始化分为三个部分:1、将原来的SessionAttribute中的值设置到Model。2、执行相应注释了@ModelAttribute的方法并将其值设置到Mode。3、处理器中注释了@ModelAttribute的参数如果同时在SessionAttribute中也配置了,而且在mavContainer中还没有值则从全部SessionAttribute中查找出来并设置进去

    private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        //获取SessionAttributeHandler
            SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
        //获取处理器的类型
            Class<?> handlerType = handlerMethod.getBeanType();
        //获取处理器类中注释了@ModelAttribute而且没有注释@RequestMapping的类型第一次获取后添加到缓存,以后直接从缓存中获取
            Set<Method> methods = this.modelAttributeCache.get(handlerType);
        //
            if (methods == null) {
                methods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
                this.modelAttributeCache.put(handlerType, methods);
            }
            List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
            // Global methods first(首先添加全局的方法)
            for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
                if (entry.getKey().isApplicableToBeanType(handlerType)) {
                    Object bean = entry.getKey().resolveBean();
                    for (Method method : entry.getValue()) {
                        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
                    }
                }
            }
            for (Method method : methods) {
                Object bean = handlerMethod.getBean();
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
            return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
        }
    

    ServletInvocableHandlerMethod继承自HandlerMethod,并且可以直接执行。实际请求的处理就是通过它来执行的,参数绑定、处理请求以及返回值处理都在它里面完成

    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;
        }
    

    前面invokeHandlerMethod方法中的三个变量弄清楚之后就比较容易理解了,上面三个变量完了之后下面还有三个变量需要处理

    • 1、新建传递参数的ModelAndViewContainer容器,并将相应的参数设置到其Model中
    • 2、执行请求
    • 3、请求处理完后进行一些后置处理

    新建ModelAndViewContainer类型的mavContainer参数,用于保存Model和View,它贯穿于整个处理过程,然后对mavContainer进行了设置,主要包括三部分内容:1、将FlashMap中的数据设置到Model;2、使用modelFactory将SessionAttribute和注释了@ModelAttribute的方法的参数设置到Model;3、根据配置对ignoreDefaultModelOnRedirect进行了参数设置,这个参数在分析ModelAndViewContainer的时候再详细讲解

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    

    执行请求,具体方法是直接调用ServletInvocableHandlerMethod里的invokeAndHandle方法执行

    requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
    

    处理完请求后的后置处理,这是在getModelAndView方法中处理的。一共做了三件事:1、调用ModelFactory的updateModel方法更新了Model。2、根据mavContainer创建了ModelAndView。3、如果mavContainer里的model是RedirectAttribute类型,则将其设置到FlashMap

    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
            modelFactory.updateModel(webRequest, mavContainer);
            if (mavContainer.isRequestHandled()) {
                return null;
            }
            ModelMap model = mavContainer.getModel();
            ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
            if (!mavContainer.isViewReference()) {
                mav.setView((View) mavContainer.getView());
            }
            if (model instanceof RedirectAttributes) {
                Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
                HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
            return mav;
        }
    
    

    相关文章

      网友评论

        本文标题:Spring MVC中组件Controller参数

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