SpringMVC 解毒2
3 HandlerMapping
图3-1
在第2章,DipatcherServlet
中使用 HandlerMapping 得到执行链HandlerExecutionChain
,然后就再也没有 HandlerMapping 的事了。也就是说SpringMVC就是靠 HandlerMapping 通过入参 HttpServletRequest 判断应该调用哪个方法、或者返回哪个文件,所以就让我们从这里入手。
3.1 HandlerExecutionChain 对象
这个对象很简单,没有继承,没有子类。DipatcherServlet
中用到了,HandlerMapping 也用到了,所以必须提一下。其中Object的handler就是具体处理器,interceptors会在handler执行前后以及整个请求处理完后调用。
HandlerInterceptor 是什么这里简单介绍一下,它是一个接口,还记得在第2章讲执行链调用预处理方法、后处理方法和完成触发方法吗?调用的就是这个接口的对应方法。MappedInterceptor 是 HandlerInterceptor 接口的继承接口,它的作用包装一个 HandlerInterceptor 并且可以设置上URI规则,允许调用接口方法前先判断是否符合规则。其他的 HandlerInterceptor 实现类目前就先不介绍了。
其中前三个主要方法各有特色,我就分别来讲讲。
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
预处理(前处理)方法大家请看,for循环是从小到大的,还记录了拦截器的下标,当有一个拦截器返回false时,会调用完成触发方法 triggerAfterCompletion。
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
后处理方法和前处理方法区别在for循环是从大往小的。
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
完成触发方法更奇葩,for循环从大往小不说,还从记录的下标开始。什么意思呢?就是说如果执行到第index+1个预处理方法返回了false,就从第index个拦截器倒着往回执行完成触发方法。
最后一个方法是针对Servlet3.0标准中的异步处理设置的,这里就先不讲了。
3.2 HandlerMapping接口分析
HandlerMapping 接口非常简单,就一个方法,从 HttpServletRequest 获取一个执行链HandlerExecutionChain
,如果没有映射上的handler,就应该返回null。
/**
* Interface to be implemented by objects that define a mapping between
* requests and handler objects.
*
* <p>This class can be implemented by application developers, although this is not
* necessary, as {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* and {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
* are included in the framework. The former is the default if no
* HandlerMapping bean is registered in the application context.
*
* <p>HandlerMapping implementations can support mapped interceptors but do not
* have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
* instance, optionally accompanied by some {@link HandlerInterceptor} instances.
* The DispatcherServlet will first call each HandlerInterceptor's
* {@code preHandle} method in the given order, finally invoking the handler
* itself if all {@code preHandle} methods have returned {@code true}.
*
* <p>The ability to parameterize this mapping is a powerful and unusual
* capability of this MVC framework. For example, it is possible to write
* a custom mapping based on session state, cookie state or many other
* variables. No other MVC framework seems to be equally flexible.
*
* <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
* interface to be able to specify a sorting order and thus a priority for getting
* applied by DispatcherServlet. Non-Ordered instances get treated as lowest priority.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.core.Ordered
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping
* @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
* @see org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
*/
public interface HandlerMapping {
/**
* Name of the {@link HttpServletRequest} attribute that contains the path
* within the handler mapping, in case of a pattern match, or the full
* relevant URI (typically within the DispatcherServlet's mapping) else.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the
* best matching pattern within the handler mapping.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
/**
* Name of the boolean {@link HttpServletRequest} attribute that indicates
* whether type-level mappings should be inspected.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations.
*/
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the URI
* templates map, mapping variable names to values.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains a map with
* URI matrix variables.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations and may also not be present depending on
* whether the HandlerMapping is configured to keep matrix variable content
* in the request URI.
*/
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains the set of
* producible MediaTypes applicable to the mapped handler.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. Handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns {@code null} if no match was found. This is not an error.
* The DispatcherServlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current HTTP request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or {@code null} if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
接口虽简单,但是要注意其中的一些常量。
PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
: 这个常量会被放到request的属性中,用来设置SpringMVC用的URI地址。BEST_MATCHING_PATTERN_ATTRIBUTE
: 这个常量会被放到request的属性中,用来设置匹配到的最合适的URI。INTROSPECT_TYPE_LEVEL_MAPPING
: 这个常量目前来说没用。URI_TEMPLATE_VARIABLES_ATTRIBUTE
: 这个常量会被放到request的属性中,用于存放临时变量,在使用restfull风格的uri时会用到。MATRIX_VARIABLES_ATTRIBUTE
: URI标准中有一种参数叫做矩阵参数,例如aaa/bbb;a=1;b=c。这个常量会被放到request的属性中,当有矩阵参数时,矩阵参数的解析结果将放到这里。PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
:这个常量会被放到request的属性中,会将映射上的处理器能产生的media type类型放到这里。
3.3 AbstractHandlerMapping 解析
在图3-1上,我们看到 HandlerMapping 的唯一直接实现类是 AbstractHandlerMapping ,那我们就从 AbstractHandlerMapping 开始看吧。
3.3.1 AbstractHandlerMapping 初始化解析
别看这个类属性那么多,还继承了你不知道的类。根据之前的经验,初始化部分,只要你看到这个类里有重写父类的方法,带有init类似字段,那这个方法保准是初始化方法。
按照这个道理,我们顺理成章地找到了下面这些方法。
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
/**
* Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
* <p>This is called in addition to any {@link MappedInterceptor}s that may have been provided
* via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
* from the current context and its ancestors. Subclasses can override and refine this policy.
* @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
*/
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(), MappedInterceptor.class, true, false).values());
}
/**
* Initialize the specified interceptors, checking for {@link MappedInterceptor}s and
* adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor}s if necessary.
* @see #setInterceptors
* @see #adaptInterceptor
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
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.add(adaptInterceptor(interceptor));
}
}
}
/**
* Adapt the given interceptor object to the {@link HandlerInterceptor} interface.
* <p>By default, the supported interceptor types are {@link HandlerInterceptor}
* and {@link WebRequestInterceptor}. Each given {@link WebRequestInterceptor}
* will be wrapped in a {@link WebRequestHandlerInterceptorAdapter}.
* Can be overridden in subclasses.
* @param interceptor the specified interceptor object
* @return the interceptor wrapped as HandlerInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter
*/
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());
}
}
这些方法做了一件事,就是在web应用上下文创建完成后,从上下文中找到 MappedInterceptor 并且和 自身属性 interceptors 做融合,设置到属性 adaptedInterceptors 中。
3.3.2 AbstractHandlerMapping 执行请求分析
AbstractHandlerMapping 是一个抽象类,所以它应该还会把具体的获取handler交给它的子类。那他在这里会做什么呢?来看代码吧
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
/**
* Look up a handler for the given request, returning {@code null} if no
* specific one is found. This method is called by {@link #getHandler};
* a {@code null} return value will lead to the default handler, if one is set.
* <p>On CORS pre-flight requests this method should return a match not for
* the pre-flight request but for the expected actual request based on the URL
* path, the HTTP methods from the "Access-Control-Request-Method" header, and
* the headers from the "Access-Control-Request-Headers" header thus allowing
* the CORS configuration to be obtained via {@link #getCorsConfigurations},
* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
* combining a handler object with dynamically determined interceptors.
* Statically specified interceptors will get merged into such an existing chain.
* @param request current HTTP request
* @return the corresponding handler instance, or {@code null} if none found
* @throws Exception if there is an internal error
*/
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
/**
* Build a {@link HandlerExecutionChain} for the given handler, including
* applicable interceptors.
* <p>The default implementation builds a standard {@link HandlerExecutionChain}
* with the given handler, the handler mapping's common interceptors, and any
* {@link MappedInterceptor}s matching to the current request URL. Interceptors
* are added in the order they were registered. Subclasses may override this
* in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
* pre-built {@link HandlerExecutionChain}. This method should handle those
* two cases explicitly, either building a new {@link HandlerExecutionChain}
* or extending the existing chain.
* <p>For simply adding an interceptor in a custom subclass, consider calling
* {@code super.getHandlerExecutionChain(handler, request)} and invoking
* {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never {@code null})
* @param request current HTTP request
* @return the HandlerExecutionChain (never {@code null})
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
以上是我整理出来的相关方法。
在 getHandler 方法中,我们看到第10行开头就将获取handler的任务交给了抽象方法,待子类实现。
其次,第12行代码告诉我们,我们可以在初始化时向 HandlerMapping 设置一个默认handler,这个以后再讲。
在第18行到第21行代码中,判断如果handler是String类型的,则会从web应用上下文中获取对应的bean。
在第22行代码中,调用了 getHandlerExecutionChain 方法,还记得前一小节中讲的 MappedInterceptor 这个类吗?它就是在这里使用的。也就是说,AbstractHandlerMapping 拥有上下文中所有的 HandlerInterceptor ,但是在处理一次请求时,会只让符合规则的 HandlerInterceptor 参与拦截。
第23行到第28行代码是处理跨域请求的,以后讲到跨域时,我们再来详细看看。
3.3.3 AbstractHandlerMapping 小结
AbstractHandlerMapping 抽象类还是比较简单的。通过第2章和第3章,我们发现DispatcherServlet
中拿到的时执行链 HandlerExecutionChain,会先调用执行链的预处理方法,再通过适配器调用执行链里的handler,再调用后处理方法,等处理完视图渲染后最后调用完成触发方法。为什么有 AbstractHandlerMapping 抽象类?我们发现他负责了 HandlerInterceptor 的初始化和构造成为 HandlerExecutionChain。而更复杂的handler生成就交给了子类。
3.4 AbstractUrlHandlerMapping 解析
在图3-1中,我们发现 AbstractHandlerMapping 只有两个子类,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。咱们就从最简单的 AbstractUrlHandlerMapping 抽象类开始吧。
3.4.1 AbstractUrlHandlerMapping 初始化分析
纵览 AbstractUrlHandlerMapping 类,发现这个类并没有初始化方法,虽是抽象类,但是并没有强制需要子类重写的方法,也是一个奇葩了。但实际上并不奇葩,来看一下我总结的初始化方法。
/**
* Register the specified handler for the given URL path.
* @param urlPath the URL the bean should be mapped to
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
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;
// Eagerly resolve handler if referencing singleton via name.
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) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
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);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
这个类的作用是保存URI和handler的对应关系,在父类调用重写的 getHandlerInternal 方法时,通过request中的URI查找最合适的handler。那URI和handler关系从哪来?当然是通过注册得来的。AbstractUrlHandlerMapping 应该是允许子类在初始化时,调用 registerHandler 方法来注册handler。
3.4.2 AbstractUrlHandlerMapping 处理请求分析
处理请求那是相当简单,前一小节不是注册了URI和handler的映射关系吗?处理请求就是直接取映射值,映射值好取吗?不带正则的话当然好取,带正则后那就稍微复杂一点。直接看代码吧。
/**
* Look up a handler for the URL path of the given request.
* @param request current HTTP request
* @return the handler instance, or {@code null} if none found
*/
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
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 + "]");
}
return handler;
}
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or {@code null} if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
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);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
if (handler == null) {
Assert.isTrue(bestPatternMatch.endsWith("/"));
handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
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;
}
3.4.3 AbstractUrlHandlerMapping 小结
AbstractUrlMapping 的一个子类叫 AbstractUrlHandlerMapping,另一个子类叫 AbstractHandlerMethodMapping。有没有发现猫腻?稍微有点常识的人都会发现 第一个子类是URI与handler对象的映射,第二个子类是handler对象上方法与URI的映射。
为什么这么讲?AbstractUrlHandlerMapping 只存了URI和handler的映射关系,没存URI到方法的关系,那你要是写一个普通类当做handler,那SpringMVC怎么知道要调用handler的哪个方法呢?所以像这种映射的handler必须是继承SpringMVC中特有的类或接口,像开发者常用的有Controllor接口及其实现子类、Servlet实现类等等。
注册URI和handler映射也有讲究,不信来看看它的实现类吧。
3.5 SimpleUrlHandlerMapping 解析
前面讲了那么多接口,抽象类,我想大家也都挺晕的,这次终于到了一个我们常见的实现类了。这个类超级简单,3.4节讲到 AbstractUrlHandlerMapping 留了一个 registerHandler 方法吗?来看看这个类是怎么用的。
/**
* Calls the {@link #registerHandlers} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
/**
* Register all handlers specified in the URL map for the corresponding paths.
* @param urlMap Map with URL paths as keys and handler beans or bean names as values
* @throws BeansException if a handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
}
}
}
简不简单?只要你在xml配置文件中配置了这个bean,然后给它的 urlMap 属性中设置映射,如果映射的Key忘记带/
了,他会帮你自动补上,映射的Value可以是ref引用bean,也可以是bean的name,都随你配置。
这里我给出一个xml配置的demo:
<bean id="simpleController" class="com.gavinzh.learn.web.simple.controller.SimpleController"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/simple" value-ref="simpleController"/>
</map>
</property>
<property name="order" value="0"/>
</bean>
3.6 AbstractDetectingUrlHandlerMapping
前面的 SimpleUrlHandlerMapping 的确很好用,在xml中的 urlMap 就能看到项目的所有映射。但是,有一个问题会在业务快速增加时困扰你。业务增加了,handler写的越来越多了,不仅得写bean配置,还得在 urlMap 中配置一遍,你说头疼不头疼?
SpringMVC的开发人员可能在某一天的早上坐地铁的时候想到了这个问题,那怎么解决呢?方法很简单,我写一个抽象类,功能和 SimpleUrlHandlerMapping 一样,在初始化时加载handler。那handler配置从哪来?想从哪来从哪来呗,我留一个抽象方法等子类实现不就对了?
是的 AbstractDetectingUrlHandlerMapping 就是干这破事的。来看看我总结的方法吧:
/**
* Calls the {@link #detectHandlers()} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
/**
* Register all handlers found in the current ApplicationContext.
* <p>The actual URL determination for a handler is up to the concrete
* {@link #determineUrlsForHandler(String)} implementation. A bean for
* which no such URLs could be determined is simply not considered a handler.
* @throws org.springframework.beans.BeansException if the handler couldn't be registered
* @see #determineUrlsForHandler(String)
*/
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");
}
}
}
}
/**
* Determine the URLs for the given handler bean.
* @param beanName the name of the candidate bean
* @return the URLs determined for the bean,
* or {@code null} or an empty array if none
*/
protected abstract String[] determineUrlsForHandler(String beanName);
啪啪啪,和 SimpleUrlHandlerMapping 是不是一样?都是在初始化时做handler的注册?。
3.7 BeanNameUrlHandlerMapping 分析
AbstractDetectingUrlHandlerMapping 竟然是抽象类?还要子类通过beanName找到URI?太过分了!!!
SpringMVC的开发人员估计怕被打,于是他们自己写了一个简单的实现类,有多简单?beanName就是URI,别名也是URI,这样简单多了吧?
来看看源码吧
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<String>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = getApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
我来展示一个demo:
<bean name="/named" id="/named" class="com.gavinzh.learn.web.simple.controller.NamedController"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" p:order="0"/>
3.8 AbstractHandlerMethodMapping 分析
前面我们分析了 AbstractUrlHandlerMapping 及其实现类,知道了那边用的时URI和handler映射,handler必须是继承或者实现某个类或接口才能用。
麻不麻烦?麻烦!一个URI就得一个类,你说麻烦不麻烦?SpringMVC的开发人员头估计都快被打爆了,怎么办怎么办?一个方法就对应一个URI!灵感就这么产生了。
3.8.1 HandlerMethod 和 MappingRegistry
想要了解针对方法的 HandlerMapping,就得先了解这两个类。
简单了解一下就行。先看 HandlerMethod 这个类,这个包含了一个bean对象、创建bean的工厂、bean的类型、方法、桥方法、方法入参类型、另一个HandlerMethod。这个对象精确到了方法,也就是说你拿着这个对象,再加入参,就能反射调用方法了。
再来看看 MappingRegistry,前面虽说 HandlerMethod 精确到了方法,但实际上并没有和URI有任何关系,MappingRegistry 就是实现URI和HandlerMethod的映射的。就有人跟我抬杠,说这个类中map的Key是T泛型,不是URI啊,这么来理解吧,一个请求request过来,携带了什么信息?URI,METHOD等等等等,是的这个T泛型之后可能指的是这一系列的组合,我们提的URI也就是代表一个请求的特征。
3.8.2 AbstractHandlerMethodMapping 初始化分析
在Spring中,对象都是有生命周期的,所以我们讲了这么多类,都是从初始化开始的。AbstractHandlerMapping 类并没有给子类留一个初始化抽象方法,那咱们就找找别的初始化点吧。下面是我找到的初始化点:
/**
* Detects handler methods at initialization.
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
return getMappingForMethod(method, userType);
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
/**
* Whether the given type is a handler with handler methods.
* @param beanType the type of the bean being checked
* @return "true" if this a handler type, "false" otherwise.
*/
protected abstract boolean isHandler(Class<?> beanType);
/**
* Provide the mapping for a handler method. A method for which no
* mapping can be provided is not a handler method.
* @param method the method to provide a mapping for
* @param handlerType the handler type, possibly a sub-type of the method's
* declaring class
* @return the mapping, or {@code null} if the method is not mapped
*/
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
实际上还会调用别的初始化方法,但实际上并没有用到,所以就不展示了。
在代码第19行到第38行,会从web应用上下文中获取所有对象,然后 调用 isHandler 方法判断是不是handler,如果是,就调用 detectHandlerMethods 方法选择出可以处理请求的method,并注册到 MappingRegistry 中。
在代码第51行到第56行,又调用了抽象方法 getMappingForMethod 获取可以处理请求的method并包装为泛型T。
在代码第63行,会把泛型T、handler和method注册到 MappingRegistry。
哦了,别的代码不用看了,子类最起码要判断bean是不是handler,还得从方法生成泛型T。
3.8.3 AbstractHandlerMethodMapping 处理请求分析
在3.3节 AbstractHandlerMapping 分析中,我们知道 HandlerInterceptor 由 AbstractHandlerMapping 添加并管理。留给子类的方法是 getHandlerInternal,用于获取真正的handler。
通过 3.8.2 节初始化分析,其实我们已经知道URI等相关信息和方法的映射是存在 MappingRegistry 中的,那么获取handler肯定也就是从那里获取的了。
猜代码不如看代码,看完代码感觉还不如猜代码,来看代码吧:
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
可以看到,由于是从泛型T中判断获取 HandlerMethod,复杂度有所提高,不过我们只要知道是从request的各个信息来综合判断就行。
3.9 RequestMappingInfoHandlerMapping 分析
AbstractHandlerMethodMapping 是一个带泛型的抽象类,其泛型代指是请求特征,而 RequestMappingInfoHandlerMapping 则指定了 RequestMappingInfo 类代替泛型。
这里,我们对 RequestMappingInfo 类做一个了解即可。
可以看到,request请求特征在这类中都有体现。
3.10 RequestMappingHandlerMapping 分析
作为 HandlerMapping的终点,也是最重要的一个类,他与我们使用注解@Controllor
和@RequestMapping
密切相关。接下来就来了解一下怎么密切相关了。
3.10.1 RequestMappingHandlerMapping 初始化分析
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
super.afterPropertiesSet();
}
我在代码中找到了这个初始化点,在这个初始化点里,我们发现对于 RequestMappingInfo 的使用,还得有一个配置文件,才能正常的工作。
3.10.2 RequestMappingHandlerMapping 处理请求分析
在 3.8.2 节 AbstractHandlerMethodMapping 初始化中,我们知道他会从web应用上下文中获取全部bean,然后判断是不是handler,然后注册到 MappingRegistry 中。它留下了两个抽象方法,一个是判断是不是handler,另一个就是通过方法生成request请求规则的方法。抽象方法全部在这里实现了,也很简单,判断是不是handler就是通过注解判断的,生成request的请求规则则是通过注解中的参数来生成。代码如下:
/**
* {@inheritDoc}
* Expects a handler to have a type-level @{@link Controller} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class<?> ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
/**
* Create a {@link RequestMappingInfo} from the supplied
* {@link RequestMapping @RequestMapping} annotation, which is either
* a directly declared annotation, a meta-annotation, or the synthesized
* result of merging annotation attributes within an annotation hierarchy.
*/
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, RequestCondition<?> customCondition) {
return RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name())
.customCondition(customCondition)
.options(this.config)
.build();
}
3.11 小结
对于 HandlerMapping 接口,其中只有一个方法就是getHandler,返回的是执行链即包含handler和一组拦截器一个对象。
AbstractHandlerMapping 很棒,帮开发者初始化加载了拦截器,然后为生成的执行链注入符合匹配规则的拦截器,把生成handler的任务交给子类。
然而 AbstractHandlerMapping 有两个子类,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。
AbstractUrlHandlerMapping 抽象类允许子类向String->Object的map中注册URI和对象的handler。旗下有两个最终实现类,SimpleUrlHandlerMapping 实现类需要在xml配置中显式设置URI和handler的对应关系,而 BeanNameUrlHandlerMapping 实现类只需要在xml中配置上,然后将handler配置的name或者id设置成以/
开头即可自动注入。
AbstractHandlerMethodMapping 抽象类则是以方法为处理请求核心,为了实现以方法为中心的handler映射,他提供了一个 MappingRegistry 属性,初始化加载所有bean,允许子类判断其是不是handler,然后由子类生成请求规则。它的唯一最终实现类是 RequestMappingHandlerMapping,也就是我们用的@Controllor
和@RequestMapping
注解所对应的映射器。
对于 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping,只需要在xml配置上bean即可,不需要再配置URI和handler的映射规则。
看到这里,你是不是有点恍然大悟的感觉?handler原来是这么来的,我们自己也可以自定义handler,只要配上自己adpater不就可以了?说对了,下一章我们将来分析 HandlerAdpater 及其实现类。