3

Spring MVC组件之HandlerMapping - yaoxtao

 2 years ago
source link: https://www.cnblogs.com/YaoxTao/p/16583805.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring MVC组件之HandlerMapping

HandlerMapping概述

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

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

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

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

 374540-20220813185319818-933612869.png

HandlerMapping类图

 

374540-20220813185327560-1466675356.png

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

AbstractHandlerMapping

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

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

    @Override

    @Nullable

    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 = obtainApplicationContext().getBean(handlerName);

    }

 

        // Ensure presence of cached lookupPath for interceptors and others

        if (!ServletRequestPathUtils.hasCachedPath(request)) {

           initLookupPath(request);

        }

 

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

 

        if (logger.isTraceEnabled()) {

           logger.trace("Mapped to " + handler);

        }

        else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {

           logger.debug("Mapped to " + executionChain.getHandler());

        }

 

        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {

           CorsConfiguration config = getCorsConfiguration(handler, request);

           if (getCorsConfigurationSource() != null) {

               CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);

               config = (globalConfig != null ? globalConfig.combine(config) : config);

           }

           if (config != null) {

               config.validateAllowCredentials();

           }

           executionChain = getCorsHandlerExecutionChain(request, executionChain, config);

        }

 

        return executionChain;

}

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

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

    @Override

    protected void initApplicationContext() throws BeansException {

        extendInterceptors(this.interceptors);

        detectMappedInterceptors(this.adaptedInterceptors);

        initInterceptors();

    }

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

1. extendInterceptors(this.interceptors)

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

2.detectMappedInterceptors(this.adaptedInterceptors)

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

3.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方法具体代码如下图所示。

    @Override

    public void afterPropertiesSet() {

        initHandlerMethods();

    }

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

    protected void initHandlerMethods() {

        for (String beanName : getCandidateBeanNames()) {

           if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {

               processCandidateBean(beanName);

           }

        }

        handlerMethodsInitialized(getHandlerMethods());

    }

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

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

  1. 通过Bean的名称找到Bean对应的类型。
  2. 通过isHandler方法,过滤掉不符合条件的Bean。isHandler方法是一个模板方法,具体逻辑是在子类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方法。

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {

        String lookupPath = initLookupPath(request);

        this.mappingRegistry.acquireReadLock();

        try {

           HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

           return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);

        }

        finally {

           this.mappingRegistry.releaseReadLock();

        }

    }

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

  1. 在initLookupPath方法中,把一个request转换成一个url.
  2. 再通过request和lookupPath 这两个参数,使用lookupHandlerMethod方法,找到相应的HandlerMethod。
  3. 在找到HandlerMethod对象的情况下,会调用HandlerMethod的createWithResolvedBean方法。该方法会判断这个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接口。接口RequestCondition是Spring 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中。

    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {

        Assert.notNull(urlPath, "URL path must not be null");

        Assert.notNull(handler, "Handler object must not be null");

        Object resolvedHandler = handler;

 

        // Eagerly resolve handler if referencing singleton via name.

        if (!this.lazyInitHandlers && handler instanceof String) {

           String handlerName = (String) handler;

           ApplicationContext applicationContext = obtainApplicationContext();

           if (applicationContext.isSingleton(handlerName)) {

               resolvedHandler = applicationContext.getBean(handlerName);

           }

        }

 

        Object mappedHandler = this.handlerMap.get(urlPath);

        if (mappedHandler != null) {

           if (mappedHandler != resolvedHandler) {

               throw new IllegalStateException(

                       "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +

                       "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");

           }

        }

        else {

           if (urlPath.equals("/")) {

               if (logger.isTraceEnabled()) {

                   logger.trace("Root mapping to " + getHandlerDescription(handler));

               }

               setRootHandler(resolvedHandler);

           }

           else if (urlPath.equals("/*")) {

               if (logger.isTraceEnabled()) {

                   logger.trace("Default mapping to " + getHandlerDescription(handler));

               }

               setDefaultHandler(resolvedHandler);

           }

           else {

               this.handlerMap.put(urlPath, resolvedHandler);

               if (getPatternParser() != null) {

                  this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);

               }

               if (logger.isTraceEnabled()) {

                   logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));

               }

           }

        }

    }

在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方法中。

    protected Object getHandlerInternal(HttpServletRequest request) throws Exception {

        String lookupPath = initLookupPath(request);

        Object handler;

        if (usesPathPatterns()) {

           RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);

           handler = lookupHandler(path, lookupPath, request);

        }

        else {

           handler = lookupHandler(lookupPath, request);

        }

        if (handler == null) {

           // We need to care for the default handler directly, since we need to

           // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.

           Object rawHandler = null;

           if (StringUtils.matchesCharacter(lookupPath, '/')) {

               rawHandler = getRootHandler();

           }

           if (rawHandler == null) {

               rawHandler = getDefaultHandler();

           }

           if (rawHandler != null) {

               // Bean name or resolved handler?

               if (rawHandler instanceof String) {

                   String handlerName = (String) rawHandler;

                   rawHandler = obtainApplicationContext().getBean(handlerName);

               }

               validateHandler(rawHandler, request);

               handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);

           }

        }

        return handler;

}

在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()方法。

protected void detectHandlers() throws BeansException {

        ApplicationContext applicationContext = obtainApplicationContext();

        if (logger.isDebugEnabled()) {

           logger.debug("Looking for URL mappings in application context: " + applicationContext);

        }

        String[] beanNames = (this.detectHandlersInAncestorContexts ?

           BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :

               applicationContext.getBeanNamesForType(Object.class));

 

        // Take any bean name that we can determine URLs for.

        for (String beanName : beanNames) {

           String[] urls = determineUrlsForHandler(beanName);

           if (!ObjectUtils.isEmpty(urls)) {

               // URL paths found: Let's consider it a handler.

               registerHandler(urls, beanName);

           }

           else {

               if (logger.isDebugEnabled()) {

                   logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");

               }

           }

        }

}

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

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

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<String, Object>类型的urlMap属性。我们可以把url和对应的HandlerMapping的放到这个属性中。SimpleUrlHandlerMapping类会在registerHandlers(Map<String, Object> urlMap)方法中,遍历这个urlMap,再调用AbstractUrlHandlerMapping父类的registerHandler(String urlPath, Object handler)方法,对url和HandlerMapping进行注册。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK