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的更多相关文章

  1. spring源码学习之springMVC(一)

    个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...

  2. 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程

    [一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven >  ...

  3. spring源码学习之springMVC(二)

    接着上一篇.继续来看springMVC中最和我们开发中接近的一部分内容: DispatcherServlet的逻辑处理 作者写到在DispatcherServlet类中存在doGet.doPost之类 ...

  4. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  5. spring源码学习之路---IOC初探(二)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...

  6. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  7. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  8. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  9. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

  10. Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

随机推荐

  1. rtf乱码解决办法

    首先,阐述下rtf,富文本格式文档,目前常用来做模板: 我遇到的问题是rtf中替换后的文本显示是正常的,rtf直接转pdf就不正常了,通过notpad++ 打开后发现rtf本身内容编码是我没有见过的( ...

  2. 安装卡巴 OFFICE链接 出现这个过程被中断,由于本机的限制

    今天 安装了卡巴后 office 超链接功能不能使用了,一点击超链接,就会发出警报,说”由于本机的限制,此操作已被取消,请与系统管理员联系“ 解决办法:1打开注册表2到这个位置:HKEY_CURREN ...

  3. 11-类中的__call__函数

    __call__是一个很神奇的特性,只要某个类型中有__call__方法,,我们可以把这个类型的对象当作函数来使用. 举例: >>>class Reader(): def __ini ...

  4. [English] Time complexity wise this solution is the best among all

    Time complexity wise this solution is the best among all, we can do all operations in O(1) time. 时间复 ...

  5. NetSec2019 20165327 Exp4 恶意代码分析

    NetSec2019 20165327 Exp4 恶意代码分析 一.实践目标 1.监控你自己系统的运行状态,看有没有可疑的程序在运行. 2.分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分 ...

  6. 容器虚拟网卡与网桥docker0虚拟网卡的veth pair的配对

    一)基本知识: Docker 安装时会自动在 host 上创建三个网络:none,host,和bridge:详细说明可参考其它文档.我们可用 docker network ls 命令查看: 基于DRI ...

  7. C# interface 的隐式与显示实现及适应范围源码演示

    把代码过程中经常用到的一些代码段做个记录,如下的资料是关于C# interface 的隐式与显示实现及适应范围演示的代码. interface IAnimal { void Dog(); } clas ...

  8. CORS在Spring中的实现

    CORS: 通常情况下浏览器禁止AJAX从外部获取资源,因此就衍生了CORS这一标准体系,来实现跨域请求. CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origi ...

  9. 【PYTHON】a-start寻路算法

    本文章适合黄金段位的LOL大神,同样更适合出门在外没有导航,就找不到家的孩子. 在英雄联盟之中,当你和你的队友都苦苦修炼到十八级的时候,仍然与敌方阵营不分胜负,就在你刚买好装备已经神装的时候,你看见信 ...

  10. TCP建立连接三次握手和释放连接四次握手

    TCP建立连接三次握手和释放连接四次握手     [转载]http://blog.csdn.net/guyuealian/article/details/52535294   在谈及TCP建立连接和释 ...