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 MaphandlerMap = 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 SetexcludedPackages = 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. 实例
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