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
属性不做过多解释。
重点说明下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 handlerMap = new LinkedHashMap();
//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 excludedPackages = Collections.singleton("org.springframework.web.servlet.mvc");
// controller classes that should be excluded
private Set 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 配置文件
测试用例
@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
配置文件
快照
测试用例
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
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等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!