1 Servlet功能分析
我们知道,WEB应用一般是打包成WAR包然后部署到Tomcat等应用服务器上去的;部署完成后,通过URL访问时,一般是http://ip:port/app这种访问路径。Tomcat根据路径中端口后的第一个单词并结合该应用的web.xml文件,可以找到哪个应用的哪个Servlet需要处理该URL请求。
在Spring之前,如果要开发一个处理Http请求的功能,需要做两项工作:一是开发继承于Servlet类的业务处理Servlet;二是在Web.xml中注册并映射该Servlet处理的请求路径。
我们先来创建一个简单原始的测试工程(通过Itellij测试),以了解原始Servlet的配置及生效过程,GITHUB地址:https://github.com/icarusliu/learn
1.1 原始的WEB工程
第一步:配置Itellij Tomcat运行环境(Tomcat9):
第二步:通过Itellij创建一个空白工程learn;
第三步:添加Module,名称为testWebApp;
Mvn初始化完成后的工程结构如下所示:
第四步:构建工程,按下图进行:
第五步:运行Tomcat,将会跳转到首页;
第六步:定义测试Servlet
此处使用Kotlin,并引入log4j等日志组件,具体的MVN配置文件见GITHUB工程。
Servlet代码见下文,需要继承自HttpServlet;
class TestServlet: HttpServlet() {
val logger: Logger = LoggerFactory.getLogger(TestServlet::class.java)
override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
logger.info("Test servlet begin to execute!")
response.outputStream.println("TestServlet")
logger.info("Test servlet has been executed!")
}
}
该Servlet实现很简单的向前台返回TestServlet字符串的过程;
此时Servlet定义即完成;但仅仅这样还不够,Tomcat并不知道什么情况下来执行这个Servlet; 这个时候就需要在Web.xml文件中进行Servlet的配置了,具体的配置见下文:
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.liuqi.learn.testWebApp.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
以上配置主要包含两个,一是Servlet的定义,名称以及它后台对应的Java类;二是Servlet与URL的映射关系,这里表示将它映射到/test这个地址上。
这个时候我们启动Tomcat,然后打开链接:http://localhost:8080/test,就会看到浏览器输出的了内容为TestServlet的页面。
经过以上几个步骤,一个最原始的不使用任何框架的WEB应用即构建完成。
经过以上分析,WEB应用一般就是一系列Servlet的集合;想象下,如果我们要完成A与B两个功能,那我们就可以定义两个Servlet并分别进行路径映射; 但这样的话功能越多Servlet就会越来越多,配置文件也越来越复杂;如果在此基础上进行抽象,Servlet只定义一个,在Servlet内部再根据URL进行功能分发,这样就会简单很多了。
SpringMVC实际就是这样做的,它定义了一个名称为DispatcherServlet的Servlet,用于接收请求并进行分发处理。
1.2 SpringMVC配置
上文已经分析了,SpringMVC的入口实际就是实现了一个名称为DispatcherServlet的Servlet。
使用Spring而非Spring Boot的实现时,需要在Web.xml中配置该DispatcherServlet;
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
使用Spring Boot的加载过程将会在后续进行分析,此处暂不涉及。
1.3 Itellij查找方法实现过程
在Java类中,可能出现A继承B,B继承C,C继承D;D中有多个方法,有的在C中实现,有的在B,而有的在C;
像这种情况,在A中需要查找某个方法的实现在其父类的哪个类中,可通过Ctrl+F12的快捷键来实现,其界面如下:
如需要在DispatcherServlet中查找Service的实现,我们按快捷键弹出查找窗口后,输入Service,会将名称中包含Service的方法全部查找出来;如果选中了”Inherited members”,则其所有父类中符合条件的方法全部都会搜索到,再鼠标选中记录后就可以方便的跳转到需要查找的方法实现的地方。
如上图中表示Service在FrameworkServlet及HttpServlet中都有实现,通过查看这两个地方可以清楚的知道FrameworkServlet中的实现为最终实现。
这个操作在阅读Spring源码时会经常使用到,后续不再说明。
2 SpringMVC分析
2.1 关键类分析
DispatcherServlet收到请求后,根据它本身所维护的URL路径映射关系,决定调用哪个Controller来进行请求响应;其中涉及到的关键类见下表:
类 | 说明 |
---|---|
HandlerMapping | 将请求映射到相应的处理器(Controller),在执行实际处理器前后会执行一系列的前置和后置拦截器; |
HandlerAdapter | 调用真正需要调用的处理器的处理方法; |
ViewResolver | 将Handler执行后返回的字符串视图名称映射到实际的页面或者其它的视图上去; |
WebApplicationContext | 为Web应用提供配置功能; |
ServletContext | 提供了一系列方法以供Servlet与应用服务器之间进行交互;每个应用每个虚拟机启动一个ServletContext对象; 因此不同Tomcat中的ServletContext是不能共享的; |
我们先来看下几个关键类在响应请求时是如何相互合作的:
在后面的分析中我们将会看到这个请求响应过程是如何进行的。
2.2 生效过程源码分析
Servlet的关键方法为init与service;Init方法用于初始化ApplicationContext、BeanFactory等对象; 而Service用于响应请求。 我们将针对DispatcherServlet的init与service方法进行分析,以了解SpringMVC的初始化及请求响应过程。
2.2.1 初始化
我们先来看DispatcherServlt的Init方法。
2.2.1.1 init
经过源码阅读,DispatcherServlet的init方法实际在ServletBean中定义,其实现如下:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
实际调用initServletBean方法;该方法在DispatcherServlet的父类FrameworkServlet中定义:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
其关键调用为initWebApplicationContext
2.2.1.2 initWebApplicationContext
该方法主要用于初始化WebApplicationContext,实现BeanFactory的初始化、Bean配置的加载等功能,其实现如下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
实际就是对DispatcherServlet的webApplicationContext属性进行配置与刷新;
其关键过程就是如果属性为空时,通过createWebApplicationContext创建属性,此时实例化的实际类名为contextClass属性所指定的类;该属性的初始化是在哪个地方进行的?
跟踪该属性,我们可以看到在FrameworkServlet中有定义:
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
实际DEFAULT_CONTEXT_CLASS的值为: XmlWebApplicationContext.class
也就是,如果没有特殊配置,默认的ContextClass为XmlWebApplicationContext类;
该对象默认从名为/WEB-INF/applicationContext.xml的文件中加载Bean定义;
其refresh方法定义如下:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
其主要过程:
invokeBeanFactoryPostProcessors: 在Bean初始化前配置BeanFactory中的属性,可修改Bean的定义属性;
registerBeanPostProcessors:从容器中查找BeanPostProcessors的Beans并进行注册;
onRefresh: 初始化Servlet的一些属性;
初始化完成后,如果调用ApplicationContext中的GetBean方法时,实际调用的是BeanFactory中的GetBean方法进行Bean的实例化等过程;关于Bean实例化的具体过程后续再进行分析。
2.2.1.3 onRefresh
我们来重点分析下DispatcherServlet的onRefresh方法,通过阅读源代码,可以发现其调用的实际为initStrategies方法,其实现如下:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
可以看到在这个地方会初始化HandlerMappings、HandlerAdapters、ViewResolvers等属性;
我们先来看下HandlerMapping的初始化:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.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属性,则查找所有类型为HandlerMapping的Bean;如果没有配置则只查找名称为handlerMapping的Bean; 如果两个都没有查找到则使用默认的HandlerMapping类。那默认的Handler在哪进行配置的?
跟踪getDefaultStrategies方法,其逻辑就是从DispatcherServlet.properties文件中查找默认的配置项; 这个文件的内容如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
可以看到HandlerMapping如果用户没有进行配置的话,则使用默认的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping两个HandlerMapping。
HandlerAdapter等的初始化过程与HandlerMappings一致,不再赘述。
2.2.2 请求响应
Servlet的请求响应是通过Service方法来实现的;我们先来分析Service方法的实现;
2.2.2.1 Service方法实现分析
我们先来跟踪Service方法的实现,在DispatcherServlet中已经没有没有Service方法定义,查找其父类,查找到最终实现在HttpServlet中,其关键代码段如下:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
...
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
可见HttpServlet中实际调用的是doGet/doPost等方法;
再跟踪doGet等方法的实现,以doGet为例,实际定义在FrameworkServlet中:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
实际调用为processRequest方法,其关键代码段如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
doService(request, response);
...
}
可以看到实际调用的是doService方法,而这个方法的实现就在DispatcherServlet中:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
该方法主要是为Request设置了一系列的属性,最终调用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);
// 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 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;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
此处看到,之前所介绍的几个关键类已经开始起作用了;
该方法即为Service生效的关键过程,分析该方法执行流程如下:
接下来我们就上述流程中几个关键的过程进行分析。
2.2.2.2 Handler查找过程
handler查找通过DispatcherServlet中的findHandler方法进行:
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;
}
其中,HandlerMappings这个属性的初始化过程在2.2.1.3中已分析,我们知道默认情况下如果用户未配置HandlerMapping类时使用默认的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping,查看DefaultAnnotationHandlerMapping类,看到这个类已经废弃了,其建议是使用RequestMappingHandlerMapping类,而这个类是注解中主要使用的RequestMapping注解的处理类,因此此处我们主要分析RequestMappingHandlerMapping这个类的实现及生效过程。
我们从其getHandler方法开始分析;其getHandler方法在其父类的AbstractHandlerMapping中实现,其实现如下:
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;
}
这个方法主要做了两个事情,一是调用getHandlerInternal获取Handler;二是调用getHandlerExecutionChain获取生效的各个拦截器并组装成HandlerExecutionChain并返回;
跟踪getHandlerInternal实现,其实现在类AbstractHandlerMethodMapping中,实际上调用了lookupHandlerMethod方法来获取HandlerMethod:
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);
}
}
其过程为先从mappingRegistry中获取RequestMappingInfo对象,然后将所有的RequestMappingInfo对象转换成Match对象;如果找到的Match对象列表不为空,则返回其第一个Match元素的HandlerMethod属性;
这个地方逻辑有点绕;我们先来看下mappingRegistry属性,这个属性对象类型为MappingRegistry对象,主要包含了一系列的Map来存储数据。 它的初始化过程在方法initHandlerMethods中进行:
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());
}
实际上就是找出所有满足isHandler方法的所有Bean,检测其所有满足条件的Methods,最终注册到mappingRegistry中去。
isHandler的判断依据,其实现在RequestMappingHandlerMapping中:
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
可以看到如果Bean被Controller或者是RequestMapping注解,则认为它是一个Handler。
而detectHandlerMethods方法完成的功能就是检测满足条件的Methods,并将其注册到mappingRegistry属性中去;
至此Handler的查找过程已经分析完毕。
2.2.2.3 HandlerAdapter查找过程
回到doDispatcher方法,在查找Handler完成后即调用getHandlerAdapter(DispatcherServlet)来查找HandlerAdapter:
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");
}
就是从handlerAdapters中查找合适的HandlerAdapter并返回。在2.2.1.3中我们已经分析了handlerAdapters的加载过程,其加载的默认的HandlerAdapter为HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、AnnotationMethodHandlerAdapter
查看AnnotationMethodHandlerAdapter发现其已建议被RequestMappingHandlerAdapter替换。
2.2.2.4 Handler执行过程
查找到HandlerAdapter及Handler后就简单了,最终就是通过HandlerAdapter调用到了Handler的handle方法;最终返回ModelAndView对象。
而前置及后置拦截器也就是在handle方法执行前后执行的。
参考文档:
1 https://docs.spring.io/spring/docs/5.0.2.RELEASE/spring-framework-reference/web.html#mvc-servlet