<SpringMVC源码分析(4)剖析DispatcherServlet重要组件>   简要介绍了各个组件,从本章开始,针对各个组件,深入介绍。首先是HandlerMapping。

HanlerMapping是沟通请求和后端controller映射,是所有请求的入口。

1.类结构介绍

 SpringMVC源码分析(5)剖析重要组件HandlerMapping_springmvc           

该图只对属性和类层级进行了描述,屏蔽了方法,主要是为了根据内部属性,重点理解Spring HandlerMapping提供功能。

1.1 AbstractHandlerMapping

 HandlerMapping 抽象类,提供了排序,默认Handler,和handler 拦截器。

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
      implements HandlerMapping, Ordered {
private int order = Integer.MAX_VALUE;  // default: same as non-Ordered

private Object defaultHandler;

private final List<Object> interceptors = new ArrayList<Object>();

private HandlerInterceptor[] adaptedInterceptors;
...
}

属性不做过多解释。

重点说明下interceptors 和adaptedInterceptors两个属性的区别

interceptors :作用于所有的mapping对应的所有Handler;

adaptedInterceptors:转换interceptors的镜像;

protected void initInterceptors() {
   if (!this.interceptors.isEmpty()) {
      this.adaptedInterceptors = new HandlerInterceptor[this.interceptors.size()];
      for (int i = 0; i < this.interceptors.size(); i++) {
         Object interceptor = this.interceptors.get(i);
         if (interceptor == null) {
            throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
         }
         this.adaptedInterceptors[i] = adaptInterceptor(interceptor);
      }
   }
}
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
   if (interceptor instanceof HandlerInterceptor) {
      return (HandlerInterceptor) interceptor;
   }
   else if (interceptor instanceof WebRequestInterceptor) {
      return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
   }
   else {
      throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
   }
}

1.2 AbstractUrlHandlerMapping

提供 URL-mapped功能,提供了根据url检索handler的能力。

url匹配规则:最长匹配。

public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
    //从请求request找出url
   private UrlPathHelper urlPathHelper = new UrlPathHelper();
    //进行url匹配,选择合适的拦截器
   private PathMatcher pathMatcher = new AntPathMatcher();
    //响应handlerMapping root请求("/")
   private Object rootHandler;
   // whether to lazily initialize handlers
   private boolean lazyInitHandlers = false;
    //registered handlers
   private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
    //MappedInterceptors 
   private MappedInterceptors mappedInterceptors;
     ...  
 }

1.3 AbstractDetectingUrlHandlerMapping

提供发现HandlerMapping能力.,通过内省(introspection)方式,从spring容器中获取。

具体见detectHandlers。

public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
//whether to detect handler beans in ancestor ApplicationContexts
private boolean detectHandlersInAncestorContexts = false;
protected void detectHandlers() throws BeansException {
   if (logger.isDebugEnabled()) {
      logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
   }
   String[] beanNames = (this.detectHandlersInAncestorContexts ?
         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
         getApplicationContext().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");
         }
      }
   }
}
...
}

1.4 AbstractControllerUrlHandlerMapping

提供获得controller到url的转换。

public abstract class AbstractControllerUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping  {

   private ControllerTypePredicate predicate = new AnnotationControllerTypePredicate();
    //Java packages that should be excluded from this mapping
   private Set<String> excludedPackages = Collections.singleton("org.springframework.web.servlet.mvc");
    // controller classes that should be excluded
   private Set<Class> excludedClasses = Collections.emptySet();
   @Override
    protected String[] determineUrlsForHandler(String beanName) {
       Class beanClass = getApplicationContext().getType(beanName);
       if (isEligibleForMapping(beanName, beanClass)) {
          return buildUrlsForHandler(beanName, beanClass);
       }
       else {
          return null;
       }
    }
...
}

2. 实例

2.1 SimpleUrlHandlerMapping

简单映射关系。

2.2 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>

   <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
      <property name="defaultHandler"><ref local="starController"/></property>
      <property name="rootHandler"><ref local="mainController"/></property>
      <property name="urlMap">
         <map>
            <entry key="/welcome*"><ref local="otherController"/></entry>
            <entry key="/welcome.html"><ref local="mainController"/></entry>
            <entry key="/show.html"><ref local="mainController"/></entry>
            <entry key="/bookseats.html"><ref local="mainController"/></entry>
            <entry key="/reservation.html"><ref local="mainController"/></entry>
            <entry key="/payment.html"><ref local="mainController"/></entry>
            <entry key="/confirmation.html"><ref local="mainController"/></entry>
         </map>
      </property>
   </bean>
   <bean id="mainController" class="java.lang.Object"/>
  <bean id="otherController" class="java.lang.Object"/>
  <bean id="starController"  class="java.lang.Object"/>
</beans>

测试用例

@Test
public void urlMappingWithUrlMap() throws Exception {
   checkMappings("urlMapping");
}
private void checkMappings(String beanName) throws Exception {
   MockServletContext sc = new MockServletContext("");
   XmlWebApplicationContext wac = new XmlWebApplicationContext();
   wac.setServletContext(sc);
   wac.setConfigLocations(new String[] {"/org/springframework/web/servlet/handler/map2.xml"});
   wac.refresh();
   Object bean = wac.getBean("mainController");
   Object otherBean = wac.getBean("otherController");
   Object defaultBean = wac.getBean("starController");
   HandlerMapping hm = (HandlerMapping) wac.getBean(beanName);

   MockHttpServletRequest req = new MockHttpServletRequest("GET", "/welcome.html");
   HandlerExecutionChain hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
   assertEquals("/welcome.html", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));

   req = new MockHttpServletRequest("GET", "/welcome.x");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == otherBean);
   assertEquals("welcome.x", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));

   req = new MockHttpServletRequest("GET", "/");
   req.setServletPath("/welcome.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/welcome.html");
   req.setContextPath("/app");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/show.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/bookseats.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/original-welcome.html");
   req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/welcome.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/original-show.html");
   req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/show.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/original-bookseats.html");
   req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/bookseats.html");
   hec = getHandler(hm, req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/");
   hec = getHandler(hm, req);//返回rootHandler
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
   assertEquals("/", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));

   req = new MockHttpServletRequest("GET", "/somePath");
   hec = getHandler(hm, req);//返回defaultHandler
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == defaultBean);
   assertEquals("/somePath", req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
}

2.2 BeanNameUrlHandlerMapping

内省方式,将注册的handler的名称作为相关的url

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>

   <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

   <bean id="godCtrl" name="/ /mypath/welcome.html /mypath/show.html /mypath/bookseats.html /mypath/reservation.html /mypath/payment.html /mypath/confirmation.html /mypath/test*"
         class="java.lang.Object"/>

</beans>

快照

 SpringMVC源码分析(5)剖析重要组件HandlerMapping_springmvc_02

测试用例

private void doTestRequestsWithSubPaths(HandlerMapping hm) throws Exception {
   Object bean = wac.getBean("godCtrl");

   MockHttpServletRequest req = new MockHttpServletRequest("GET", "/mypath/welcome.html");
   HandlerExecutionChain hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/myapp/mypath/welcome.html");
   req.setContextPath("/myapp");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/myapp/mypath/welcome.html");
   req.setContextPath("/myapp");
   req.setServletPath("/mypath/welcome.html");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/myapp/myservlet/mypath/welcome.html");
   req.setContextPath("/myapp");
   req.setServletPath("/myservlet");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/myapp/myapp/mypath/welcome.html");
   req.setContextPath("/myapp");
   req.setServletPath("/myapp");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/mypath/show.html");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);

   req = new MockHttpServletRequest("GET", "/mypath/bookseats.html");
   hec = hm.getHandler(req);
   assertTrue("Handler is correct bean", hec != null && hec.getHandler() == bean);
}

值得一提的是

//返回的lookupPath:会根据request.servletPath信息,对requestURI截取
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

2.3 ControllerClassNameHandlerMapping

注册@Controller annotated beans,按照class names 简单转换为url

 配置XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>

   <bean id="index" class="org.springframework.web.servlet.mvc.mapping.Controller"/>

   <bean id="welcome" class="org.springframework.web.servlet.mvc.mapping.WelcomeController"/>
   <bean id="admin" class="org.springframework.web.servlet.mvc.mapping.AdminController"/>
   <bean id="buy" class="org.springframework.web.servlet.mvc.mapping.BuyForm"/>
   
   <bean name="/myFile" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
   <bean name="/myFile2" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

   <bean id="mapping" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
      <!--
      We have to revert the default exclude for "org.springframework.web.servlet.mvc",
      since our test controllers sit in this package.
      -->
      <property name="excludedPackages"><list></list></property> <!-- 配置bean 的名称 -->
      <property name="excludedClasses" value="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
   </bean>

   <bean id="mapping2" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
      <!--
      We have to revert the default exclude for "org.springframework.web.servlet.mvc",
      since our test controllers sit in this package.
      -->
      <property name="excludedPackages"><list></list></property>
      <property name="excludedClasses" value="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
      <property name="caseSensitive" value="true"/>
      <property name="pathPrefix" value="/myapp"/><!-- 可匹配/myapp/mvc/mapping/buyForm-->
      <property name="basePackage" value="org.springframework.web.servlet"/>
   </bean>

   <bean id="mapping3" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
      <!--
      We have to revert the default exclude for "org.springframework.web.servlet.mvc",
      since our test controllers sit in this package.
      -->
      <property name="excludedPackages"><list></list></property>
      <property name="excludedClasses" value="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
      <property name="caseSensitive" value="true"/>
      <property name="pathPrefix" value="/myapp"/><!--/myapp/welcome -->
      <property name="basePackage" value="org.springframework.web.servlet.mvc.mapping"/>
   </bean>
    
   <bean id="mapping4" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
      <!--
      We have to revert the default exclude for "org.springframework.web.servlet.mvc",
      since our test controllers sit in this package.
      -->
      <property name="excludedPackages"><list></list></property>
      <property name="excludedClasses" value="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
      <property name="pathPrefix" value="myapp/"/>
      <property name="basePackage" value=""/>
      <!--匹配/myapp/org/springframework/web/servlet/mvc/mapping/welcome -->
   </bean>

</beans>

basePackage:Set the base package to be used for generating path mappings, including all subpackages underneath this packages as path elements.

pathPrefix:Specify a prefix to prepend to the path generated from the controller name.


生成url规则具体见generatePathMappings方法。

2.4 ControllerBeanNameHandlerMapping

和ControllerClassNameHandlerMapping类似,可以理解为url的生成规则有差异而已。

总结

介绍了相关的类结构,不难发现HandlerMapping的主要职责

  1. 注册Handler.可以是注解,可以是XML声明。

  2. 生成url,有很多策略。beanName,前缀,包名称都可以作为参考

  3. 维护mapping关系

  4. url的匹配能力

接下来,会继续探讨HandlerMapping的另一个重要部分

拦截器和DefaultAnnotationHandlerMapping