• Spring MVC组件之HandlerMapping


    Spring MVC组件之HandlerMapping

    HandlerMapping概述

    HandlerMapping组件的作用解析一个个Request请求,并找到相应处理这个Request的Handler。Handler一般可以理解为Controller控制器里的一个方法。

    HandlerMapping组件主要做了两件事件。

    1. 在组件初始化时,会把Request请求和对应的Handler进行注册,其实就是把Request和对应的Handler以键值对的形式存在一个map中。
    2. 解析一个个Request请求,在注册的map中找相应的handler。

    SpringMvc的源码中,HandlerMapping定义为一个接口。接口除了定义几个属性字段,只定义了一个getHandler方法。

    HandlerMapping类图

    从以上类图中可以看出,HandlerMapping组件主要是分了两个系列。一个系列主要继承至AbstractHandlerMethodMapping。另一个系列主要继承至AbstractUrlHandlerMapping。而AbstractHandlerMethodMapping和AbstractUrlHandlerMapping这两个抽象类又都是继承至AbstractHandlerMapping。

    AbstractHandlerMapping

    AbstractHandlerMapping是一个抽象类,它实现了HandlerMapping接口。AbstractHandlerMapping是一个非常基础的类,HandlerMapping的所有子类系列都是继承自它。AbstractHandlerMapping采用了模板模式进行了整体的设计,各个子类通过覆写模板方法来实现相应功能。

    AbstractHandlerMappin抽象类既然继承了HandlerMapping接口,它肯定事要实现getHandler方法。在AbstractHandlerMappin类中,具体代码如下:

    1. @Override
    2.     @Nullable
    3.     public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    4.         Object handler = getHandlerInternal(request);
    5.         if (handler == null) {
    6.            handler = getDefaultHandler();
    7.         }
    8.         if (handler == null) {
    9.            return null;
    10.         }
    11.         // Bean name or resolved handler?
    12.         if (handler instanceof String) {
    13.            String handlerName = (String) handler;
    14.            handler = obtainApplicationContext().getBean(handlerName);
    15.     }
    16.         // Ensure presence of cached lookupPath for interceptors and others
    17.         if (!ServletRequestPathUtils.hasCachedPath(request)) {
    18.            initLookupPath(request);
    19.         }
    20.         HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    21.         if (logger.isTraceEnabled()) {
    22.            logger.trace("Mapped to " + handler);
    23.         }
    24.         else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
    25.            logger.debug("Mapped to " + executionChain.getHandler());
    26.         }
    27.         if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
    28.            CorsConfiguration config = getCorsConfiguration(handler, request);
    29.            if (getCorsConfigurationSource() != null) {
    30.                CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
    31.                config = (globalConfig != null ? globalConfig.combine(config) : config);
    32.            }
    33.            if (config != null) {
    34.                config.validateAllowCredentials();
    35.            }
    36.            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    37.         }
    38.         return executionChain;
    39. }

    getHandler方法中要特别注意getHandlerInternal(request)方法,该方法是一个模板方法。在AbstractHandlerMapping类中,getHandlerInternal(request)方法只是一个抽象的方法,没有做任何的事情。该方法专门预留给AbstractHandlerMapping的子类来覆写,从而实现自己的业务逻辑。

    AbstractHandlerMapping还继承自WebApplicationObjectSupport类,并重写了该父类的initApplicationContext方法。

       

    1. @Override
    2.     protected void initApplicationContext() throws BeansException {
    3.         extendInterceptors(this.interceptors);
    4.         detectMappedInterceptors(this.adaptedInterceptors);
    5.         initInterceptors();
    6.     }

    在initApplicationContext方法,定义了三个方法。

    • extendInterceptors(this.interceptors)

    extendInterceptors是一个模板方法,给子类提供了一个修改this.interceptors拦截器的入口。

    • detectMappedInterceptors(this.adaptedInterceptors)

    detectMappedInterceptors方法是将Spring MVC中所有MappedInterceptor类型的bean,添加到this.adaptedInterceptors的集合中。

    • initInterceptors()

    initInterceptors是初始化拦截器,将所有的this.interceptors集合中的拦截器包装后,添加到this.adaptedInterceptors的集合中。

    AbstractHandlerMethodMapping系列

    AbstractHandlerMethodMapping

    AbstractHandlerMethodMapping是一个非常重要的类,AbstractHandlerMethodMapping除了继承AbstractHandlerMapping抽象类,它还实现了InitializingBean接口。

    Handler的注册

    在Spring中如果一个类实现了InitializingBean接口,Spring容器就会在实例化该Bean时,会调用Bean的afterPropertiesSet方法。

    AbstractHandlerMethodMapping类中,在覆写InitializingBean接口的afterPropertiesSet方法时,完成了初始化注册的工作。这也是HandlerMapping组件的第一步工作,把Request请求和对应的Handler先进行注册。

    AbstractHandlerMethodMapping类的afterPropertiesSet方法具体代码如下图所示。

    1.     @Override
    2.     public void afterPropertiesSet() {
    3.         initHandlerMethods();
    4.     }

    可以看出在AbstractHandlerMethodMapping类的afterPropertiesSet方法中,调用了initHandlerMethods()方法。看initHandlerMethods()方法的名称,就知道它其实是完成了初始化的工作。

       

    1. protected void initHandlerMethods() {
    2.         for (String beanName : getCandidateBeanNames()) {
    3.            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    4.                processCandidateBean(beanName);
    5.            }
    6.         }
    7.         handlerMethodsInitialized(getHandlerMethods());
    8.     }

    initHandlerMethods()方法中,通过getCandidateBeanNames方法,先获取Spring容器中所有的Bean的名称。过滤掉名称以 scopedTarget.打头的Bean。通过循环再Bean的名称传入processCandidateBean(beanName)方法。

    processCandidateBean(beanName)方法主要做了三件事情。

    1. 通过Bean的名称找到Bean对应的类型。
    2. 通过isHandler方法,过滤掉不符合条件的BeanisHandler方法是一个模板方法,具体逻辑是在子类RequestMappingHandlerMapping中实现的。isHandler方法只会选择含有@Controller@RequestMapping注解的bean
    3. 通过detectHandlerMethods方法,建立request请求和handler之间的对应映射关系。

    detectHandlerMethods方法

    detectHandlerMethods方法主要做了两件事情。

    1. 使用getMappingForMethod方法,通过handler找到有@RequestMapping注解的方法。在AbstractHandlerMethodMapping类中,getMappingForMethod方法在只是一个抽象方法,具体实现是在子类RequestMappingHandlerMapping类中,实现具体的业务逻辑。
    2. 使用registerHandlerMethod方法,将找到的方法进行注册。所谓注册其实就是将找到的HandlerMothed放到一个Map中。如果同一个HandlerMothed进行第二次注册,就会抛出异常。

    Handler的查找

    在AbstractHandlerMethodMapping类中,覆写了父类AbstractHandlerMapping的模板方法getHandlerInternal方法。

       

    1. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    2.         String lookupPath = initLookupPath(request);
    3.         this.mappingRegistry.acquireReadLock();
    4.         try {
    5.            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    6.            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    7.         }
    8.         finally {
    9.            this.mappingRegistry.releaseReadLock();
    10.         }
    11.     }

    在getHandlerInternal方法中,主要做了三件事件。

    1. initLookupPath方法中,把一个request转换成一个url.
    2. 再通过requestlookupPath 这两个参数,使用lookupHandlerMethod方法,找到相应的HandlerMethod
    3. 在找到HandlerMethod对象的情况下,会调用HandlerMethodcreateWithResolvedBean方法。该方法会判断这个HandlerMethod对象。是不是String 类型的。如果是String 类型则说明他只是一个Bean的名称,会根据这个Bean的名称在Spring容器中找到该Bean。根据这个Bean,会创建一个新的HandlerMethod对像返回。

    RequestMappingInfoHandlerMapping

    RequestMappingInfoHandlerMapping类主要是重写了父类的getMatchingMapping方法。getMatchingMapping方法会根据当前的请求,返回一个匹配了各种RequestCondition的RequestMappingInfo对象。

    SpringMVC会根据这个RequestMappingInfo对象来获取对应的Handler

    RequestMappingHandlerMapping

    Spring MVC容器在初始化HandlerMapping类型的组件时,最终初始化的AbstractHandlerMethodMapping系列组件,就是RequestMappingHandlerMapping

    RequestMappingHandlerMapping主要是重写了父类的三个方法。

    1. afterPropertiesSet方法

    重写了父类的初始化方法,在afterPropertiesSet方法中,创建了一个BuilderConfiguration类型的对象。然后对BuilderConfiguration对象,进行了属性的设置。

    1. isHandler方法

        主要是用于判断获取何种类型的Handler。对Handler起到一个过滤的作用,只取@Controller@RequestMapping两种注解类型的Handler

    1. getMappingForMethod方法

    getMappingForMethod方法主要是通过method来创建相应的RequestMappingInfo对象。程序先从method对象上获取RequestMapping注解。再从RequestMapping注解的信息,来创建路径匹配头部匹配请求参数匹配可产生MIME匹配可消费MIME匹配请求方法匹配”,RequestCondition接口的实例。最后组合这些RequestCondition接口实例,来创建一个RequestMappingInfo对象。SpringMVC会根据RequestMappingInfo对象来注册请求和Handler的映射关系。

    RequestMappingInfo对象实现了RequestCondition接口。接口RequestConditionSpring MVC对一个请求匹配条件的概念建模。

    AbstractUrlHandlerMapping系列

    AbstractUrlHandlerMapping系列从名字就可以看出,主要是处理url和handler的关系。AbstractUrlHandlerMapping类先将url和handler的映射关系存在一个Map。再通过url来获取相应的handler。

    AbstractUrlHandlerMapping

    AbstractUrlHandlerMapping类跟AbstractHandlerMethodMapping类一样,也继承了AbstractHandlerMapping这个抽象类。所有url跟HandlerMapping相关系列的子类,都是继承至AbstractUrlHandlerMapping这个父类。

    AbstractUrlHandlerMapping同时也实现了MatchableHandlerMapping的接口。MatchableHandlerMapping接口定义了一个用于匹配的match方法。

    HandlerMap的注册

    在AbstractUrlHandlerMapping类中,registerHandler这个方法是专门负责对handler进行注册的。Handler的注册,其实就是把url和对应的handler存储到handlerMap这个哈希map中。

       

    1. protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    2.         Assert.notNull(urlPath, "URL path must not be null");
    3.         Assert.notNull(handler, "Handler object must not be null");
    4.         Object resolvedHandler = handler;
    5.         // Eagerly resolve handler if referencing singleton via name.
    6.         if (!this.lazyInitHandlers && handler instanceof String) {
    7.            String handlerName = (String) handler;
    8.            ApplicationContext applicationContext = obtainApplicationContext();
    9.            if (applicationContext.isSingleton(handlerName)) {
    10.                resolvedHandler = applicationContext.getBean(handlerName);
    11.            }
    12.         }
    13.         Object mappedHandler = this.handlerMap.get(urlPath);
    14.         if (mappedHandler != null) {
    15.            if (mappedHandler != resolvedHandler) {
    16.                throw new IllegalStateException(
    17.                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
    18.                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
    19.            }
    20.         }
    21.         else {
    22.            if (urlPath.equals("/")) {
    23.                if (logger.isTraceEnabled()) {
    24.                    logger.trace("Root mapping to " + getHandlerDescription(handler));
    25.                }
    26.                setRootHandler(resolvedHandler);
    27.            }
    28.            else if (urlPath.equals("/*")) {
    29.                if (logger.isTraceEnabled()) {
    30.                    logger.trace("Default mapping to " + getHandlerDescription(handler));
    31.                }
    32.                setDefaultHandler(resolvedHandler);
    33.            }
    34.            else {
    35.                this.handlerMap.put(urlPath, resolvedHandler);
    36.                if (getPatternParser() != null) {
    37.                   this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
    38.                }
    39.                if (logger.isTraceEnabled()) {
    40.                    logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
    41.                }
    42.            }
    43.         }
    44.     }

    在registerHandler方法中,主要做了4件事情。

    1. 如果handler不是懒加载的,且handler是字符串类型的。此时就把handler作为一个BeanName,在Spring 容器中获取这个handler的bean对象。
    2. 对urlPath进行了校验,如果一个urlPath对应了多个不同的handler,代码就会抛出异常。
    3. 对特殊的urlPath进行了单独的处理,对”/”,”/*”分别调用了setRootHandler方法和setDefaultHandler方法进行了特殊的处理。
    4. 在handlerMap中记录url和handler的对应关系。通过this.handlerMap.put(urlPath, resolvedHandler)这句代码,把url和handler通过键值对的方式存储到hash散列中。

    Handler的查找

    在AbstractUrlHandlerMapping类中,具体实现了如何从一个url来获取相应的的handler。AbstractUrlHandlerMapping类中,重写了getHandlerInternal方法。通过url来获取handler的逻辑,就写在getHandlerInternal方法中。

      

    1.   protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    2.         String lookupPath = initLookupPath(request);
    3.         Object handler;
    4.         if (usesPathPatterns()) {
    5.            RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
    6.            handler = lookupHandler(path, lookupPath, request);
    7.         }
    8.         else {
    9.            handler = lookupHandler(lookupPath, request);
    10.         }
    11.         if (handler == null) {
    12.            // We need to care for the default handler directly, since we need to
    13.            // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
    14.            Object rawHandler = null;
    15.            if (StringUtils.matchesCharacter(lookupPath, '/')) {
    16.                rawHandler = getRootHandler();
    17.            }
    18.            if (rawHandler == null) {
    19.                rawHandler = getDefaultHandler();
    20.            }
    21.            if (rawHandler != null) {
    22.                // Bean name or resolved handler?
    23.                if (rawHandler instanceof String) {
    24.                    String handlerName = (String) rawHandler;
    25.                    rawHandler = obtainApplicationContext().getBean(handlerName);
    26.                }
    27.                validateHandler(rawHandler, request);
    28.                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
    29.            }
    30.         }
    31.         return handler;
    32. }

    getHandlerInternal方法中,程序会先根据request获取到一个url。再通过lookupHandler方法来获取相应的Handler。

    lookupHandler方法

    lookupHandler从方法名称就可以知道,就是通过url来查找对应的Handler。lookupHandler首先会从handlerMap中直接获取。若找到了Handler,则会直接返回。

    若不能直接从handlerMap中获取,则会使用PathPattern进行模式匹配。如果一个url匹配了多个PathPattern,会对多个PathPattern进行排序,取最优的一个。

    获取到了PathPattern后,通过PathPattern从pathPatternHandlerMap获取Handler。如果Handler为String类型,那么这个Handler是Handle Bean的名称。再根据这个BeanName在Spring容器中找到相应的Bean。

    获取到Handler后,会使用validateHandler对这个Handler进行校验。validateHandler是一个模板方法,主要留给子类进行扩展实现。

    最后会使用buildPathExposingHandler方法,为这个Handler增加一些拦截器。

    buildPathExposingHandler

    buildPathExposingHandler方法主要是给Handler注册了两个内部的拦截器。他们分别是PathExposingHandlerInterceptor和UriTemplateVariablesHandlerInterceptor拦截器。

    AbstractDetectingUrlHandlerMapping

    在AbstractDetectingUrlHandlerMapping类中,主要是重写了父类的initApplicationContext()方法。在initApplicationContext()方法中,调用了detectHandlers()方法。

       

    1. protected void detectHandlers() throws BeansException {
    2.         ApplicationContext applicationContext = obtainApplicationContext();
    3.         if (logger.isDebugEnabled()) {
    4.            logger.debug("Looking for URL mappings in application context: " + applicationContext);
    5.         }
    6.         String[] beanNames = (this.detectHandlersInAncestorContexts ?
    7.            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
    8.                applicationContext.getBeanNamesForType(Object.class));
    9.         // Take any bean name that we can determine URLs for.
    10.         for (String beanName : beanNames) {
    11.            String[] urls = determineUrlsForHandler(beanName);
    12.            if (!ObjectUtils.isEmpty(urls)) {
    13.                // URL paths found: Let's consider it a handler.
    14.                registerHandler(urls, beanName);
    15.            }
    16.            else {
    17.                if (logger.isDebugEnabled()) {
    18.                    logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
    19.                }
    20.            }
    21.         }
    22. }

    调用了detectHandlers()方法中,主要做了以下几个步骤。

    1. 获取Spring 容器中所有bean的名称。
    2. 循环遍历所有bean的名称,对每一个beanName调用determineUrlsForHandler方法,获取这个beanName对应的url
    3. 再调用父类的registerHandler(urls, beanName)方法,对urlhandler进行注册。

    determineUrlsForHandler(beanName)方法AbstractDetectingUrlHandlerMapping类中,只是一个虚方法,专门留给子类来具体实现。

    BeanNameUrlHandlerMapping

    Spring MVC容器在初始化HandlerMapping类型的组件时,默认初始化AbstractUrlHandlerMapping系列的组件时,初始化的就是BeanNameUrlHandlerMapping组件。

    BeanNameUrlHandlerMapping类继承至AbstractDetectingUrlHandlerMapping这个父类,子类中主要是重写了determineUrlsForHandler方法。determineUrlsForHandler方法中,主要是筛选了Bean的名称或者Bean的别名以“/”斜杠开头的Bean。最后会把Bean的名称和对应的Bean对象注册到handlerMap这个HashMap对象中。

    SimpleUrlHandlerMapping

       SimpleUrlHandlerMapping类继承至AbstractUrlHandlerMapping这个父类。SimpleUrlHandlerMapping类中有一个Map类型的urlMap属性。我们可以把url和对应的HandlerMapping的放到这个属性中。SimpleUrlHandlerMapping类会在registerHandlers(Map urlMap)方法中,遍历这个urlMap,再调用AbstractUrlHandlerMapping父类的registerHandler(String urlPath, Object handler)方法,对urlHandlerMapping进行注册

        除了Map类型的urlMap属性。SimpleUrlHandlerMapping类中也提供了使用Properties来进行url的注册。通过setMappings(Properties mappings)方法,SimpleUrlHandlerMapping会把mappings合并到urlMap属性中。再走urlMap属性的注册逻辑。

  • 相关阅读:
    ZZNUOJ_用C语言编写程序实现1510:A==B?(附完整源码)
    MIPI CSI-2笔记(21) -- JPEG8数据格式
    Linux系统之ip命令的基本使用
    交换机基本配置(switch)--华为-思科
    Spring Security 登录获取用户信息流程分析
    nginx的正向代理和反向代理以及负载均衡
    记录一次关于嵌套事务传播机制的bug
    Java 线程池、Thread类、创建线程的几种方式、Executor 框架、异步编排、completableFuture使用详解
    Unity 中使用foreach注意
    机械臂速成小指南(十六):带抛物线过渡的线性规划
  • 原文地址:https://blog.csdn.net/YaoXTao/article/details/126323145