前言
转载请注明来源
上一次写博客已经是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中