美文网首页Spring 源码
SpringMVC源码关于HandlerMapping

SpringMVC源码关于HandlerMapping

作者: 代码potty | 来源:发表于2018-09-06 22:04 被阅读31次

    从HandlerMapping的注册与初始化问题开始讲
    因为是初学,我们根据springMVC的配置文件层层往下
    spring-web.xml的代码

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
               xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
                http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
        
        
            <!-- 开启注解 -->
            <!-- springmvc使用<mvc:annotation-driven> -->
            <!-- 自动加载RequestMappingHandlerMapping和RequestMappingHandlerAdapter, -->
            <!-- 可用在springmvc.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置。 -->
            <mvc:annotation-driven />
        
            <mvc:default-servlet-handler />
        
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
                <property name="suffix" value=".jsp" />
            </bean>
            
            <context:component-scan base-package="com.alipay.web" />
        </beans>
    

    在这个配置文件中有四个部分
    1、<mvc:annotation-driven />
    开启springMVC的注解模式

    2、<mvc:default-servlet-handler />
    REST风格的资源URL不希望带 .html 或 .do 等后缀,为了将静态资源的请求转由Web容器处理,使用这个方法。对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。也就是静态资源处理。

    3、配置页面的前置路径和后缀

    4、扫描项目中的web包

    而HandlerMapping的注册则跟第一个部分<mvc:annotation-driven />相关

    Spring是怎么解析<mvc:annotation-driven/>标签的?
    首先,必须要有一个继承自“org.springframework.beans.factory.xml.NamespaceHandlerSupport”的类,在其init方法中,注册自己的解析器,注册mvc解析器的类为MvcNamespaceHandler。这个类源码如下:

        public class MvcNamespaceHandler extends NamespaceHandlerSupport {
            public MvcNamespaceHandler() {
            }
        
            public void init() {
                this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
                this.registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
                this.registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
                this.registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
                this.registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
                this.registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
                this.registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
                this.registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
                this.registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
                this.registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
                this.registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
                this.registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
                this.registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
                this.registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
            }
        }
    

    从初始化方法中可以看到,对于annotation-driven有个解析器AnnotationDrivenBeanDefinitionParser。而这个解析器必须实现org.springframework.beans.factory.xml.BeanDefinitionParser接口。
    这个类一开始便设置了两个常量

        public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();
        public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();
    

    这两个常量分别是获取HandlerMapping和HandlerAdapter的类名

    解析器的主要代码如下:

        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
        parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
        parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
        parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
        //格式转换处理拦截类,比如时间、数字等
        parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
    
        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
    

    主要目的是注册与mvc处理有关的相关beans以及默认的mvc组件

    这边介绍一下HandlerMapping的

         //生成RequestMappingHandlerMapping组件对象
                RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
                handlerMappingDef.setSource(source);
                handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                //优先级设置为最高
                handlerMappingDef.getPropertyValues().add("order", 0);
        //添加contentNegotiationManager属性,处理media type        handlerMappingDef.getPropertyValues().add("contentNegotiationManager",      contentNegotiationManager);
                
                //查看mvc:annotation-driven有无enable-matrix-variables/enableMatrixVariables,表示是否开启多变量映射比如/cars;a=1;b=1
                //具体使用可查阅相关文档,默认removeSemicolonContent为false
                if (element.hasAttribute("enable-matrix-variables")) {
                    Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
                    handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
                }
                else if (element.hasAttribute("enableMatrixVariables")) {
                    Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
                    handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
                }
                //配置路径匹配解析器等属性
                configurePathMatchingProperties(handlerMappingDef, element, parserContext);
        //将RequestMappingHandlerMapping注册为bean对象放置bean工厂中       readerContext.getRegistry().registerBeanDefinition(     HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
    

    RequestMappingHandlerMapping主要是处理@Controller和@RequestMapping注解的,这个是<mvc:annotation-driven />标签默认的配置的,从代码可以看出来,这个标签同时配置了RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 两个bean。

    configurePathMatchingProperties配置路径匹配解析器用的,代码如下:

        private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef, Element element, ParserContext parserContext) {
        Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
        if (pathMatchingElement != null) {
            Object source = parserContext.extractSource(element);
            Boolean useRegisteredSuffixPatternMatch;
            if (pathMatchingElement.hasAttribute("suffix-pattern")) {
                useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("suffix-pattern"));
                handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useRegisteredSuffixPatternMatch);
            }
    
            if (pathMatchingElement.hasAttribute("trailing-slash")) {
                useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("trailing-slash"));
                handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useRegisteredSuffixPatternMatch);
            }
    
            if (pathMatchingElement.hasAttribute("registered-suffixes-only")) {
                useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("registered-suffixes-only"));
                handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch);
            }
    
            RuntimeBeanReference pathHelperRef = null;
            if (pathMatchingElement.hasAttribute("path-helper")) {
                pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper"));
            }
    
            pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, parserContext, source);
            handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef);
            RuntimeBeanReference pathMatcherRef = null;
            if (pathMatchingElement.hasAttribute("path-matcher")) {
                pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
            }
    
            pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, parserContext, source);
            handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
        }
    
    }
    

    看代码逻辑,主要是通过mvc:annotation-driven下的mvc:path-matching节点来配置

    1、suffix-pattern-是否启用后缀匹配,默认为true,即对指定的url会再新增.,比如/user实际匹配/user.->/user匹配/user.html,/user.jsp
    2、trailing-slash-是否启动尾部斜线匹配,默认为true,即对指定的url会新增/,比如/user也会匹配/user/
    3、registered-suffixes-only-是否启用media type类型的匹配,即对指定的url新增.json/.xml等匹配,默认为false
    4、path-helper-路径获取帮助类,默认为UrlPathHelper类
    5、path-matcher-路径匹配解析器,默认为AntPathMather

    此处完成了对于handlerMapping的注册以后,程序正常走,首先进入到DispatcherServlet进行初始化操作

        protected void initStrategies(ApplicationContext context) {
                initMultipartResolver(context);
                initLocaleResolver(context);
                initThemeResolver(context);
                initHandlerMappings(context);
                initHandlerAdapters(context);
                initHandlerExceptionResolvers(context);
                initRequestToViewNameTranslator(context);
                initViewResolvers(context);
                initFlashMapManager(context);
            }       
    

    其中的方法initHandlerMappings(context)的代码如下:

        private void initHandlerMappings(ApplicationContext context) {
                this.handlerMappings = null;
        
                if (this.detectAllHandlerMappings) {
                    // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
                    Map<String, HandlerMapping> matchingBeans =
                            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
                    if (!matchingBeans.isEmpty()) {
                        this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                        // We keep HandlerMappings in sorted order.
                        AnnotationAwareOrderComparator.sort(this.handlerMappings);
                    }
                }
                else {
                    try {
                        HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                        this.handlerMappings = Collections.singletonList(hm);
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        // Ignore, we'll add a default HandlerMapping later.
                    }
                }
        
                // Ensure we have at least one HandlerMapping, by registering
                // a default HandlerMapping if no other mappings are found.
                if (this.handlerMappings == null) {
                    this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
                    if (logger.isDebugEnabled()) {
                        logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
                    }
                }
            }       
    

    语句:detectAllHandlerMappings会判断是否默认添加所有的HandlerMappings,如果存在进行下一步,取出上文注册好的handlerMapping,如果不是,就从配置文件中搜索是否存在相关的bean进行初始化。当两种情况下都未能找到handlerMapping的时候,程序会通过getDefaultStrategies()方法给handlerMappings赋值一个默认的值,这个默认的值在DispatcherServlet.properties中,这个配置文件如下:

        # Default implementation classes for DispatcherServlet's strategy interfaces.
        # Used as fallback when no matching beans are found in the DispatcherServlet context.
        # Not meant to be customized by application developers.
        
        org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
        
        org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
        
        org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
            org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
        
        org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
            org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
            org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
        
        org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
            org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
            org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
        
        org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
        
        org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
        
        org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    

    到此完成了对于handlerMapping的初始化工作。

    总结
    1、通过spring-web.xml配置文件写入<mvc:annotation-driven />标签对mvc需要的组件进行注册,其中重要的部分有handlerMapping、handlerAdapter、interceptor、viewResolver等的注册。注册的类为MvcNamespaceHandler类,然后通过AnnotationDrivenBeanDefinitionParser类的parse()方法去解析标签。

    2、组件注册完后,进入DispatcherServlet类进行第二步,组件的初始化,这边介绍handlerMapping的初始化,判断是否默认添加所有的HandlerMappings,如果是,则取出所有注册好的HandlerMapping,否则去context里边找是否有相关的bean,然后取出来,如果前两步都无法初始化handlerMapping,那么程序会给handlerMapping初始化一个默认的值。

    3、初始化完成后,调用doDispatch()方法进行操作,首先通过handlerMapping.getHandler(request)的方法获取到handlerExcutionChain对象,然后通过getHandlerAdapter()获取handlerAdapter,传入handlerExcutionChain.getHandler()作为参数,之后调用handlerAdapter.handler()的方法获取ModelAndView,(包括模型数据、逻辑视图名),接着调用applyDefaultViewName(processedRequest, mv)方法获取view,再调用applyPostHandle(request,response,mv)方法,执行HandlerExecutionChain所有Interceptor中的postHandle方法,从这块代码看来,postHandle的执行时机是在执行完方法后没有把响应写入到response中执行的;最后调用 processDispatchResult(),处理请求,把参数都写入到request里面,进行渲染工作。

    参考链接:
    https://blog.csdn.net/sjjsh2/article/details/53100728
    https://www.cnblogs.com/question-sky/p/7090875.html
    https://blog.csdn.net/qq_38410730/article/details/79507465
    https://blog.csdn.net/wangbiao007/article/details/50510274
    https://www.cnblogs.com/davidwang456/p/4132215.html
    https://www.cnblogs.com/he-px/p/7133240.html
    https://blog.csdn.net/king_is_everyone/article/details/51446260

    相关文章

      网友评论

        本文标题:SpringMVC源码关于HandlerMapping

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