前言
转载请注明来源
上一次写博客已经是5月份的事了,其实是蛮想抽时间写写,沉淀一下自己,无奈,天天加班加点,人都傻了,心累.真心觉得,对于一个搞技术的人来说,不能只是工作工作,虽然这是必要的(毕竟大多数人还是要吃饭的),但是经常的周期性的沉淀下自己,总结自己一段时间的收获是分重要的.
话不多说了,本篇主要是针对一下springMVC的处理流程及实现原理通过debug方式层层深入理解.
整个篇幅过长,偏向于个人的一个debug过程的展示,以及个人理解的笔记,感兴趣的可以参照我的流程进行debug,加上一些我个人的理解与提示,多debug几遍流程,相信看的人都能得到自己的理解.
服务器原理简介
springMVC作为现在Java这块非常流行的MVC框架,基本上只要会spring就可以无缝衔接springMVC,
那么springMVC到底是如何工作的呢,这里不得不提的是服务器,相信在Java Web这块的程序员们都非常的熟悉.
正好最近撸了点tomcat源码的皮毛,了解了类tomcat等的web服务器的工作原理,有兴趣的可以吃一吃我的安利
<> 这本书.
那么为什么需要服务器呢?
简单来讲,服务器通过ServerSocket获取到Http请求然后对其解析并封装成Request和Response对象,
然后将其交给Servlet容器进行处理,选择对应的Servlet处理请求,返回结果(实际上是很复杂,作为一个web
程序员,这个真的是应该好好了解研究的).
那么为什么tomcat和springmvc可以结合起来呢,最最核心的原因是他们都是基于Servlet规范的,
由于Servlet规范,他们可以互相通信(服务器和SpringMVC的结合在debug时将会简单体现).
SpringMVC
开始详解SpringMVC了.
1、web.xml
web.xml中配置了最重要的ContextLoaderListener以及DispatchServlet.
ContextLoaderListener用于启动web容器时,自动装ApplicationContext的配置信息,
由于 ContextLoaderListener实现了ServletContextListener,所以在web容器启动应用时,
创建ServletContext对象,每个应用都有一个对应的ServletContext对象,ServletContext在应用关闭
时将会销毁,在启动时,可以向ServletContext中添加WebApplicationContext,这个在ServletContext
整个运行期间都是可见的.
DispatchServlet是SpringMVC最重要的一个类,它实现了Servlet,用于对请求做逻辑处理,相应的
ContextLoaderListener实际上只是为了创建WebApplicationContext,DispatchServlet则是负责了
SpringMVC中对客户端请求的逻辑处理,我们的每个请求都是经过DispatchServlet进行处理,调用对应的
逻辑实现(Controller中对应的请求的方法),返回视图,所以说DispatchServlet是SpringMVC中最重要的一个
类一点不为过.
2、启动
终于要接触代码了,下面就对springMVC启动过程的流程与逻辑进行debug。
本文采用的是Jetty服务器.
如下所示,web.xml中配置的监听器类:
image.png
可以看到,该类实现了ServletContextListener,ServletContextListener接口,当系统启动时,将会调用ServletContextListener的实现类的contextInitialized方法,用于在初始化时加入一些属性,这样就可以在全局范围内调用这些属性.
debug启动web工程,直入主题,在contextInitialized方法出打断点:
image.png
继续跳入父类ContextLoader中的initWebApplicationContext方法中进行WebApplicationContext的创建,WebApplicationContext是继承自ApplicationContext,在ApplicationContext的基础上增加了一些特定于Web的操作及属性,详细可以自行查看.
下面是整个initWebApplicationContext方法的详细代码,加入了一点个人理解的注释:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//验证context是否已经存在,
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!");
}
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 {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
//初始化 WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//刷新上下文环境
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将WebApplicationContext记录在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
//映射当前的类加载器与context实例到全局变量currentContextPerThread中
currentContextPerThread.put(ccl, 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 elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
debug到下图280行,创建WebApplicationContext,
image.png
debug 跳入createWebApplicationContext(servletContext)方法中,
image.png
determineContextClass方法返回一个WebApplicationContext 接口的实现类,否则默认返回XmlWebApplicationContext 或者一个指定的context
image.png
此处有一个defaultStrategies,可以看下图,ContextLoader有一个static代码块,
image.png
image.png
image.png
通过以上我们可以得知,在ContextLoader类加载的时候就先读取了ContextLoader同级目录下的ContextLoader.properties配置文件,在初始化WebApplicationContext时,根据其中的配置提取WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建.
image.png
接着debug走,将WebApplicationContext记录在servletContext中
image.png
映射当前的类加载器与context实例到全局变量currentContextPerThread中
image.png
初始化servlet
SpringMVC通过DispatcherServlet对请求进行处理,而DispatcherServlet是实现了Servlet接口的,而servlet接口是基于servlet规范编写的一个Java类,实际上一个servlet会经历三个阶段:初始化阶段、运行阶段、销毁阶段,也就是我们熟知的servlet 的init、doGet/doPost、destroy这三个方法的执行,而dispatchservlet是实现了servlet接口的,那么必然也会经历这三个阶段,下面是DispatchServlet类的父类结构图:
image.png
可以看到dispatServlet的父类FrameworkServlet,FrameworkServlet又继承了HttpServletBean,实际上整个servlet的三个阶段都在这三个类中做了具体的实现。
初始化阶段在HttpServletBean中可以找到相应的方法,
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
//解析init parameters 并封装到PropertyValues中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//将当前这个Servlet类转化为BeanWrapper,从而能以spring的方式对init parameters的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,一旦遇到resource类型的属性将会使用ResourceEditor进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空实现,留给子类覆盖
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//在FrameworkServlet中覆盖了该方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
debug进入init方法中,跳入FrameworkServlet的initServletBean方法中,如下图
image.png
可以看到,最重要的就是this.webApplicationContext = initWebApplicationContext();
这段代码,这个方法的作用是创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化:
image.png
image.png
可以看到上图这段代码将不会执行,直接到if(wac == null)中去了,
image.png
跳入findWebApplicationContext方法中,这个方法是用于根据ContextAttribute属性加载WebApplicationContext,但这里可以看到ContextAttribute为空,所以这段代码最终返回的还是null
image.png
image.png
image.png
接着走下一个if,
image.png
image.png
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取servlet初始化参数,如果没有则默认为XMLWebApplicationContext.class
Class> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + 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 '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通过反射的方式实例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置servlet环境
wac.setEnvironment(getEnvironment());
//这个parent使用的就是ContextLoaderListener初始化时创建的那个root WebApplicationContext
wac.setParent(parent);
//获取ContextConfigLocation属性,配置在servlet初始化参数中
wac.setConfigLocation(getContextConfigLocation());
//初始化spring环境包括加载配置文件等
configureAndRefreshWebApplicationContext(wac);
return wac;
}
上面是对createWebApplicationContext方法的一个详细介绍,下面debug一步一步看这个方法的逻辑:
image.png
image.png
image.png
接着创建wac并配置了servlet初始化的一些参数后,初始化整个spring的环境:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// the wac environment's #initPropertySources will be called in any case when
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//加载配置文件及整合parent到wac中
wac.refresh();
}
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) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
上述代码每一步都有自带的注释,相信很容易就能理解,其中的onRefresh方法是FrameworkServlet类中提供的模板方法,在子类dispatchservlet中进行了重写,其主要作用就是为了刷新spring在web功能实现中必须使用的全局变量,这些变量在接下来的处理请求响应中将会用到,如下,就不详细介绍了
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
*
May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
/**
* 初始化MultipartResolver,主要用于处理文件上传,默认情况下,spring是没有Multipart处理的
* 需要用户自己去配置,常用配置如下:
*
* class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
*
*
*
*
*/
initMultipartResolver(context);
/**
* 初始化LocaleResolver,用于国际化配置
*/
initLocaleResolver(context);
/**
* 初始化ThemeResolver,主题theme用于控制网页风格
*/
initThemeResolver(context);
/**
* 初始化HandlerMappings,dispatchservlet会将Request请求提交给HandlerMapping然后HandlerMapping
* 根据webapplicationcontext的配置来返回给dispatchservlet 相应的controller
* 默认情况下,springMVC会加载当前系统中所有实现了HandlerMapping接口的bean
*/
initHandlerMappings(context);
/**
* 初始化HandlerAdapters,适配器设计模式,HandlerAdapter适配当前请求到对应的控制器,
*
*/
initHandlerAdapters(context);
/**
* 初始化HandlerExceptionResolvers,
*/
initHandlerExceptionResolvers(context);
/**
* 初始化RequestToViewNameTranslator,当controller处理方法没有返回一个view或者逻辑视图名称时,并且
* 没有在该方法中直接往response的输出流中写数据时,就会通过RequestToViewNameTranslator接口的实现类
* 来提供一个约定好的逻辑视图名称供使用,spring中提供了一个默认的实现类
*/
initRequestToViewNameTranslator(context);
/**
* 初始化ViewResolvers,当controller将请求处理结果放入到modelandview中后,dispatchservlet会根据
* modelandview选择合适的视图进行渲染,springMVC通过ViewResolver接口定义的resolverViewName方法
* 根据合适的viewname创建对应的view.
* 配置如下:
*
* class="org.springframework.web.servlet.view.InternalResourceViewResolver">
*
*
*
*/
initViewResolvers(context);
/**
* 初始化FlashMapManager用于管理,FlashMapManager用于管理FlashMap,FlashMap用于保持flash attributes,
* flash attributes提供了一个请求存储属性,在使用请求重定向时非常重要,flash attributes在重定向之前暂存
* 以便重定向之后还能使用,并立即删除.
*/
initFlashMapManager(context);
}
创建完WebApplicationContext 并刷新成功后,接着走下一步
image.png
发布wac
image.png
到此,dispatchservlet初始化完成,整个web工程才算启动完成.
image.png
处理请求响应
完成了servlet的初始化过程后,现在可以进行对请求的处理响应过程了,打开浏览器地址栏输入url;
http://localhost:8080/demo-idle-web/index.do
这个时候其实可以通过debug的信息简略看下服务器是如何处理请求并与springMVC交互的:
上图可以看到,从最下面的信息看起,可以看到jetty服务器先解析http请求,解析成HTTPServletRequest以及HTTPServletResponse后经过一系列逻辑处理后将request 与response传递给servlet容器,然后容器选择对应的servlet进行处理request 与 response,这个时候其实就传递到了springMVC中的DispatchServlet中去了.
接下来继续 debug:
image.png
image.png
继续,跳入了doGet方法中,
image.png
deGet/doPost都没有直接对请求进行处理,都是在processRequest方法中对请求进行处理的:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
/**
* 为了保证当前线程的LocaleContext属性,RequestAttributes属性可以再当前请求完成后还能恢复,
*/
//提取当前线程LocaleContext属性
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//根据当前request创建对应的localeContext
LocaleContext localeContext = buildLocaleContext(request);
//提取当前线程对应的RequestAttributes属性
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//根据当前request创建对应的RequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将上述的根据request创建后的两个属性绑定到当前线程
initContextHolders(request, localeContext, requestAttributes);
try {
//准备工作做完后,具体的处理逻辑委托给了子类dispatchServlet中的doService方法进行处理
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//请求处理结束后恢复线程到原始状态
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
//请求处理结束后无论成功与失败都发布事件通知
publishRequestHandledEvent(request, startTime, failureCause);
}
}
看了上面这段带有注释的代码,相信对processRequest的处理逻辑应该是比较清楚了,这里额外讲一点东西(仅仅是个人所了解的一些知识,可能不完全对):
针对每个request请求,服务器都会分配一个线程进行处理,线程也不是无限的,频繁的创建销毁线程,
进行线程上下文切换是非常消耗资源的,所以针对这些请求进行线程分配,一般来说都是通过线程池完成的,
所以, 在请求处理完成后,是需要恢复线程到原始状态的,删除掉前一个request请求遗留的信息
接着debug进入doService方法中:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + requestUri + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
Enumeration> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
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()) {
return;
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
doService方法也是讲具体的逻辑处理放入了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 {
//如果是MultipartContent类型的request则转换成MultipartHTTPServletRequest类型的Request
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.
//根据request信息寻找对应的handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//如果没找到对应的handler则通过response返回错误信息
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根据当前的handler寻找对应的handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
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);
}
}
}
继续跳入getHandler方法中,getHandler会通过request的信息从handlerMappings中提取对应的handler,其实就是提取了当前实例中的Controller的相关的信息,debug可以看到相关的信息:
,
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
=
public java.lang.String com.axa.idle.controller.IdleController.heartBeatCode() throws java.lang.Exception}
可以看到 /index 与 controller中的方法IdleController.heartBeatCode() 有了一个映射关系,继续debug,hm.getHandler这个方法中,这里就不花篇幅详细解读了,这一块的逻辑处理挺长,但是都还挺简单,容易理解,且源码的注释也是写的比较详细,这里简单介绍下这一过程,
通过Request中的url等信息去匹配对应的controller,这里分一个直接匹配和通配符匹配的处理方式,
匹配完成后,将handler封装成HandlerExecutionChain执行链,然后往执行链中加入拦截器,
以保证拦截器可以作用到目标对象中.
看到这个返回的handler的信息:
image.png
接着debug:
image.png
看名字就知道是个适配器设计模式,看下具体的逻辑,简单易懂,遍历所有的handlerAdapters,选择适配的适配器:
image.png
image.png
接着debug,处理last-modified 请求头缓存,客户端第一次访问url时会添加一个last-modified 的响应头,客户端第二次访问url时,客户端会向服务器发送请求头"if-modified-since",询问服务器该时间之后当前请求的内容是否修改过,如果无变化,则自动返回 304 状态码(只要响应头,内容为空,节省服务器网络带宽).
image.png
继续debug,拦截器拦截请求前置处理:
image.png
接着debug,处理逻辑:
image.png
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
debug step into 方法可以看到,跳入的是具体的哪个实现类(AbstractHandlerMethodAdapter):
image.png
看下该方法收到的具体的参数信息:
image.png
protected final ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
//
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
}
else {
// Uses configured default cacheSeconds setting.
checkAndPrepare(request, response, true);
}
// Execute invokeHandlerMethod in synchronized block if required.
//需要在session内的同步执行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return invokeHandleMethod(request, response, handlerMethod);
}
}
}
//调用用户逻辑
return invokeHandleMethod(request, response, handlerMethod);
}
上面这段代码重点在最后一句invokeHandleMethod(request, response, handlerMethod),这里就是执行具体的controller中的方法的逻辑了,该方法返回的是一个ModelAndView,这里的具体实现是通过将request解析以及提供的参数组合成controller中映射的方法所需要的参数,利用反射的方式调用该方法逻辑,计算执行结果,将返回结果再封装到ModelAndView中,如下图可以看到调用invokeHandleMethod方法会跳入controller中/index 映射的方法中去执行逻辑
image.png
image.png
返回结果封装到ModelAndView中,由于heartBeatCode方法并没有将任何执行结果放入model中,所以可以看到mv中view为index,model is {}:
image.png
接着debug:
image.png
applyDefaultViewName方法则是当mv中没有view的值时,采用之前初始化时这个方法中提供的信息:
/**
* 初始化RequestToViewNameTranslator,当controller处理方法没有返回一个view或者逻辑视图名称时,并且
* 没有在该方法中直接往response的输出流中写数据时,就会通过RequestToViewNameTranslator接口的实现类
* 来提供一个约定好的逻辑视图名称供使用,spring中提供了一个默认的实现类
*/
initRequestToViewNameTranslator(context);
这个时候mv已经封装好了,那么就是要做渲染视图的事情了:
image.png
image.png
这段代码逻辑篇幅有点长,这里就总结下resolveViewName实现了什么逻辑:
采用之前初始化时的ViewResolvers对视图进行解析:
/**
* 初始化ViewResolvers,当controller将请求处理结果放入到modelandview中后,dispatchservlet会根据
* modelandview选择合适的视图进行渲染,springMVC通过ViewResolver接口定义的resolverViewName方法
* 根据合适的viewname创建对应的view.
* 配置如下:
*
* class="org.springframework.web.servlet.view.InternalResourceViewResolver">
*
*
*
*/
initViewResolvers(context);
image.png
然后解析视图名时看当前的这个viewName是否在缓存中,在则直接从缓存中提取,提高效率,不在则直接创建该视图,并且提供了对 redirect:xx 和 forward:xx 前缀的支持,最后向view中添加前缀以及后缀,并向view中添加了必要的属性设置,view渲染完成后,接着是页面跳转了,
image.png
image.png
在renderMergedOutputModel方法中,主要就是完成了将model中的信息放入到Request中,这样我们就可以在页面中使用JSTL语法或者Request信息直接获取的方式渲染页面,这样到达了我们通常在使用jsp页面时采用JSTL的语法的方式获取后台返回过来的值渲染到页面上.这一步最主要的就是通过将model中的值放入到Request中,这样我们就可以在别的地方调用到这些值.看下页面结果:
image.png
到此为止,我们就完成了整个springMVC处理Request请求响应的过程,整个过程中略过了一些东西,像异常视图处理,url错误处理等等.
总结
总结一下整个springMVC的处理流程:
ContextLoaderListener初始化WebApplicationContext ROOT,接着初始化servlet,
初始化WebApplicationContext以及一些web应用中必须用到的属性,初始化servlet完成后,
整个web应用算是启动成功了,接着开始处理请求,所有的请求都是通过dispatchservlet进行处理的,
通过解析Request信息从handlermappings中找到对应handler(通常来说就是controller),封装成一个
包含拦截器的执行器链HandlerExecutionChain,然后找到对应的handlerAdapter适配器通过反射的方式
调用controller中的方法进行逻辑处理,返回的结果封装成ModelAndView,然后通过viewReslover对view
进行试图渲染,将model的值注入到Request中,最后返回response响应.