springmvc 响应封装_debug方式深入springMVC源码的实现

前言

转载请注明来源

上一次写博客已经是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响应.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值