SpringMVC源码分析(5)剖析重要组件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.1 SimpleUrlHandlerMapping
简单映射关系。
2.2 配置文件
<?xml version="1.0" encoding="UTF-8"?><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
欢迎工作一到五年的Java工程师朋友们加入Java高并发: 957734884,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!