今天终于可以写SpringMVC中的HandlerMapping了,之前三天看HandlerMapping的源码看的我快要精神错乱了,因为源码中常常用到回调,而且其中的继承父类和接口也比较多,即使打着断点跟踪代码运行,也被它调到晕头转向,不过今天终于算是大体弄懂了。废话不多说,现在开讲。
HandlerMapping的作用就是解析请求链接,然后根据请求链接找到执行这个请求的类(HandlerMapping所说的handler,也就是我们写的Controller或是Action)。至于我们在配置文件中配置的BeanNameUrlHandlerMapping或者是SimpleUrlHandlerMapping,他们的目的是一样的,只是通过请求链接来找handler的方式不一样罢了。先借网上的一张图来说明HandlerMapping相关类之间的关系
HandlerMapping的使用主要分为两步:注册和查找。
注册是根据配置文件中的配置将一个字符串和一个Controller类以<key,value>的形式存入到Map中,这个key就是对应的url中的某个字段。
查找就是HandlerMapping根据url中的的某个字段,在Map中以这个字段为key值对应的Controller类,并将Controller类封装成一个HandlerExecutionChain对象,HandlerExecutionChain中除了有Controller对象外,还有一组拦截器。
这里将以SimpleUrlHandlerMapping为例子来分析HandlerMapping是如何根据请求链接找到Controller类的。
一.注册
SpringMVC中的HandlerMapping配置
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<span style="background-color: rgb(255, 0, 0);"><props>
<prop key="/springMVC.d">/HelloWorld</prop>
</props></span>
</property>
</bean>
<bean name="/HelloWorld" class="com.wangbiao.springMVC.HelloWorld">
<property name="methodNameResolver">
<ref local="methodNameResolver"/>
</property>
</bean>
当我们第一次访问服务器的时候IOC容器会根据配置文件中的红色的部分生成一个Map<String, Object>,这个map里面的值就是{/springMVC.d=/HelloWorld}。
SimpleUrlHandlerMapping的作用就是获取这个集合,然后根据这个集合里的value找到对应的bean,这样就可以把url中的某个字段和我们写的处理器对应起来。下面是SimpleUrlHandlerMapping中的关键源码
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
这个方法就是初始化SpringMVC容器,并对handler进行注册,urlMap中的值根据上面的配置文件就是{/springMVC.d=/HelloWorld},下面再看看registerHandlers方法
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();
}
<span style="color:#ff0000;">registerHandler(url, handler);</span>
}
}
}
主要是对urlMap中的key值进行了一些处理,要是没有“/”的就加上"/",去掉空格等处理。这个方法中的重点是调用了registerHandler(url, handler)这个方法,在这个方法是它的父类
AbstractUrlHandlerMapping中的方法,我们来看看AbstractUrlHandlerMapping中的registerHandler(url, handler)
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)) {
<span style="background-color: rgb(255, 255, 255);"><span style="color:#ff0000;">resolvedHandler = getApplicationContext().getBean(handlerName);</span></span>
}
}
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 {
<span style="color:#ff0000;">this.handlerMap.put(urlPath, resolvedHandler);</span>
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
看registerHandler方法红色的部分大家,可以看出是根据SimpleUrlHandlerMapping中的urlMap中的value值在ioc容器中找到对应的bean,并将url的某个字段作为key值,bean作为value存入到AbstractUrlHandlerMapping的urlMap属性中去,这样就达到url的某个字段对应到具体的controller了的目的,当遇到有请求访问服务器的时候,就可以根据url找到具体的controller去执行这个请求了。
二.查找
在Dispatcher类中,根据配置文件对handlerMapping进行注册,即对handlerMapping的初始化。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
<span style="color:#ff0000;">initHandlerMappings(context);</span>
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
}
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
<span style="color:#ff0000;">Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);</span>
if (!matchingBeans.isEmpty()) {
<span style="color:#ff0000;">this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());</span>
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
由于在配置文件中有两种不同类型的handlerMapping,所以从ioc容器中读取出来的handlerMapping有两个,然后将这两个handlerMapping的实例放入Dodispatcher中的handlerMappings属性中。
下面一步就是真正的根据url中的某个字段到已经注册好了的Map<urlString,Controller>中找出执行这个url请求的Controller,用户的请求在被Dispatcher拦截后,会交给Dispatcher的doDispatch执行。在doDispatch方法中主要看红色标记的getHandler方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
<span style="color:#ff0000;">mappedHandler = getHandler(processedRequest, false);</span>
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// Apply preHandle methods of registered interceptors.
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// Apply postHandle methods of registered interceptors.
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
getHandler方法主要会调用已经注册好了的handlerMapping中的getHandler方法
DispatcherServlet中的getHandler方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
<span style="color:#ff0000;">HandlerExecutionChain handler = hm.getHandler(request);</span>
if (handler != null) {
return handler;
}
}
return null;
}
在来看看HandlerMapping的getHandler方法,可以看到HandlerMapping接口中只有一个getHandler方法。
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
/**
* 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.handler.SimpleUrlHandlerMapping}
* 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</code> method in the given order, finally invoking the handler
* itself if all <code>preHandle</code> methods have returned <code>true</code>.
*
* <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.handler.SimpleUrlHandlerMapping
*/
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 {@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";
/**
* 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</code> 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</code> if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
再看看实现了HandlerMapping的AbstractHandlerMapping抽象类,AbstractHandlerMapping中的getHandler方法,这个方法的主要作用是根据url找到controller后,并将controller封装成一个HandlerExecutionChain对象
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
<span style="color:#ff0000;">Object handler = getHandlerInternal(request);</span>
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);
}
return getHandlerExecutionChain(handler, request);
}
AbstractHandlerMapping的getHandlerInternal方法是个抽象方法,由AbstractHandlerMapping的子类AbstractUrlHandlerMapping实现。
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
<span style="color:#ff0000;">String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);</span>
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 && this.mappedInterceptors != null) {
Set<HandlerInterceptor> mappedInterceptors =
this.mappedInterceptors.getInterceptors(lookupPath, this.pathMatcher);
if (!mappedInterceptors.isEmpty()) {
HandlerExecutionChain chain;
if (handler instanceof HandlerExecutionChain) {
chain = (HandlerExecutionChain) handler;
}
else {
chain = new HandlerExecutionChain(handler);
}
chain.addInterceptors(mappedInterceptors.toArray(new HandlerInterceptor[mappedInterceptors.size()]));
}
}
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;
}
getLookupPathForRequest方法主要是截取url中对应controller的那一部分,lookupHandler方法根据截取的url字段找到对应的controller,看到红色的部分就和我们注册handlerMapping的那一步相关了,我们早早的就将url的部分字段所对应的controller放到了AbstractUrlHandlerMapping中的handlerMap属性中了,现在就能根据url找到对应的controller了。
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
<span style="color:#ff0000;">Object handler = this.handlerMap.get(urlPath);</span>
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);
}
}
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);
// 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) {
uriTemplateVariables
.putAll(getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath));
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
废话了那么多,也许大家都已经看的晕头转向了,也没有耐心看下去,那么在这里就对源码中是如何根据url找到对应的controller进行总结一下。
1.SimpleUrlHandlerMapping根据配置文件中的SimpleUrlHandlerMapping的配置,获得一个map集合,map中存储的是{urlString=beanId}。SimpleUrlHandlerMapping调用父类
AbstractUrlHandlerMapping的registerHandler方法。
2.AbstractUrlHandlerMapping的registerHandler方法有SimpleUrlHandlerMapping传入的map中的urlString和beanId,并根据beanId找到对应的bean即controller,将urlString和urlString对应的controller放入AbstractUrlHandlerMapping的handlerMap中。
3.Dispatcher获取IOC容器中已经初始化好的HandlerMapping,再由HandlerMapping调用自己的getHandler方法根据请求返回HandlerExecutionChain对象。AbstractHandlerMapping方法实现了HandlerMapping接口的getHandler方法。AbstractHandlerMapping中的getHandler方法的主要作用是找到controller,并对controller进行封装成HandlerExecutionChain对象,HandlerExecutionChain中除了controller对象外,还有拦截器对象的集合。
4.AbstractHandlerMapping的getHandler方法中又 调用了AbstractHandlerMapping子类的AbstractUrlHandlerMapping getHandlerInternal方法。getHandlerInternal方法就是截取url中对应的controller字段,并以这个字段为key值去AbstractUrlHandlerMapping 的handlerMap中找寻对应的value,即controlle。
总结
handlerMapping的作用
handlerMapping根据用户请求找到执行该请求的controller。
handlerMapping的找到controller过程
1.根据配置文件对url到controller的映射进行注册
2.根据具体的url请求找到执行该请求的controller
这篇文章拖拖拉拉的到现在才写完,拖拖拉拉的习惯还是改不掉,中间有些细节可能还是有一些不太明白的,但是大体流程算是走通了,春节前一定要将SpringMVC的源码研究完,并完成SpringMVC的专题博客。