<SpringMVC源码分析(4)剖析DispatcherServlet重要组件> 简要介绍了各个组件,从本章开始,针对各个组件,深入介绍。首先是HandlerMapping。
HanlerMapping是沟通请求和后端controller映射,是所有请求的入口。
1.类结构介绍
该图只对属性和类层级进行了描述,屏蔽了方法,主要是为了根据内部属性,重点理解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>
快照
测试用例
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的主要职责
注册Handler.可以是注解,可以是XML声明。
生成url,有很多策略。beanName,前缀,包名称都可以作为参考
维护mapping关系
url的匹配能力
接下来,会继续探讨HandlerMapping的另一个重要部分
拦截器和DefaultAnnotationHandlerMapping