Spring源码学习(8)——SpringMVC
spring框架提供了构建Web应用程序的全功能MVC模块。通过实现servlet接口的DispatcherServlet来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上传下载文件支持。
SpringMVC的配置文件
1)配置web.xml
一个Web中可以没有web.xml文件,它主要用来初始化配置信息:例如welcome页面、servlet、servlet-mapping、filter、listener、启动加载级别等。但SpringMVC的实现原理是通过Servlet拦截所有URL来达到控制的目的,所以web.xml的配置是必须的。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"> <!-- Spring Listener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!-- Spring MVC Dispatcher Servlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list> </web-app>
这里面有两个关键的配置
1、contextConfigLocation:Spring容器的所在位置。这个参数是使Web与Spring的配置文件相结合的关键配置
2、DispatcherServlet:包含了SpringMvc的请求逻辑,Spring使用DispatcherServlet类拦截Web请求并进行相应的逻辑处理。
2)创建Spring配置文件applicationContext.xml
这里的配置就是用来存放应用所需的bean配置信息,和普通的ApplicationContext并无不同。
3)servlet的配置文件
默认的文件名就是servlet名+'_servlet.xml'
在Web启动时,服务器会加载对应于Servlet的配置文件,通常我们将Web部分的配置存放于此配置文件中。
容器之间的关系(个人理解)
这三个配置文件实际上对应的都是容器。首先web.xml对应的容器应该是tomcat加载web应用时首先加载的一个web容器。contextConfigLocation对应的是Spring的webApplicationContext,这是一个IOC容器,主要用来控制反转消除依赖用的。servlet对应的是servlet的容器
首先web容器中会通过listener持有IoC容器,这样web容器可以通过持有的IoC容器获取其中的bean。
其次web容器可以存放多个servlet,并不唯一,tomcat初始化servlet容器时,会将IoC容器作为此servlet容器的父容器,子容器将拥有访问父容器对象的权限,而父容器不可以访问子容器的权限。同时将其存放在web容器中。
1、ContextLoaderListener
我们首先先从web.xml开始。在编程方式的时候我们可以直接将配置信息作为参数传入Spring容器中,但在Web下,我们需要通过context-param的方式注册并使用ContextLoaderListener进行监听读取配置
ContextLoaderListener的作用就是启动Web容器的时候,自动装配applicationContext的配置信息。因为它实现了ServletContextListener这个接口,在启动容器的时候会默认执行它实现的方法,通过这个接口,我们可以在应用处理请求之前向servletContext中(也就是web容器)添加任意对象,这个对象在servletContext启动时被初始化,在整个servletContext运行期间都是可见的。
servletContext在启动之后会调用ServletContextListener的contextInitialized方法
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if(logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
} long startTime = System.currentTimeMillis(); try {
if(this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
} if(this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext err = (ConfigurableWebApplicationContext)this.context;
if(!err.isActive()) {
if(err.getParent() == null) {
ApplicationContext elapsedTime = this.loadParentContext(servletContext);
err.setParent(elapsedTime);
} this.configureAndRefreshWebApplicationContext(err, servletContext);
}
} servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader err1 = Thread.currentThread().getContextClassLoader();
if(err1 == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if(err1 != null) {
currentContextPerThread.put(err1, this.context);
} if(logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
} if(logger.isInfoEnabled()) {
long elapsedTime1 = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime1 + " ms");
} return this.context;
} catch (RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
}
这个方法做了几件事情
1、WebApplicationContext存在性验证
如果servletContext中已经包含了此WebApplicationContext的话,就会抛出异常
2、创建WebApplicationContext实例,通过createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class contextClass = this.determineContextClass(sc);
if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter("contextClass");
if(contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
首先先确定WebApplicationContext的类型,在determineContextClass方法中可以看到,如果配置中有contextClass时,则使用此类型,若没有,就是用默认的类型,该类型在defaultStrategies下可以找到
我们可以在ClassLoader.properties文件中看到默认类型是XmlWebApplicationContext
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
确定了context的类型后,就会使用反射实例化容器,之后再调用configureAndRefreshWebApplicationContext方法读取容器配置
3、将实例记录在servletContext中
存放在servletContext的property中,对应的键为 WebApplicationContext.class.getName() + ".ROOT"
4、映射当前的类加载器与创建的实例到全局变量currentContextPerThread中
2、DispatcherServlet
servlet在初始化阶段首先会调用其init方法,我们可以再DispatcherServlet的父类HttpServletBean找到方法的实现
public final void init() throws ServletException {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Initializing servlet \'" + this.getServletName() + "\'");
} HttpServletBean.ServletConfigPropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if(!pvs.isEmpty()) {
try {
BeanWrapper ex = PropertyAccessorFactory.forBeanPropertyAccess(this);
ServletContextResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
ex.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(ex);
ex.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if(this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet \'" + this.getServletName() + "\'", var4);
} throw var4;
}
} this.initServletBean();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Servlet \'" + this.getServletName() + "\' configured successfully");
} }
1、首先是对配置中的初始化参数进行封装,也就是servlet中配置的<init-param>,将其封装至propertyValue中。
2、将当前的servlet实例转换成beanWrapper实例,PropertyAccessorFactory.forBeanPropertyAccess方法是spring中提供的工具方法,主要用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例
3、注册Resource的属性编辑器,即在创建Bean的过程中一旦遇到了Resourcel类型的注入就使用ResourceEditor去解析。
4、将之前封装的propertyValues放到生成的BeanWrapper中。
5、接下来就是servletBean的初始化initServletBean()
初始化ServletBean的逻辑在FrameworkServlet中
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring FrameworkServlet \'" + this.getServletName() + "\'");
if(this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet \'" + this.getServletName() + "\': initialization started");
} long startTime = System.currentTimeMillis(); try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (ServletException var5) {
this.logger.error("Context initialization failed", var5);
throw var5;
} catch (RuntimeException var6) {
this.logger.error("Context initialization failed", var6);
throw var6;
} if(this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet \'" + this.getServletName() + "\': initialization completed in " + elapsedTime + " ms");
} }
1、WebApplicationContext的初始化
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if(this.webApplicationContext != null) {
wac = this.webApplicationContext;
if(wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext attrName = (ConfigurableWebApplicationContext)wac;
if(!attrName.isActive()) {
if(attrName.getParent() == null) {
attrName.setParent(rootContext);
} this.configureAndRefreshWebApplicationContext(attrName);
}
}
} if(wac == null) {
wac = this.findWebApplicationContext();
} if(wac == null) {
wac = this.createWebApplicationContext(rootContext);
} if(!this.refreshEventReceived) {
this.onRefresh(wac);
} if(this.publishContext) {
String attrName1 = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName1, wac);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet \'" + this.getServletName() + "\' as ServletContext attribute with name [" + attrName1 + "]");
}
} return wac;
}
首先当前的servlet会从servletContext中的缓存中取到WebApplicationContext,就是我们先前通过监听器添加的WebApplicationContext,对应的Key为WebApplicationContext.class.getName() + ".ROOT"
取到的容器是dispacher-servlet的容器的父容器。
接下来就是寻找及创建当前servlet的WebApplicationContext了
1、通过构造函数的的注入进行初始化
如果当前的servlet已经有了WebApplicationContext的话,说明这是通过构造函数传入的容器,那么接下来就会将之前找到的rootApplicationContext设置为其父容器,并执行configureAndRefreshWebApplicationContext方法
2、通过contextAttribute进行初始化
如果在web.xml中的servlet设置了contextAttribute这个参数,那么这个容器在servletContext中存放的键名就是这个参数的值。因此接下来就会获取这个容器
protected WebApplicationContext findWebApplicationContext() {
String attrName = this.getContextAttribute();
if(attrName == null) {
return null;
} else {
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName);
if(wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
} else {
return wac;
}
}
}
3、如果上述两步还是没有找到对应的容器,那么就要重新实例化一个WebApplicationContext了
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class contextClass = this.getContextClass();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name \'" + this.getServletName() + "\' will try to create custom WebApplicationContext context of class \'" + contextClass.getName() + "\', using parent context [" + parent + "]");
} if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name \'" + this.getServletName() + "\': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(this.getContextConfigLocation());
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
实例化的逻辑和之前的很相似,这里就不再赘述了。从这边我们可以看到最后也执行了configureAndRefreshWebApplicationContext方法。
configureAndRefreshWebApplicationContext
这个方法用来会已经实例化的WebApplicationContext进行配置及刷新
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if(ObjectUtils.identityToString(wac).equals(wac.getId())) {
if(this.contextId != null) {
wac.setId(this.contextId);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
}
} wac.setServletContext(this.getServletContext());
wac.setServletConfig(this.getServletConfig());
wac.setNamespace(this.getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener(null)));
ConfigurableEnvironment env = wac.getEnvironment();
if(env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
} this.postProcessWebApplicationContext(wac);
this.applyInitializers(wac);
wac.refresh();
}
方法里主要对设置了一些属性,例如servletContext,servletConfig等,然后就是一些后处理器的执行:postProcessWebApplicationContext是一个空方法,为了支持拓展,applyInitializers方法则是找到在web.xml配置的初始化参数globalInitializerClasses,并执行其中ApplicationContextInitializer的initialize方法。
接下来就是refresh()方法了
这是一个模板方法,在之前将ApplicationContext容器的时候也说到过,不过之前只是对classPathXmlApplicationContext进行了分析,webApplicationContext的实现逻辑其实也类似
接下来到了刷新onRefresh,这也是一个模板方法
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
1、初始化MultipartResolver
MultipartResolver主要用来处理文件上传的,默认情况下Spring是没有multipart处理的。如果要用的话,需要在容器中添加multipart解析器,这样每个请求都会被检查是否包含multipart,并使用容器中定义的resulver来解析它,常用配置如下:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxInMemorySize" value="100000"/>
</bean>
解析器就是在initMultipartResolver方法中,被设置到此servlet中。
2、初始化LocalResolver
支持国际化配置
3、初始化ThemeResolver
支持主题功能
4、初始化HandlerMappings
当客户端发起Request时,DispatcherServlet会将Request提交给HandlerMapping,然后根据配置回传给相应的controller。
默认情况下,spring会加载所有实现handlerMapping接口的Bean,如果只期望Spring加载指定的HandlerMapping,那么可以修改Web.xml中DispatcherServlet的初始化参数,将detectAllHandlerMappings设置为false.
这样Spring会查找名为handlerMapping的bean,若没有找到对应bean,spring会在DispatcherServlet.property中查找org.Springframework.web.servlet.HandlerMapping的内容来加载HandlerMapping
5、初始化HandlerAdapters
支持适配器,将Http请求对象和响应对象传递给Http请求处理器。
6、初始化HandlerExceptionResolvers
提供异常处理
7、初始化RequestToViewNameTranslator
8、初始化ViewResolver
9、初始化FlashMapManager
提供请求储存属性
DispatcherServlet的逻辑处理
在HttpServlet类中分别提供了相应的服务方法,例如doDelete() doGet()、doOptions()、doPost()、doPut()、doTrace(),根据不同的请求方式,servlet会引导至对应的方法中去。对于DispatcherServlet来说,对于不同的请求方式,都是统一交给processRequest()这一个方法来处理的。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Object failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = this.buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
this.initContextHolders(request, localeContext, requestAttributes); try {
this.doService(request, response);
} catch (ServletException var17) {
failureCause = var17;
throw var17;
} catch (IOException var18) {
failureCause = var18;
throw var18;
} catch (Throwable var19) {
failureCause = var19;
throw new NestedServletException("Request processing failed", var19);
} finally {
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if(requestAttributes != null) {
requestAttributes.requestCompleted();
} if(this.logger.isDebugEnabled()) {
if(failureCause != null) {
this.logger.debug("Could not complete request", (Throwable)failureCause);
} else if(asyncManager.isConcurrentHandlingStarted()) {
this.logger.debug("Leaving response open for concurrent processing");
} else {
this.logger.debug("Successfully completed request");
}
} this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
} }
从中我们可以看出具体的逻辑细节转移到了doService方法中了,但在这之前还是做了一些准备和处理工作的、
1、首先提取了当前线程的LocaleContext以及RequestAttribute属性
2、根据当前线程创建对应的localeContext和RequestAttribute,并绑定到当前线程
3、doService做具体逻辑
4、请求处理结束后恢复线程到原始状态
5、请求结束后无论成功与否发布事件通知
接下来看doService的逻辑
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if(this.logger.isDebugEnabled()) {
String attributesSnapshot = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()?" resumed":"";
this.logger.debug("DispatcherServlet with name \'" + this.getServletName() + "\'" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
} HashMap attributesSnapshot1 = null;
if(WebUtils.isIncludeRequest(request)) {
attributesSnapshot1 = new HashMap();
Enumeration inputFlashMap = request.getAttributeNames(); label108:
while(true) {
String attrName;
do {
if(!inputFlashMap.hasMoreElements()) {
break label108;
} attrName = (String)inputFlashMap.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet")); attributesSnapshot1.put(attrName, request.getAttribute(attrName));
}
} request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
FlashMap inputFlashMap1 = this.flashMapManager.retrieveAndUpdate(request, response);
if(inputFlashMap1 != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap1));
} request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try {
this.doDispatch(request, response);
} finally {
if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot1 != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot1);
} } }
实际上这个方法还是在做一些准备工作,包括生成request的属性快照、设置各种resolver等
完整的请求处理过程在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 {
try {
ModelAndView err = null;
Object dispatchException = null; try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
} HandlerAdapter err1 = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if(isGet || "HEAD".equals(method)) {
long lastModified = err1.getLastModified(request, mappedHandler.getHandler());
if(this.logger.isDebugEnabled()) {
this.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;
} err = err1.handle(processedRequest, response, mappedHandler.getHandler());
if(asyncManager.isConcurrentHandlingStarted()) {
return;
} this.applyDefaultViewName(processedRequest, err);
mappedHandler.applyPostHandle(processedRequest, response, err);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
} this.processDispatchResult(processedRequest, response, mappedHandler, err, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
} } finally {
if(asyncManager.isConcurrentHandlingStarted()) {
if(mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if(multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
} }
}
1、首先会先检查是否是文件上传
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if(this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if(WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");
} else if(this.hasMultipartException(request)) {
this.logger.debug("Multipart resolution failed for current request before - skipping re-resolution for undisturbed error rendering");
} else {
try {
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException var3) {
if(request.getAttribute("javax.servlet.error.exception") == null) {
throw var3;
}
} this.logger.debug("Multipart resolution failed for error dispatch", var3);
}
} return request;
}
如果配置了MultipartResolver,并且当前request是multipartContent类型的话,则转换MultipartHttpServletRequest,并且调用MultipartResolver的resolverMultipart方法对文件做解析。
2、根据request信息寻找对应的Handler
spring默认的handler是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping这两个
如果在配置中配置了<mvc:annotation-driven />的话,会使用RequestMappingHandlerMapping和BeanNameUrlHandlerMapping这两个Handler
我们看一下RequestMappingHandlerMapping的实现逻辑
首先spring会遍历所有的handler的getHandler()方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = this.getHandlerInternal(request);
if(handler == null) {
handler = this.getDefaultHandler();
} if(handler == null) {
return null;
} else {
if(handler instanceof String) {
String executionChain = (String)handler;
handler = this.getApplicationContext().getBean(executionChain);
} HandlerExecutionChain executionChain1 = this.getHandlerExecutionChain(handler, request);
if(CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
CorsConfiguration config = globalConfig != null?globalConfig.combine(handlerConfig):handlerConfig;
executionChain1 = this.getCorsHandlerExecutionChain(request, executionChain1, config);
} return executionChain1;
}
}
根据request找到对应的handler
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Looking up handler method for path " + lookupPath);
} this.mappingRegistry.acquireReadLock(); HandlerMethod var4;
try {
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
if(this.logger.isDebugEnabled()) {
if(handlerMethod != null) {
this.logger.debug("Returning handler method [" + handlerMethod + "]");
} else {
this.logger.debug("Did not find handler method for [" + lookupPath + "]");
}
} var4 = handlerMethod != null?handlerMethod.createWithResolvedBean():null;
} finally {
this.mappingRegistry.releaseReadLock();
} return var4;
}
这里首先获取了lookupPath对应的就是请求的uri
接下来根据缓存中解析得到的mapping得到对应的HandlerMethod
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = handler instanceof HandlerExecutionChain?(HandlerExecutionChain)handler:new HandlerExecutionChain(handler);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
Iterator var5 = this.adaptedInterceptors.iterator(); while(var5.hasNext()) {
HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
if(interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
} return chain;
}
然后将配置中的对应拦截器加入到责任链中。
3、找到对应的HandlerAdapter
找到handlerAdaptor之后会调用其handle方法处理逻辑,对于之前提到的RequestMappingHandlerMapping对应的RequestMappingHandlerAdapter来说,就是执行之前的handlerMethod
Spring源码学习(8)——SpringMVC的更多相关文章
- spring源码学习之springMVC(一)
个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...
- 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程
[一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven > ...
- spring源码学习之springMVC(二)
接着上一篇.继续来看springMVC中最和我们开发中接近的一部分内容: DispatcherServlet的逻辑处理 作者写到在DispatcherServlet类中存在doGet.doPost之类 ...
- spring源码学习之路---深入AOP(终)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...
- spring源码学习之路---IOC初探(二)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...
- Spring源码学习
Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件
写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...
随机推荐
- XML 与 XML Schema的使用教程
引言:我写本文的宗旨在于给需要使用XML,而又对XML不是很熟悉的人们提供一种使用思路,而不没有给出具体的 使用方法,至于下文中提到的使用方法,还未尝试过,都是从网上整理而来! 一.概述 什么 ...
- Asp.net core Identity + identity server + angular 学习笔记 (第一篇)
用了很长一段时间了, 但是一直没有做过任何笔记,感觉 identity 太多东西要写了, 提不起劲. 但是时间一久很多东西都记不清了. 还是写一轮吧. 加深记忆. 这是 0-1 的笔记, 会写好多篇. ...
- 亚马逊促销活动Promotion②:Money Off(满减折扣)的设置教程
满减.折扣是放之四海皆有效的促销手段,虽然亚马逊对卖家有诸多限制,但这个促销方式却是允许的,对亚马逊的卖家而言,这对提升商品销量.打造爆款都是极好的.今天小编来讲讲亚马逊的Money Off要怎么设置 ...
- yii2部署nginx
页面全部提示404,nginx平台下需要额外配置yii rewrite规则,配置如下: 在nginx 的配置文件nginx.conf //增加部分 location / { # Redirect ev ...
- 深入了解UML类图
深入浅出UML类图 在UML 2.0的13种图形中,类图是使用频率最高的UML图之一.Martin Fowler在其著作<UML Distilled: A Brief Guide to the ...
- 【js】【图片显示】js控制html页面显示图片方式
js控制html页面显示图片方式,只需要引入“jquery-1.11.2.min.js” js: /* 引用 <script src="jquery-1.11.2.min.js&quo ...
- hashlib 和 hmac 算法的区别
-----md5 = hashlib.md5() md5.update(password+salt) md5.hexdigest() ----- h = hmac.new(key,password,d ...
- python3 LDA主题模型以及TFIDF实现
import codecs #主题模型 from gensim import corpora from gensim.models import LdaModel from gensim import ...
- 关于HttpClient上传中文乱码的解决办法
使用过HttpClient的人都知道可以通过addTextBody方法来添加要上传的文本信息,但是,如果要上传中文的话,或还有中文名称的文件会出现乱码的问题,解决办法其实很简单: 第一步:设置Mult ...
- DOM 操作成本究竟有多高,HTML、CSS构建过程 ,从什么方向出发避免重绘重排)
前言: 2019年!我准备好了 正文:从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM.尤其是React.vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQ ...