Spring MVC 解读——@RequestMapping (2)

Stella981
• 阅读 692

Spring MVC 解读——@RequestMapping

    上一篇文章中我们了解了Spring如何处理@RequestMapping注解,并将请求映射信息保存到系统中以处理客户端发送来的请求,但是Spring是怎样接受请求,并根据请求URL来匹配正确的处理器方法呢,更重要的是Spring允许我们定义签名灵活的处理器方法,也就是说你的参数类型,顺序,返回类型都可以自定义,只要你方便就好。真的很棒,但是他怎样做到的呢? 这篇文章我们就研究这个问题。

一、重要的类和接口

  1. HandlerMethodArgumentResolver 一个策略接口,根据请求解析处理方法的参数值(这是重点),我们就是通过实现它来自定义我们的参数类型的。Spring默认提供了十多个类型的解析器来处理常见的方法参数,如@PathVariabe,@RequestParam, Model,@ModelAttribute等等。

  2. HandlerMethodReturnValueHandler 一个策略接口,用来处理处理器方法的返回值(重点啊), 我们通过实现它来自定义我们的返回类型

  3. RequestMappingHandlerAdapter 一个HandlerAdapter的实现类,用来处理HandlerMethod,它包含了上面两个接口一系列实现类的列表,用于处理不同的参数和返回类型

  4. HandlerMethodArgumentResolverComposite 这个类 维护了一个MethodParameter 与 HandlerMethodArgumentResolver的映射表,可以快速检索某一MethodParameter对应的Resolver,并调用Resolver进行参数解析。

  5. HandlerMethodReturnValueHandlerComposite 这个类只维护了一个所有HandlerMethodReturnValueHandler的列表,每次遍历检索支持某返回类型的处理器。

  6. ModelAndViewContainer 这个类记录了Model和View的对应关系,可快速检索某个视图对应的Model。

说明:当我们阅读@RequestMapping注解的说明时会了解到,Spring的处理器方法默认支持诸多参数类型和返回值类型,并提供了每一个参数类型,返回值类型的解析器和处理器,上述1,2两个接口的实现类,我们大致看一下他们的类层次结构:

Spring MVC 解读——@RequestMapping (2)

Spring MVC 解读——@RequestMapping (2)

    以上是方法参数的可能类型以及他们的解析器,Spring默认支持还是挺棒的,可以应付绝大多数需求了。

Spring MVC 解读——@RequestMapping (2)

Spring MVC 解读——@RequestMapping (2)

    以上是可能的返回值类型和他们的处理器,同样Spring的默认支持很强大,我们都可以实现自己的参数解析器和返回值处理器。

我们可以看到支持的类型非常多,由于篇幅限制,我们只讲解最常用的一个或几个,如@PathVariable,@RequestParam,Model等,如果有兴趣大家可以自行研究,思路都一样。

二、找到请求对应的处理方法

    我们知道Spring会通过DispatcherServlet来处理所有的请求,那么我们就看他是怎么处理的

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                processedRequest = checkMultipart(request);
                // 确定当前请求的处理器(HandlerExecutionChain 包含Handler和Interceptor列表)
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);//404异常
                    return;
                }
                // 确定当前请求的处理器适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //......省略诸多代码......
                // 调用处理处理器方法,返回ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            }//处理返回结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
    }

   相信这个方法大家都很熟了,我删除了一些与当前主体无关的代码,下面我们看getHandler是怎么做的:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //这里有一个handlerMappings实例变量,如果你看过<mvc:annotation-driven/>那篇文章,相信你就知道这
        //个handlerMappings都包括哪些HandlerMapping实例了?
        for (HandlerMapping hm : this.handlerMappings) {
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

   mvc:annotation-driven/这篇博客 中说明了在启用该标签时Spring会默认注册RequestMappingHandlerMapping实例在处理@RequestMapping注解,而@RequestMapping (1) 这篇博客说明了该HandlerMapping是如何处理@RequestMapping注解,以及怎么保存请求映射关系的。下面我们就看RequestMappingHandlerMapping的getHandler方法:

//.....
//AbstractHandlerMapping
//.....
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);//调用下面的方法
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }//返回该请求对应的HandlerExecutionChain(包括处理器方法和拦截器)
        return getHandlerExecutionChain(handler, request);
    }
//.....
//AbstractHandlerMethodMapping(RequestMappingHandlerMapping 的祖先类)
//返回值是HandlerMethod
//.....
@Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        ///查找请求路径对应的HandlerMethod实例
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        //确保HandlerMethod中的handler是处理器实例而不是处理器名字
        return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null;
    }

以上两个方法都是RequestMappingHandlerMapping的祖先类,逻辑很简单,先获取当前请求的路径,然后查找该路径对应的HandlerMethod实例。@RequestMapping (1)
这篇博客最后讲到了,RequestMappingHandlerMapping中的两个映射表实例,urlMap和handlerMethods,第一个是路径与RequestMappingInfo的映射,第二个是RequestMappingInfo和HandlerMethod的映射,不用说,lookupHandlerMethod方法肯定是检索这两个变量了:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request){
        List<Match> matches = new ArrayList<Match>();
        //查找urlMap,获取直接匹配的RequestMappingInfo列表。如
        //URL 是/work/produce/2, @RequestMapping("/work/produce/2")直接匹配
        List<T> directPathMatches = this.urlMap.get(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {//如果没有找到直接匹配项,遍历所有的注册的RequestMappingInfo来查找
            //遍历所有可能的RequestMappingInfo,找到完全匹配的RequestMappingInfo实例,并生成Match对象
            //添加到Match列表中,Match是RequestMappingInfo和HandlerMethod的临时映射表。
            //举个例子:请求URL可能是GET:/work/produce/2,
            //而@RequestMapping("/work/produce/{no}" "GET")此时需要匹配是否是GET请求,以及模式是否匹配
            addMatchingMappings(this.handlerMethods.keySet(), matches, request);
        }
        if (!matches.isEmpty()) {//排序,找出最佳匹配
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {//如果可能的方法多余一个
                Match secondBestMatch = matches.get(1);//并且两个方法的@RequestMapping内容相同
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {//抛出异常
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for HTTP path");
                }
            }
            //这里是处理请求路径中的变量,如果/work/produce/{no}匹配的/work/produce/2中将no=2
            //添加到Request的属性表中,以便后面@PathVarible参数的处理
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
        }
    }

   在进行URL匹配中,Spring会先查找是否存在直接匹配的RequestMappingInfo实例,即@RequestMapping中的value,method属性完全匹配请求的,如果没有找到通常是存在PathVariable的,如果上面讲的/{no}和/222的情况等也是匹配的,  找到匹配项后,需要找出最优解,然后将路径中的变量存入Request的变量表中,我们分别详细的了解下:

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, 
                                                                HttpServletRequest request) {
        for (T mapping : mappings) {//遍历所有的RequestMappingInfo列表
            T match = getMatchingMapping(mapping, request);//获取匹配的RequestMappingInfo实例
            if (match != null) {//并生成匹配的RequestMappingInfo实例和对应HandlerMethod的Match实例
                matches.add(new Match(match, handlerMethods.get(mapping)));
            }
        }
    }

   继续看getMatchingMapping的实现:

public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        //查看RequestMappingInfo的所有属性是否匹配
        RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request);
        ParamsRequestCondition params = paramsCondition.getMatchingCondition(request);
        HeadersRequestCondition headers = headersCondition.getMatchingCondition(request);
        ConsumesRequestCondition consumes = consumesCondition.getMatchingCondition(request);
        ProducesRequestCondition produces = producesCondition.getMatchingCondition(request);
        if (methods == null || params == null || headers == null 
                                                    || consumes == null || produces == null) {
            return null;
        }//我们重点看这个,路径是否匹配
        PatternsRequestCondition patterns = patternsCondition.getMatchingCondition(request);
        if (patterns == null) {
            return null;
        }
        RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request);
        if (custom == null) {
            return null;
        }
        return new RequestMappingInfo(patterns, methods, params, headers, 
                                                        produces, custom.getCondition());
    }

   我们知道RequestMappingInfo就是@RequestMapping注解的抽象,它包含@RequestMapping中的所有属性,因此在查找匹配项时,需要查看所有这些属性是否与请求匹配。我们这里只看路径模式是否匹配,其他属性自行研究,都很简单:

public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
        if (this.patterns.isEmpty()) {
            return this;
        }//获取请求路径如/work/produce/2
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        List<String> matches = new ArrayList<String>();
        //遍历@RequestMapping中的所有模式
        for (String pattern : patterns) {
            //找出与请求路径匹配的模式,如/work/produce/{no}
            String match = getMatchingPattern(pattern, lookupPath);
            if (match != null) {
                matches.add(match);
            }
        }//排序
        Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
        //返回匹配的请求模式实例
        return matches.isEmpty() ? null : 
            new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, 
                                       this.useSuffixPatternMatch, this.useTrailingSlashMatch);
    }

private String getMatchingPattern(String pattern, String lookupPath) {
        if (pattern.equals(lookupPath)) {
            return pattern;//直接匹配
        }
        if (this.useSuffixPatternMatch) {//是否使用后缀模式,/abc/de匹配/abc
            boolean hasSuffix = pattern.indexOf('.') != -1;
            if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
                return pattern + ".*";
            }
        }//匹配/work/produce/{no} 和/work/produce/2
        if (this.pathMatcher.match(pattern, lookupPath)) {
            return pattern;
        }//是否使用结尾的斜线匹配
        boolean endsWithSlash = pattern.endsWith("/");
        if (this.useTrailingSlashMatch) {
            if (!endsWithSlash && this.pathMatcher.match(pattern + "/", lookupPath)) {
                return pattern +"/";
            }
        }
        return null;
    }

   至于上面的pathMatcher.match方法这里就不分析了,可以自己看看,匹配算法还是比较复杂的,主要是尽可能的全面,除了进行匹配外,还会将路径中的变量保存起来以便@PathVariable参数使用。

以上便是整个的匹配过好麻烦,或许你会说这会不会降低Spring的性能?实话说,在处理首次请求时,效率是很差,但是Spring使用了各种缓存策略,一旦程序进入正轨,效率就非常高了。

三、处理器方法的调用

    现在我们已经查找到了对应请求的处理器方法,下面我们就看Spring是如何在运行时动态地调用处理器方法的,并传递正确的参数。在doDispatch方法中,我们看到,确定了处理器(方法)后,Spring接着获取了该处理器方法的适配器(HandlerAdapter概念讲解中说到过,用来调用处理器方法的)

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        //这个handlerAdapters跟handlerMappings一样,启用<mvc:annoation-driven/>后默认注册
        //RequestMappingHandlerAdapter(since3.1)
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (ha.supports(handler)) {
                return ha;
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: Does your handler implement a supported interface like Controller?");
    }
//是否支持当前处理器,其实就是看看处理器是不是HandlerMethod实例
public final boolean supports(Object handler) {
        return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler);
    }

   获取了HandlerAdapter后,Spring就会调用handlerAdapter实例的handle方法,并返回ModelAndView实例:

@Override
    protected final ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        //如果HandlerMethod所属的处理器被@SessionAttribute注解标记了
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
         //设置响应头信息,防止缓存以便Session属性的管理
         checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
        }
        else {
            //设置响应头,缓存默认时间
            checkAndPrepare(request, response, true);
        }
        //要求在Session级别上进行同步,即同一个客户端的多个请求需要阻塞调用该处理器方法
        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);
    }

   我们接着看invokeHandlerMethod方法:

private ModelAndView invokeHandleMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        //包装请求和响应对象
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        //获取与HandlerMethod对应的DataBinderFactory。
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        //获取处理器方法所属处理器中被@ModelAttribute标记,但是没有被@RequestMapping标记的方法
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        //创建请求映射方法,并将HandlerAdapter中的参数解析器列表和返回值处理器列表传递给它。
        ServletInvocableHandlerMethod requestMappingMethod = 
                                      createRequestMappingMethod(handlerMethod, binderFactory);
        //创建ModelAndViewContainer
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
        //获取并设置当前请求的异步调用链实例
        AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
        chain.addDelegatingCallable(getAsyncCallable(mavContainer, modelFactory, webRequest));
        chain.setAsyncWebRequest(createAsyncWebRequest(request, response));
        chain.setTaskExecutor(this.taskExecutor);
        //调用该处理器方法。
        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

        if (chain.isAsyncStarted()) {
            return null;
        }
        //获取并返回ModelAndView
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }

   关于异步调用链那块我们暂不关心,后续文章会专题讨论。从上面代码可以看到,在调用方法前,分别检查了处理器中存在的@InitBinder注解的方法和@ModelAttribute注解的方法,InitBinder方法用于类型转化,如将String转化为Date类型等,可以通过@InitBinder方法实现,感兴趣可以自己看看,不在详细分析。至于@ModelAttribute注解的方法,其返回值会被放入Model对象中供视图使用。下面我们看invokeAndHandle方法:

public final 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);
        }
    }

   接着我们看下invokeForRequest方法:----we are so close.

public final Object invokeForRequest(NativeWebRequest request,
                                         ModelAndViewContainer mavContainer,
                                         Object... providedArgs) throws Exception {
        //获取方法参数值。
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        //传递参数值,调用,返回返回值
        Object returnValue = invoke(args);
        return returnValue;
    }

   啊哈上面的代码貌似很简单,实则不是,重点就在getMethodArgumentValues方法,这才是我们这片文章的真正主题呢。打起精神来了:

private Object[] getMethodArgumentValues(
            NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //获取处理器方法的MethodParameter数组,就是方法的“参数定义”列表。
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        //遍历所有方法参数
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(parameterNameDiscoverer);
            //确定泛型参数的类型
            GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
            //根据提供的参数值,解析当前参数的值
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            //根据内置的参数解析器(上面的图片中列出了),来解析当前的参数值
            if (argumentResolvers.supportsParameter(parameter)) {
                try {
                    args[i] = argumentResolvers.resolveArgument(parameter, mavContainer,
                                                                 request, dataBinderFactory);
                    continue;
                }
            }
            //如果参数值依旧为空,抛出异常。
            if (args[i] == null) {
         String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
         throw new IllegalStateException(msg);
            }
        }
        return args;
    }

   该方法是HandlerMethod中的方法,因此可以调用getMethodParameters()方法获取参数列表,然后遍历这些参数,分别用参数解析器来解析当前参数值,其中,argumentResolvers是HandlerMethodArgumentResolverComposite,概念讲解中已经阐述,它包含了所有的参数解析器的列表,以及参数类型和解析器的映射表,我们不妨看看到底什么怎么回事:

//是的,这个方法是HandlerAdapter中的方法,上一篇文章我们介绍了这是InitializingBean接口中的方法,
//会被自动调用
public void afterPropertiesSet() {
        if (this.argumentResolvers == null) {
            //调用下面的方法,获取所有某人参数解析器。
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            //这个是HandlerAdapterComposite实例。
            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);
        }
    }

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

   这下清晰了吧,RequestMappingHandlerAdapter实现了InitializingBean接口,因此Spring启动是会调用它的afterPropertySet方法,进行上述参数解析器的注册。然后在处理器方法调用过程中会遍历这些解析器找到支持当前参数的解析器并解析参数。Perfect。我们再回到之前的resolveArgument方法:

public Object resolveArgument(
            MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
            throws Exception {
        //调用下面的方法回去支持当前参数类型的解析器。
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        //调用该解析器的解析方法进行解析。
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
    //前面说过了,HandlerAdapterComposite会维护一个MethodParameter到解析器的映射关系。没错吧
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) {
                //判断解析器是否支持当前参数类型
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

   再往下就到某个解析器怎样解析具体参数了,我们知道就像上面图片中描述的,有大约一二十个解析器,我们不可能全部分析,这里我们只分析其中常见的一个:@PathVariable注解的解析器。

public boolean supportsParameter(MethodParameter parameter) {
        //如果该参数没有被@PathVariable注解标记,则返回false,不支持
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            return false;
        }
        //如果该参数是Map类型的。则判断@PathVariable是否设置了value属性
        if (Map.class.isAssignableFrom(parameter.getParameterType())) {
            String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
            return StringUtils.hasText(paramName);
        }
        return true;
    }

   从上面代码我们知道PathVariableMethodArgumentResolver支持被@PathVariable注解的参数。下面我们看它怎样解析参数值得:

public final Object resolveArgument(
            MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
            throws Exception {
        //获取参数类型
        Class<?> paramType = parameter.getParameterType();
        //获取参数的名-值信息,如@PathVariable("NO") 则NO为名称,值为请求路径中对应
        //@RequestMapping("/work/produce/NO")中NO的值。
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        //解析请求路径中对应名称的值。上例中NO对应的值。
        Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveDefaultValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required) {
                handleMissingValue(namedValueInfo.name, parameter);
            }
            arg = handleNullValue(namedValueInfo.name, arg, paramType);
        }//DataBinder,后续讲解。
        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, 
                                                                    namedValueInfo.name);
            arg = binder.convertIfNecessary(arg, paramType, parameter);
        }
        //将上面解析到的名称和值放到Request的属性表中。
        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }

   上面代码首先获取参数的@PathVariable的value属性值,如果value是空,则将参数的名称作为NameValueInfo的name值,然后用这个name值匹配请求路径中的变量值,作为NameValueInfo的value值。

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
        Map<String, String> uriTemplateVars =//从请求的属性表中获取值
            (Map<String, String>) request.getAttribute(
             HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null;
    }

   上面再lookupHandlerMethod方法中,调用了handleMatch方法,我们说了它会将解析到的路径变量放到request的变量表中,这不,这里就用到了,这样我们就获取到了@PathVariable对应参数的值了。到此为止,@PathVariable参数的解析就算完成了,其他类型的参数解析思路一样不同的就是resolveArgument方法中的逻辑了,大家可以自行了解。

注:这里Spring用到了多种设计模式,包括组合模式,策略模式,适配器模式等。其实这些实现都是3.1v的,之前的版本,参数解析这块相当乱,几乎完全在一个方法内实现的,拓展性,维护性相当差,3.1后我们可以很轻松的实现自己的参数解析器等,真的很棒。

真的很累了。。。。。。

四、返回值的处理

    上一节我们分析了参数的解析,及方法的调用,下面我们再来看返回值的处理,回到HandlerMethod的invokeAndHandle方法来,该方法的最后调用了returnValueHandlers.handleReturenValue方法,其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite实例,就像HandlermethodArgumentResolverComposite一样,它包含了所有HandlerMethodReturnValueHandler的列表,并在Spring启动时完成注册。

public void handleReturnValue(
            Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws Exception {

        HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
        for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
            if (returnValueHandler.supportsReturnType(returnType)) {
                return returnValueHandler;
            }
        }
        return null;
}
//这个是处理String类型的返回值,即将返回值解析为视图名------ViewNameMethodReturnValueHandler
public void handleReturnValue(
            Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws Exception {
        if (returnValue == null) {
            return;
        }
        else if (returnValue instanceof String) {
            String viewName = (String) returnValue;
            //将返回值存储为视图名
            mavContainer.setViewName(viewName);
            if (isRedirectViewName(viewName)) {
                mavContainer.setRedirectModelScenario(true);
            }
        }
        else {
            throw new UnsupportedOperationException("Unexpected return type: ");
        }
    }

   返回值的处理思路与参数的处理几乎一样了,根据不同的返回值类型,查找匹配的处理器,然后进行处理(主要就是设置Model和View了,如上面的代码将返回值解析为视图名),这里就不多说了。返回值处理完了,剩下的就是将返回值响应给客户端了,再往下就是视图的解析了,也就是我们下篇文章的主题了。

欢迎大家评论,交流。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Spring MVC 解读——@RequestMapping (1)
SpringMVC解读——@RequestMapping    为了降低文章篇幅,使得文章更目标化,简洁化,我们就不例举各种@RequestMapping的用法等内容了.文章主要说明以下问题:1.Spring怎样处理@RequestMapping(怎样将请求路径映射到控制器类
Easter79 Easter79
3年前
SpringMVC学习(二)@Requestmapping映射和Rest风格
1、@RequestMapping1.1、@RequestMapping映射请求注解在SpringMVC中使用@RequestMapping注解可以为控制器指定处理哪些URL请求可以用于类上或者方法上类定义处:提供初步的请求映射信息。相对于WEB应用
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这