MatchableHandlerMapping
这是HandlerMapping
的另外一个分支,这是它的一个子接口。
// @since 4.3.1 出现得挺晚的接口 public interface MatchableHandlerMapping extends HandlerMapping { // 确定给定的请求是否符合请求条件 pattern:模版 @Nullable RequestMatchResult match(HttpServletRequest request, String pattern); }
目前有两个类实现了此方法RequestMappingHandlerMapping和AbstractUrlHandlerMapping,但是Spring内部并还没有调用过此接口方法,因此此处暂时略过此部分~
接下来最重要的就是以AbstractHandlerMapping为主线,看看他的真正实现类们了。它主要分为两大主线:
AbstractUrlHandlerMapping方向和AbstractHandlerMethodMapping
AbstractHandlerMethodMapping系列是当前使用得最多的,基于方法的Handler模式,文章二会着重继续分析
AbstractUrlHandlerMapping系列
从命名中也能看出来,它和URL有关。它的大致思路为:将url对应的Handler保存在一个Map中,在getHandlerInternal方法中使用url从Map中获取Handler
// 虽然附加实现了MatchableHandlerMapping ,但本文并不准备详细分析 public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping { // 根路径 / 的处理器~ @Nullable private Object rootHandler; // 是否使用斜线/匹配 如果为true 那么`/users`它也会匹配上`/users/` 默认是false的 private boolean useTrailingSlashMatch = false; // 设置是否延迟初始化handler。仅适用于单实例handler 默认是false表示立即实例化 private boolean lazyInitHandlers = false; // 这个Map就是缓存下,URL对应的Handler(注意这里只是handler,而不是chain) private final Map<String, Object> handlerMap = new LinkedHashMap<>(); ... // 这个就是父类留给子类实现的抽象方法,此抽象类相当于进行了进一步的模版实现~ @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 找到URL的后半段:如`/api/v1/hello` 由此可见Spring MVC处理URL路径匹配都是从工程名后面开始匹配的~~~~ String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 根据url查找handler // 1、先去handlerMap里找,若找到了那就实例化它,并且并且给chain里加入一个拦截器:`PathExposingHandlerInterceptor` 它是个private私有类的HandlerInterceptor // 该拦截器的作用:request.setAttribute()请求域里面放置四个属性 key见HandlerMapping的常量们~~~ // 2、否则就使用PathMatcher去匹配URL,这里面光匹配其实是比较简单的。但是这里面还解决了一个问题:那就是匹配上多个路径的问题 // 因此:若匹配上多个路径了,就按照PathMatcher的排序规则排序,取值get(0)~~~最后就是同上,加上那个HandlerInterceptor即可 // 需要注意的是:若存在uriTemplateVariables,也就是路径里都存在多个最佳的匹配的情况 比如/book/{id}和/book/{name}这两种。 // 还有就是URI完全一样,但是一个是get方法,一个是post方法之类的 那就再加一个拦截器`UriTemplateVariablesHandlerInterceptor` 它request.setAttribute()了一个属性:key为 xxx.uriTemplateVariables // 这些Attribute后续都是有用滴~~~~~~ 请注意:这里默认的两个拦截器每次都是new出来的和Handler可议说是绑定的,所以不会存在线程安全问题~~~~ Object handler = lookupHandler(lookupPath, request); // 若没找到: if (handler == null) { // 处理跟路径 / 和默认的Handler~~~~ Object rawHandler = null; if ("/".equals(lookupPath)) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); // 就是注册上面说的默认的两个拦截器~~~~~~~ 第四个参数为null,就只会注册一个拦截器~~~ // 然后把rawHandler转换成chain(这个时候chain里面可能已经有两个拦截器了,然后父类还会继续把用户自定义的拦截器放上去~~~~) handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } // =========该抽象类提供的这个方法就特别重要了:向handlerMap里面put值的唯一入口~~~ 可以批量urls protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { Assert.notNull(urlPaths, "URL path array must not be null"); for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; // 如果是beanName,并且它是立马加载的~~~~ if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); // 并且还需要是单例的,那就立马实例化吧~~~~ if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } // 先尝试从Map中去获取 Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { // 这个异常错误信息,相信我们在开发中经常碰到吧:简单就是说就是一个URL只能映射到一个Handler上(但是一个Handler是可以处理多个URL的,这个需要注意) // 这个校验必不可少啊~~~~ if (mappedHandler != resolvedHandler) { throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { // 如果你的handler处理的路径是根路径,那太好了 你的这个处理器就很特殊啊~~~~ if (urlPath.equals("/")) { setRootHandler(resolvedHandler); } // 这个路径相当于处理所有 优先级是最低的 所以当作默认的处理器来使用~~~~ else if (urlPath.equals("/*")) { setDefaultHandler(resolvedHandler); } // 正常的路径了~~~ // 注意此处:好像是Spring5之后 把这句Mapped的日志级别 直接降低到trace级别了,简直太低了有木有~~~ // 在Spring 5之前,这里的日志级别包括上面的setRoot等是info(所以我们在控制台经常能看见大片的'Mapped URL path'日志~~~~) // 所以:自Spring5之后不再会看controller这样的映射的日志了(除非你日志界别调低~~~)可能Spring认为这种日志多,且不认为是重要的信息吧~~~ else { this.handlerMap.put(urlPath, resolvedHandler); if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } // 该缓存也提供了一个只读视图给调用者访问~~~ public final Map<String, Object> getHandlerMap() { return Collections.unmodifiableMap(this.handlerMap); } }
该抽象类提供了一个Map,缓存着了URL和它对应的Handler,这是个非常重要的缓存。它提供了registerHandler()允许子类调用,向缓存里注册url和handler的对应关系~
注意:此处肯定不能把Map放出去,让子类直接put的。因为程序必须要高内聚,才能保证更好的隔离性以及稳定性
AbstractDetectingUrlHandlerMapping
这又是个抽象类,继承自AbstractUrlHandlerMapping。它就越来越具有功能化了:Detecting表明它是有检测URL的功能的~
// @since 2.5 它Spring2.5后才出来 public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping { // 是否要去祖先容器里面检测所有的Handlers 默认是false表示只在自己的容器里面找 // 若设置为true,相当于在父容器里的Controller也会被挖出来~~~~ 一般我并不建议这么去做 private boolean detectHandlersInAncestorContexts = false; public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) { this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts; } // 说白了,这里是检测的入口 detectHandlers(); @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); } protected void detectHandlers() throws BeansException { // 这个就不解释了:默认只会在当前容器里面去查找检测~~~ // 注意:这里使用的Object.class 说明是把本容器内所有类型的Bean定义都拿出来了~~~~ 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) { // 这是个抽象方法由子类去实现。 它的作用就是看看url和bean怎么才算是匹配呢?也就是说这个handler到底能够处理哪些URL呢? // 注意:此处还是类级别(Bean),相当于一个类就是一个Handler哦~~~~ String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // 注册进去 缓存起来~ registerHandler(urls, beanName); } } } }
AbstractDetectingUrlHandlerMapping是通过扫描方式注册Handler,收到请求时由AbstractUrlHandlerMapping的getHandlerInternal进行分发看看到底是交给哪个Handler进行处理~
最后真的是得看它实现类的时候了
BeanNameUrlHandlerMapping
它是AbstractDetectingUrlHandlerMapping的唯一实现类
说明:DefaultAnnotationHandlerMapping、BeanNameUrlHandlerMapping、AbstractControllerUrlHandlerMapping在Spring4.3的时候都被标记为过期,在Spring5以后直接就把这些类干掉了,因此本处说的唯一、源码都是基于Spring5.以上的版本的~~~
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); // 意思就是必须以/开头才行~~~~~~这算是一种约定吧~~~ // 这种方式和@WebServlet方式一毛一样~~~~~ if (beanName.startsWith("/")) { urls.add(beanName); } // 当然别名也是可以的 String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
该实现有点那啥,就是根据bean的名称来匹配URL。方式同@WebServlet一毛一样
SimpleUrlHandlerMapping
它是AbstractUrlHandlerMapping的直接实现类,也是一个基于Map的简单实现。
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { private final Map<String, Object> urlMap = new LinkedHashMap<>(); public void setMappings(Properties mappings) { CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap); } public void setUrlMap(Map<String, ?> urlMap) { this.urlMap.putAll(urlMap); } @Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); } // 这个实现简单到令人发指 protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.trace("No patterns in " + formatMappingName()); } else { urlMap.forEach((url, handler) -> { // 如果还没有斜线,在前面加上斜线 if (!url.startsWith("/")) { url = "/" + url; } if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); } } }
它的实现就是吧开发者指定的一个Map,然后容器启动的时候把它注册进去即可,非常的简单的一个实现。当然我们自己已经知道了URL和Handler的映射关系了,然后需要进一步构造出一个HandlerMapping的时候,或许它是一个较快解决问题的选择~~~~ 它最重要的是urlMap这个参数~
它一般用于基于XML的配置文件的 形式,形如:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property> <map> <entry key="/hello.do" value="myController"/> <entry key="/my.do" value="myController"/> </map> </property> </bean>
Demo
写一个控制器:
@Controller("/hello") // 注意此处BeanName必须是/开头,否则是不会作为handler的 public class HelloController { }
这样项目启动的时候,就可以看到这么一句日志,证明url是映射成功了的~:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping.registerHandler Mapped URL path [/hello] onto handler '/hello'
但是请求后报出如下错误(http://localhost:8080/demo_war_war/hello):
No adapter for handler [com.fsx.controller.HelloController@5e31e1a4]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
找不到适配器,这其实是下一节讲解HandlerAdapter的内容,此处介绍怎么做:
// 实现`org.springframework.web.servlet.mvc.Controller`这个接口,才被认为是一个控制器~ @Controller("/hello") public class HelloController extends AbstractController { @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("this is my demo"); return null; } }
这样子我们再请求,就能正常的进入handleRequestInternal方法来处理请求了。
我们的Controller类必须(直接|间接)实现org.springframework.web.servlet.mvc.Controller接口,否则无法判断具体处理方法是谁!!!
总结
本篇介绍的HandlerMapping,除了介绍它的抽象实现外。就是介绍了AbstractUrlHandlerMapping系列。
它主要实现是BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping,属于Spring最早期的控制器实现。完全是基于类级别的:一个类就是一个Handler,模式和源生的Servlet没太大差异(还是和Servlet源生API有耦合的~)。
显然这种模式在现在全注解时代已经完全过时了,开发、运行效率都太低下了。但是了解了这些过去的技术,才会发现这些都是一脉相承的,了解背后作者的设计意图、走向才是最为重要的目的
从分析DispatcherServlet的时候发现,SpringMVC默认是给容器内注入了两个HandlerMapping组件的:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping由此可见Spring还是保持了充分的向下兼容的~