猫耳山在天边 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的初始化过程,接下来针对分析中提到的点进行验证,以检验我们的分析是正确的。
代码分析
pom配置文件
引入Spring MVC支持,代码如下:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version> </dependency>
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)没有任何内容,此时我们启动应用,通过断点方式验证,结果如下:
Spring MVC默认配置为:
从结果可知,在未进行任何配置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模式启动服务,结果如下:
从结果可知,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模式启动服务,结果如下:
从两种截图中可知,Spring取出了我们配置的所有的映射解析器,对比两种图,可知经过排序,Spring实现了我们指定的解析器的order。
本小节分析了HandlerMapping的初始化,过程主要以SimpleUrlHandlerMapping实现类为例,替换成其他的实现类,启动过程也是一样的,后续会对实现类逐个进行分析,希望本章对大家能有帮助。
最后创建了qq群方便大家交流,可扫描加入,共同学习、共同进步,谢谢!