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类中,具体代码如下:

    @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属性的注册逻辑。

Spring MVC组件之HandlerMapping的更多相关文章

  1. Spring MVC中的HandlerMapping与HandlerAdapter

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  2. Spring MVC组件之HandlerAdapter

    Spring MVC组件之HandlerAdapter HandlerAdapter概述 HandlerAdapter组件是一个处理器Handler的适配器.HandlerAdapter组件的主要作用 ...

  3. Spring MVC(2)Spring MVC 组件开发

    一.控制器接收各类请求参数 代码测试环境: 接收各类参数的控制器--ParamsController package com.ssm.chapter15.controller; @Controller ...

  4. spring mvc 关键接口 HandlerMapping HandlerAdapter

    HandlerMapping Spring mvc 使用HandlerMapping来找到并保存url请求和处理函数间的mapping关系.   以DefaultAnnotationHandlerMa ...

  5. Spring MVC HandlerMapping

    http://www.cnblogs.com/tengyunhao/p/7658952.html http://www.cnblogs.com/tengyunhao/p/7518481.html Sp ...

  6. Java之Spring mvc详解

    文章大纲 一.Spring mvc介绍二.Spring mvc代码实战三.项目源码下载四.参考文章   一.Spring mvc介绍 1. 什么是springmvc   springmvc是sprin ...

  7. Spring MVC(1)Spring MVC的初始化和流程以及SSM的实现

    一.Spring MVC概述 1.Spring MVC 的架构 对于持久层而言,随着软件的发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足迁移数据库的要求.与此 ...

  8. Spring MVC 设计概述

      MVC设计的根本原因在于解耦各个模块 Spring MVC的架构 对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求. ...

  9. Spring MVC(一)--SpringMVC的初始化和流程

    SpringMVC是Spring提供给WEB应用的MVC框架,MVC框架一般来说由三部分组成: Model:模型层,一般由java bean完成,主要是进行数据库操作: View:视图层,用于前端展示 ...

随机推荐

  1. CF1601F Two Sorts

    CF1601F Two Sorts 给定 \(n\),将 \(1\sim n\) 按照字典序排序,\(a_i\) 表示第 \(i\) 小的数,求: \[\left(\sum_{i=1}^{n} ((i ...

  2. git bisect:让你闭眼都能定位疑难 bug的利器

    摘要:git bisect命令使用二分搜索算法来查找提交历史中的哪一次提交引入了错误.它几乎能让你闭着眼睛快速定位任何源码导致的问题,非常实用. 本文分享自华为云社区<利用好 git bisec ...

  3. mysql 超时配置

    日志报错: No operations allowed after connection closed.; nested exception is com.mysql.jdbc.exceptions. ...

  4. csv.reader(f)和f.readlines()、追加数据

    假如某个文档f中存储如下内容: 你好,中国. 1,2,3,4 共两行内容. 当你使用csv.reader(f),则会存储为如下形式: [['你','好','中','国'] ['1','2','3',' ...

  5. Ubuntu Linux处理Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 3365 (unattended-upgr)问题

    问题 在Ubuntu中,执行apt install后,出现以下问题: Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-fro ...

  6. BUUCTF-被嗅探的流量

    被嗅探的流量 提示告知是文件传输的流量,那进去过滤http流量包即可,找到一个upload目录的,并且是post方式即可,追踪http流即可发现flag

  7. python——5行代码采集3000+上市公司信息

    毕业季也到了找工作的季节了,很多小伙伴都会一家一家的公司去看,这得多浪费时间啊.今天用Python教大家怎么采集公司的信息,相信大家会很喜欢这个教程的,nice! 基本环境配置 版本:Python3 ...

  8. Python简单实现自动评论、自动点赞、自动关注脚本

    一些哔哔: 今天的这个脚本,是一个别人发的外包,交互界面的代码就不在这里说了,但是可以分享下自动评论.自动点赞.自动关注.采集评论和视频的数据是如何实现的 开发环境 python 3.8 运行代码py ...

  9. docker安装报错failure: repodata/repomd.xml from mirrors.aliyun.com_docker-ce_linux_centos_docker-ce.pro

    1.进入 /etc/yum.repos.d 目录下,将所有有关 docker 的 repo 全部删掉 2.重新添加镜像 sudo yum-config-manager --add-repo https ...

  10. CMU15445 (Fall 2019) 之 Project#1 - Buffer Pool 详解

    前言 这个实验有两个任务:时钟替换算法和缓冲池管理器,分别对应 ClockReplacer 和 BufferPoolManager 类,BufferPoolManager 会用 ClockReplac ...