SpringMVC源码系列:AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
是通过url来进行匹配的,也就是说通过url与对应的Handler包存到一个Map中,然后在getHandlerInternal方法中使用url作为key从Map中获取我们的handler。
AbstractUrlHandlerMapping
实现了从url获取handler的过程,具体的映射关系,也就是handlerMap则是交给具体子类来去完成的。AbstractUrlHandlerMapping
中定义了handlerMap用来维护映射关系,如下:
private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
除此之外,还有一个rootHandler,这个用于处理“/”请求。
在前面三篇文章中提到过,handler的获取是通过getHandlerInternal方法完成的,下面看下具体的源码,分析下handler的获取和handlerMap的构建。
//查找给定请求的URL路径的Handler。 protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //使用lookupPath从Map中查找handler Object handler = lookupHandler(lookupPath, request); if (handler == null) { //临时变量,保存原始的handler Object rawHandler = null; //是否是‘/’根路径 if ("/".equals(lookupPath)) { //获取rootHandler rawHandler = getRootHandler(); } //如果rawHandler是null if (rawHandler == null) { //获取默认的handler rawHandler = getDefaultHandler(); } //如果rawHandler不是null if (rawHandler != null) { // 如果是string类型,则到容器中查找具体的bean if (rawHandler instanceof String) { String handlerName = (String) rawHandler; //容器中获取 rawHandler = getApplicationContext().getBean(handlerName); } //校验handler和request是否匹配 validateHandler(rawHandler, request); //注册拦截器 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } //日志debug if (handler != null && logger.isDebugEnabled()) { logger.debug("Mapping [" + lookupPath + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { logger.trace("No handler mapping found for [" + lookupPath + "]"); } //返回handler return handler; }
在getHandlerInternal
方法中有几个方法调用,像getLookupPathForRequest、getRootHandler、getDefaultHandler、lookupHandler、buildPathExposingHandler等。其中getLookupPathForRequest、getRootHandler、getDefaultHandler这几个没啥好说的;比较核心的就是lookupHandler、buildPathExposingHandler这两个方法。
lookupHandler
lookupHandler使用getUrlPathHelper().getLookupPathForRequest(request)获取到的lookupPath从Map中查找需要的Handler,通常情况下是直接get不到的。为什么呢?原因在于很多的handler都是使用了Pattern的匹配模式,比如说“/user/*”,星号表示匹配任意内容,并非是指定url串中的字符。如果Pattern中包含了PathVariable,也不能直接从Map中获取到。
除此之外,一个url还可能和多个Pattern相匹配,那么这个时候咱们肯定就需要选择最优的,所以说查找过程其实并不是直接从map中获取那么简单。那么就来看下在lookupHandler中都干了哪些事情:
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { // 直接匹配,直接从Map中获取 Object handler = this.handlerMap.get(urlPath); //取到了 if (handler != null) { // 如果是string类型,则从容器中获取Bean if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } //验证是否匹配 validateHandler(handler, request); //注册拦截器 return buildPathExposingHandler(handler, urlPath, urlPath, null); } // Pattern 匹配,带*号的模式与url进行匹配 List<String> matchingPatterns = new ArrayList<String>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { matchingPatterns.add(registeredPattern +"/"); } } } //获取最佳匹配 String bestPatternMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { Collections.sort(matchingPatterns, patternComparator); if (logger.isDebugEnabled()) { logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns); } bestPatternMatch = matchingPatterns.get(0); } //最佳匹配不为null if (bestPatternMatch != null) { //从Map中看看是否有对应的Handler handler = this.handlerMap.get(bestPatternMatch); //如果Map中没有 if (handler == null) { //是否以/结尾 Assert.isTrue(bestPatternMatch.endsWith("/")); //去除/之后再获取一次 handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1)); } // 如果是String类型,则从容器中获取Bean? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } //验证是否匹配 validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath); // 可能有多种最佳模式,让我们确保我们有正确的URI模板变量(译) Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isDebugEnabled()) { logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; }
上面代码中,关于译注的部分需要说一下;代码如下:
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } }
之前是通过sort方法进行排序的,然后将第一个作为bestPatternMatch,但是如果多个pattern的顺序相同,也就是说sort返回的是0,存在多种最佳匹配,那就需要确保我们有正确的URI模板变量。上面代码就是处理这种情况的。
buildPathExposingHandler
这个方法在上面的两段代码中都频繁出现,那么这个方法到底有什么作用呢?代码中我注释的是注册拦截器,那么注册的又是什么拦截器?带着这两个问题,我们来看下代码。
//buildPathExposingHandler为给定的rawHandler构建一个Handler对象,并在执 //行处理程序之前暴露实际的处理程序PATH_WITHIN_HANDLER_MAPPING_ATTRIBUT //E以及URI_TEMPLATE_VARIABLES_ATTRIBUTE。 //默认实现用一个特殊的拦截器构建一个HandlerExecutionChain,该拦截器暴露 //path属性和uri模板变量。 protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, Map<String, String> uriTemplateVariables) { HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); if (!CollectionUtils.isEmpty(uriTemplateVariables)) { chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); } return chain; }
四个参数:
- rawHandler 原始处理程序
- bestMatchingPattern 最佳匹配模式
- pathWithinMapping 在执行Handler之前公开的路径
- uriTemplateVariables 如果没有找到变量,URI模板变量可以是{null}
从代码注释翻译及代码内容可以了解到,buildPathExposingHandler的作用就是给已经查找到的handler注册两个拦截器
- ExposingHandlerInterceptor
- UriTemplateVariablesHandlerInterceptor
这两个类均是AbstractUrlHandlerMapping
的内部类,也就是两个内部拦截器。这两个拦截器的主要作用就是将与当前url实际匹配的pattern、匹配条件以及url模板参数等设置到request的属性里面去,这样在后面的处理过程中就可以直接从request属性中获取。看下两个内部类的定义:
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter { private final String bestMatchingPattern; private final String pathWithinMapping; public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) { this.bestMatchingPattern = bestMatchingPattern; this.pathWithinMapping = pathWithinMapping; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request); //设置request属性 request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings()); return true; } } private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter { private final Map<String, String> uriTemplateVariables; public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) { this.uriTemplateVariables = uriTemplateVariables; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //这exposeUriTemplateVariables种设置request属性 exposeUriTemplateVariables(this.uriTemplateVariables, request); return true; } }
从内部类的代码可以看出,这两个内部类是通过在preHandle方法中调用exposePathWithinMapping和exposeUriTemplateVariables完成属性设置到request中的。
对于查找handler的关键其实就是维护url和handler的映射关系,也就是handlerMap的构建。在AbstractUrlHandlerMapping
中是通过registerHandler这个方法来构建handlerMap的。AbstractUrlHandlerMapping
提供了两个registerHandler方法,下面就通过代码来看下具体的实现。
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); } }
第一个registerHandler是将多个url注册到一个处理器。beanName其实就是咱们处理器的名称,可以通过beanName到容器中去找到真正的处理器Bean。具体处理就是通过遍历所有的url,然后再通过调用第二个registerHandler将handler注册到handlerMap中。来看第二个:
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; // 如果的handler是string类型,并且不是lazyInitHandlers,则从SpringMV //C容器中获取handler if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; if (getApplicationContext().isSingleton(handlerName)) { resolvedHandler = getApplicationContext().getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { //异常处理 } } else { //是否是跟路径 if (urlPath.equals("/")) { if (logger.isInfoEnabled()) { logger.info("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } //是否是*模式 else if (urlPath.equals("/*")) { if (logger.isInfoEnabled()) { logger.info("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } //加入到handlerMap中 else { this.handlerMap.put(urlPath, resolvedHandler); if (logger.isInfoEnabled()) { logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } }
这个里面首先是看Map中是否有原来传入的url,如果没有就加入,如果有就看下原来保存的和当前注册的handler是否是同一个,如果不是同一个就抛出异常。(同一个url不可能存在两个不同的handler)。
在put之前,也做了一些“/”和“/*”的验证处理,如果是这两种路径的话就不保存到handlerMap中了。
- “/”:setRootHandler(resolvedHandler);
- “/*”:setDefaultHandler(resolvedHandler);
OK,到这AbstractUrlHandlerMapping
这个类就分析完了,其实AbstractUrlHandlerMapping
做的事情就是定义了一个框子,子类只要完成对Map的初始化就可以了。关于AbstractUrlHandlerMapping
的子类后续再谈。