博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门
阅读量:6564 次
发布时间:2019-06-24

本文共 16212 字,大约阅读时间需要 54 分钟。

hot3.png

刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊,最近花了一点时间稍微看了下源代码,再加上调试,开始逐渐理解它,网上的类似的内容有很多,写本文主要是自己加深一下理解。本文适合用过SpringMVC的开发者,言归正传,首先搭建一个最简单的工程体验一下。
该工程是基于maven的,pom配置不再说明,所使用的spring版本4.0.5。
首先是web.xml文件配置,最简单的配置
Archetype Created Web Application
mvc
org.springframework.web.servlet.DispatcherServlet
1
mvc
/*
然后是mvc-servlet.xml文件的配置,上面配置DispatcherServlet会默认加载[servlet-name]-servlet.xml文件。对于我的配置,会去加载mvc-servlet.xml文件。
mvc-servlet.xml文件的内容:
zh_CN
在该配置中定义了一个HomeAction的Bean。内容为:
package com.lg.mvc;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.Controller;public class HomeAction implements Controller{	@Override	public ModelAndView handleRequest(HttpServletRequest request,			HttpServletResponse response) throws Exception {		return new ModelAndView("hello");	}}
这是最原始的mvc做法,要继承Controller接口,先从原始的说起,最后再过渡到@Controller和@RequestMapping注解式的配置。它在mvc-serlet.xml文件中的配置有一个关键的属性name="/index"。
WEB-INF/view目录下有一个简单的hello.html,内容为:
						hello lg !	
至此该工程就写完了,部署到tomcat中,项目路径为/,运行一下。
访问 http://localhost:8080/index
07101142_oQd9.png
至此整个工程就算搭建成功了。
下面就要说说原理了。
用过python Django框架的都知道Django对于访问方式的配置就是,一个url路径和一个函数配对,你访问这个url,就会直接调用这个函数,简单明了。对于java的面向对象来说,就要分两步走。第一步首先要找到是哪个对象,即handler,本工程的handler则是HomeAction对象。第二步要找到访问的函数,即HomeAction的handleRequest方法。所以就出现了两个源码接口 HandlerMapping和HandlerAdapter,前者负责第一步,后者负责第二步。借用网上的SpringMVC架构图。
07101143_4yTV.png
HandlerMapping接口的实现(只举了我认识的几个) :
  • BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象
    SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
    DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
    RequestMappingHandlerMapping :取代了上面一个
HandlerAdapter 接口实现:
  • HttpRequestHandlerAdapter : 要求handler实现HttpRequestHandler接口,该接口的方法为                                                             void handleRequest(HttpServletRequest request, HttpServletResponse response)也就是  handler必须有一个handleRequest方法
    SimpleControllerHandlerAdapter:要求handler实现Controller接口,该接口的方法为ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),也就是本工程采用的
    AnnotationMethodHandlerAdapter :和上面的DefaultAnnotationHandlerMapping配对使用的,也已过时
    RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping
先简单的说下这个工程的流程,访问http://localhost:8080/index首先由DispatcherServlet进行转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每个adapter进行适配,由于HomeAction实现了Controller接口,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest方法的调度。然后就顺利的执行了我们想要的方法,后面的内容不在本节中说明。
了解了大概流程,然后就需要看源代码了。
首先就是SpringMVC的入口类,DispatcherServlet,它实现了Servlet接口,不再详细说DispatcherServlet的细节,不然又是一大堆的内容。每次请求都会调用它的doService->doDispatch,我们关注的重点就在doDispatch方法中。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {		HttpServletRequest processedRequest = request;		HandlerExecutionChain mappedHandler = null;		boolean multipartRequestParsed = false;		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);		try {			ModelAndView mv = null;			Exception dispatchException = null;			try {				processedRequest = checkMultipart(request);				multipartRequestParsed = (processedRequest != request);                      //这个是重点,第一步由HandlerMapping找到对应的handler				// Determine handler for the current request.				mappedHandler = getHandler(processedRequest);				if (mappedHandler == null || mappedHandler.getHandler() == null) {					noHandlerFound(processedRequest, response);					return;				}				// Determine handler adapter for the current request.                       //这是第二步,找到合适的HandlerAdapter,然后由它来调度执行handler的方法				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()) {						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);					}					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {						return;					}				}				if (!mappedHandler.applyPreHandle(processedRequest, response)) {					return;				}				try {					// Actually invoke the handler.					mv = ha.handle(processedRequest, response, mappedHandler.getHandler());				}				finally {					if (asyncManager.isConcurrentHandlingStarted()) {						return;					}				}				applyDefaultViewName(request, mv);				mappedHandler.applyPostHandle(processedRequest, response, mv);			}			catch (Exception ex) {				dispatchException = ex;			}			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);		}		catch (Exception ex) {			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);		}		catch (Error err) {			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);		}		finally {			if (asyncManager.isConcurrentHandlingStarted()) {				// Instead of postHandle and afterCompletion				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);				return;			}			// Clean up any resources used by a multipart request.			if (multipartRequestParsed) {				cleanupMultipart(processedRequest);			}		}	}
第一步详细查看:
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() + "'");			}			HandlerExecutionChain handler = hm.getHandler(request);			if (handler != null) {				return handler;			}		}		return null;	}
可以看到就是通过遍历所有已注册的HandlerMapping来找到对应的handler,然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下
public class HandlerExecutionChain {	private final Object handler;	private HandlerInterceptor[] interceptors;	private List
interceptorList; //其他代码省略}
其中HandlerMapping的getHandler实现:
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);		}		return getHandlerExecutionChain(handler, request);	}
这里的getHandlerInternal(request)是个抽象方法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean。
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过一系列的接口设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只用实现该方法中的一部分),如下
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {	/**	 * Checks name and aliases of the given bean for URLs, starting with "/".	 */	@Override	protected String[] determineUrlsForHandler(String beanName) {		List
urls = new ArrayList
(); 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); }}
这里面注释说,bean的name必须以/开头,它才处理,将信息存储在Map<String, Object> handlerMap中,对于本工程来说就是{'/index':HomeAction对象}。
至此这里完成了第一步,下面开始第二步,即方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());的具体实现:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {		for (HandlerAdapter ha : this.handlerAdapters) {			if (logger.isTraceEnabled()) {				logger.trace("Testing handler adapter [" + ha + "]");			}			if (ha.supports(handler)) {				return ha;			}		}		throw new ServletException("No adapter for handler [" + handler +				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");	}
遍历所有的HandlerAdapter,判断他们是否支持这个handler。
我们来看下HttpRequestHandlerAdapter的supports(handler)方法:
public class HttpRequestHandlerAdapter implements HandlerAdapter {	@Override	public boolean supports(Object handler) {          //就是判断handler是否实现了HttpRequestHandler接口		return (handler instanceof HttpRequestHandler);	}	@Override	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)			throws Exception {           //若handler实现了HttpRequestHandler接口,则调用该接口的方法,执行我们在该方法中写的业务逻辑		((HttpRequestHandler) handler).handleRequest(request, response);		return null;	}	@Override	public long getLastModified(HttpServletRequest request, Object handler) {		if (handler instanceof LastModified) {			return ((LastModified) handler).getLastModified(request);		}		return -1L;	}}
同理SimpleControllerHandlerAdapter也是这样类似的逻辑
public class SimpleControllerHandlerAdapter implements HandlerAdapter {	@Override	public boolean supports(Object handler) {		return (handler instanceof Controller);	}	@Override	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)			throws Exception {		return ((Controller) handler).handleRequest(request, response);	}	@Override	public long getLastModified(HttpServletRequest request, Object handler) {		if (handler instanceof LastModified) {			return ((LastModified) handler).getLastModified(request);		}		return -1L;	}}
剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比较复杂,我也没看。
按照本工程的配置,则SimpleControllerHandlerAdapter是支持HomeAction的,然后就会执行SimpleControllerHandlerAdapter的handle(processedRequest, response, mappedHandler.getHandler())方法。本质上就会调用HomeAction实现Controller接口的方法。至此就分析完了。
了解过程了之后,然后就是最重要的也是经常配置出问题的地方。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。
DispatcherServlet初始化的时候,会调用一个方法如下:
protected void initStrategies(ApplicationContext context) {		initMultipartResolver(context);		initLocaleResolver(context);		initThemeResolver(context);//初始化一些HandlerMapping		initHandlerMappings(context);//初始化一些HandlerAdapter		initHandlerAdapters(context);		initHandlerExceptionResolvers(context);		initRequestToViewNameTranslator(context);		initViewResolvers(context);		initFlashMapManager(context);	}
这里可以看到,它会初始化一些HandlerMapping和HandlerAdapter,这两个方法非常重要,理解了这两个方法你就会知道,配置不对问题出在哪里,下面具体看下这两个方法:
private void initHandlerMappings(ApplicationContext context) {		this.handlerMappings = null;		if (this.detectAllHandlerMappings) {			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.			Map
matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList
(matchingBeans.values()); // 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. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
detectAllHandlerMappings是DispatcherServlet的一个属性,你是可以在web.xml中配置的,默认是true,如果为true,则会去从本工程mvc-servlet.xml文件中去探测所有实现了HandlerMapping的bean,如果有,则加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings为false,则直接去容器中找id="handlerMapping"且实现了HandlerMapping的bean.如果以上都没找到,则会去加载默认的HandlerMapping。
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */	private boolean detectAllHandlerMappings = true;
本工程由于没有配置HandlerMapping,所以它会去加载默认的,下面看看默认的配置是什么
protected 
List
getDefaultStrategies(ApplicationContext context, Class
strategyInterface) { String key = strategyInterface.getName();//defaultStrategies存储了默认的配置 String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List
strategies = new ArrayList
(classNames.length); for (String className : classNames) { try { Class
clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err); } } return strategies; } else { return new LinkedList
(); } }
继续看看defaultStrategies是如何初始化的:
private static final Properties defaultStrategies;	static {		// Load default strategy implementations from properties file.		// This is currently strictly internal and not meant to be customized		// by application developers.		try {//这里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);		}		catch (IOException ex) {			throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());		}	}
这里使用静态代码块来加载配置文件DispatcherServlet.properties,它所在位置就是和DispatcherServlet同一目录下面的,如下图所示:
07101143_oPoi.png
该默认的配置文件的内容如下
# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver#这里就是默认的HandlerMapping的配置org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#这里就是默认的HandlerAdapter的配置org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
也就是说,当你什么都没有配置时,默认会加载以上的配置。正是由于有了上述默认配置的BeanNameUrlHandlerMapping(它要求name必须是以/开头的),它才会存储我们在mvc-servlet.xml中配置的<bean name="/index" class="com.lg.mvc.HomeAction"></bean>,同样正是由于有了SimpleControllerHandlerAdapter(由于handler实现了Controller接口,所以它的support方法支持我们的handler),才会调度执行HomeAction的handleRequest方法。

转载于:https://my.oschina.net/pingpangkuangmo/blog/376293

你可能感兴趣的文章
nullnullDataTable 排序
查看>>
Codeforces Ilya and Queries
查看>>
Viewport
查看>>
〖Linux〗Debian 7.1.0 Wheezy使用ltib报错的解决办法
查看>>
〖Android〗(how-to) fix k860/k860i buletooth.
查看>>
static与线程安全 -摘自网络
查看>>
jsf标签,jsp标签与jstl标签
查看>>
使用PHP CURL的POST数据
查看>>
struts2:表单标签
查看>>
mysql字符串截取
查看>>
ASP.NET MVC3 通过Url传多个参数方法
查看>>
遭遇sql server 2005 启动包未能正确加载需要重新安装错误,重装.NET FRAMEWORK经历分析...
查看>>
ASP.NET MVC上传文件----uploadify的使用
查看>>
简明 MongoDB 入门教程
查看>>
.NET Core 3.0中的数据库驱动框架System.Data
查看>>
北大AI公开课2019 | 雷鸣:人工智能革命与机遇
查看>>
英特尔开源计算机视觉数据标签工具CVAT,加速数据注释
查看>>
consule服务注册和发现 安装 部署
查看>>
多个帐户都用root 来登录 怎么看另一个用户使用的那些命令
查看>>
Map集合案例
查看>>