SpringMVC之源码分析--HandlerMapping(一)

猫耳山在天边 2019-06-27

概述

接口

HandlerMapping接口作用是将请求映射到处理程序,以及预处理和处理后的拦截器列表,映射是基于一些标准的,其中的细节因不同的实现而不相同。这是官方文档上一段描述,该接口只有一个方法getHandler(request),返回一个HandlerExecutionChain对象,接口本身很简单,源码如下:

public interface HandlerMapping {

    省略属性...

    // 返回请求的一个处理程序handler和拦截器interceptors
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

初始化

初始化HandlerMapping的入口方法是DispatcherServlet的initHandlerMappings(ApplicationContext context)方法,该方法源码如下:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    // detectAllHandlerMappings默认为true,可通过DispatcherServlet的init-param参数进行设置
    if (this.detectAllHandlerMappings) {
        // 在ApplicationContext中找到所有的handlerMapping,包括父上下文。
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 对handlerMapping排序,可通过指定order属性进行设置,order的值为int型,数越小优先级越高
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    // detectAllHandlerMappings=false时
    else { 
        try {
            // 从ApplicationContext上下文中取id(或name)="handlerMapping"的bean
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            // 将hm转换成list,并赋值给属性handlerMappings
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // 从context上下文中定义HandlerMapping时,Spring MVC将使用默认HandlerMapping,默认的HandlerMapping在DispatcherServlet.properties属性文件中定义,
    // 该文件是在DispatcherServlet的static静态代码块中加载的
    // 默认的是:BeanNameUrlHandlerMapping和RequestMappingHandlerMapping
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

至此,我们基本分析了HandlerMapping的初始化过程,接下来针对分析中提到的点进行验证,以检验我们的分析是正确的。

实战

  • 项目结构

SpringMVC之源码分析--HandlerMapping(一)

  • 代码分析

    • pom配置文件

      引入Spring MVC支持,代码如下:

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.0.5.RELEASE</version>
      </dependency>
    • spring配置文件

      spring-servlet.xml是Spring MVC的配置文件,在验证过程中会修改此文件内容,内容根据验证目的会有改变,具体参见验证场景的配置。

    • web配置文件

      web.xml是部署描述文件,主要配置了Spring MVC的DispatcherServlet,代码如下:

      <servlet>
          <!-- Servlet名称,可任意定义,但必须与servlet-mapping中对应 -->
          <servlet-name>dispatcher</servlet-name>
          <!-- 指定Spring MVC核心控制类,即J2EE规范中的前端控制器(Front Controller) -->
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!-- 指定Spring MVC配置文件,默认在WEB-INF目录下,切名字为[servlet-name]-servlet.xml,此文件中配置web相关内容,比如:指定扫描Controller路径、配置逻辑视图前缀后缀、上传文件等等 -->
          <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:spring-servlet.xml</param-value>
          </init-param>
          <!-- 此配置的值为正整数时,表示容器启动时初始化,即调用Servlet的init方法 -->
          <load-on-startup>1</load-on-startup>
          <async-supported>true</async-supported>
      </servlet>
      <!-- 定义servlet映射 -->
      <servlet-mapping>
          <!-- 与servlet中servlet-name对应 -->
          <servlet-name>dispatcher</servlet-name>
          <!-- 映射所有的url -->
          <url-pattern>/</url-pattern>
      </servlet-mapping>
    • Controller控制器

      本例使用的是SimpleUrlHandlerMapping映射处理器,所以我们的Controller会继承org.springframework.web.servlet.mvc.Controller类,并实现handlerRequest方法,代码如下:

      public class DemoController implements Controller{
      
          @Nullable
          @Override
          public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
              request.getServletContext().log("进入Controller(Handler)处理器。。。");
              return null;
          }
      }
  • 验证

    • 默认映射处理器

      在我们没有对HandlerMapping进行配置时,Spring会使用默认的HandlerMapping策略,此时我们的Spring配置文件(spring-servlet.xml)没有任何内容,此时我们启动应用,通过断点方式验证,结果如下:

      SpringMVC之源码分析--HandlerMapping(一)

      Spring MVC默认配置为:

      SpringMVC之源码分析--HandlerMapping(一)

      从结果可知,在未进行任何配置HandlerMapping时,系统使用(支持)默认的BeanNameUrlHandlerMapping和RequestMappingHandlerMapping映射解析器。

    • detectAllHandlerMappings

      该参数是boolean类型,作用是检查所有的HandlerMappings映射解析器或使用id或name为"handlerMappping"的bean,默认为true,即从context上下文中检查所有的HandlerMapping。

      我们先通过修改此参数为false,即在web.xml配置DispatcherServlet时,设置init-param参数,代码如下:

      <!-- 使用id或name为handlerMapping的映射处理器 -->
      <init-param>
          <param-name>detectAllHandlerMappings</param-name>
          <param-value>false</param-value>
      </init-param>

      在Spring配置文件spring-servlet.xml中定义映射处理器,以SimpleUrlHandlerMapping为例,代码如下:

      <!-- 定义映射处理器 -->
      <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
          <property name="urlMap">
              <props>
                  <prop key="/demo">demoController</prop>
              </props>
          </property>
          <!-- 设置顺序,在多个映射处理器时用于排序,可不设置 -->
          <property name="order" value="1"/>
      </bean>
      
      <bean id="demoController" class="com.github.dalianghe.controller.DemoController"/>

      打上断点,以debug模式启动服务,结果如下:

      SpringMVC之源码分析--HandlerMapping(一)

      从结果可知,Spring取到了我们配置的HandlerMapping(SimpleUrlHandlerMapping)并转换为List赋值给属性参数handlerMappings。

    • 加载所有映射处理器

      此场景需要我们配置多个映射处理器,Spring配置文件代码如下:

      <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
          <property name="urlMap">
              <props>
                  <prop key="/demo">demoController</prop>
              </props>
          </property>
          <property name="order" value="1"/>
      </bean>
      
      <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
          <property name="order" value="0"/>
      </bean>

      注意将web.xml配置的DispatcherServlet的detectAllHandlerMappings注释掉或设置为true

      打上断点,以debug模式启动服务,结果如下:

      SpringMVC之源码分析--HandlerMapping(一)

      SpringMVC之源码分析--HandlerMapping(一)

      从两种截图中可知,Spring取出了我们配置的所有的映射解析器,对比两种图,可知经过排序,Spring实现了我们指定的解析器的order。

总结

本小节分析了HandlerMapping的初始化,过程主要以SimpleUrlHandlerMapping实现类为例,替换成其他的实现类,启动过程也是一样的,后续会对实现类逐个进行分析,希望本章对大家能有帮助。

最后创建了qq群方便大家交流,可扫描加入,共同学习、共同进步,谢谢!

SpringMVC之源码分析--HandlerMapping(一)

相关推荐

TiDBPingCAP / 0评论 2020-07-29