前期准备:
我建立了一个SpringMVC的项目并且配置了一个tomcat,配置文件内容如下。
web.xml配置文件
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc.xml</param-value>
</init-param>
// 配置为1时表示启动tomcat时就去创建DispatcherServlet
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
mvc.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com"/>
</beans>
SpringMVC和传统MVC的不同点:
- 传统MVC是由 Controller,view, module三层组成
- 而SpringMVC的控制器多了一个前端控制器。前端控制器的作用在于将不同的请求根据地址转给不同的控制器进行处理,并对返回的模型选择相应的视图进行渲染。
DispatcherServlet:
如下是DispatcherServlet的一个继承关系图,在我们SpringMVC中,所有请求都是由DispatcherServlet进行分发的,所以我们主要会从DispatcherServlet去进行源码分析。
初始化流程:
1. 解析web.xml文件
当我们启动tomcat时,tomcat会去解析我们的web.xml的配置文件,这个是由tomcat去进行调用。
2.创建并初始化DispatcherServlet
在我们的web.xml配置文件当中,load-on-startup配置的为1,表示在启动时就会去创建我们的DispatcherServlet。
<load-on-startup>1</load-on-startup>
3.调用init初始化方法,和initServletBean()
在上面DispatcherServlet继承图当中,HttpServletBean是GenericServlet的子类,而GenericServlet实现了Servlet接口。所以tomcat在运行过程中会去调用HttpServletBean的init初始化方法,具体代码如下所示。
public final void init() throws ServletException {
PropertyValues pvs = new ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.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;
}
}
// 初始化Selvlet,创建spring容器
this.initServletBean();
}
在initServletBean方法中会调用this.initWebApplicationContext()方法进行创建bean
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 创建spring容器
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var4) {
this.logger.error("Context initialization failed", var4);
throw var4;
}
if (this.logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
}
if (this.logger.isInfoEnabled()) {
this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
4.initWebApplicationContext方法
- 在该方法中首先会进行父容器的设置,如果没有父容器,紧接着就调用createWebApplicationContext方法进行spring容器的创建。
protected WebApplicationContext initWebApplicationContext() {
// 获取ServletContext(全局唯一)获取与之关联的父容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果SpringMVC的servlet子上下文对象不为空,则设置根上下文为其父类上下文,然后刷新
if (this.webApplicationContext != null) {
// 获取子容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 根据init-param配置的属性名称从ServletContext查找SpringMVC的servlet子上下文
wac = findWebApplicationContext();
}
if (wac == null) {
// 若还为空,则创建一个新的上下文对象并刷新
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 子类自定义对servlet子上下文后续操作,在DispatcherServlet中实现
onRefresh(wac);
}
// 将新创建的上下文注册到ServletContext
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
- 在调用createWebApplicationContext()方法后,进行容器的创建。
1.会先确认使用哪个spring容器。默认使用XMLwebApplicationContext,这里也可以自己进行配置 将容器进行实例化 .
2. 将容器对象进行实例化
3. 读取web.xml配置的init-param,进行设置到容器当中。
4. 调用configureAndRefreshWebApplicationContext方法进行扫描Bean的创建。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 确认使用哪个spring容器。默认使用XMLwebApplicationContext,这里也可以自己进行配置
Class<?> contextClass = this.getContextClass();
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);
// 读取web.xml配置的spring扫描配置文件
String configLocation = this.getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 进行扫描,对象的创建
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
5. configureAndRefreshWebApplicationContext
调用Spring核心refresh()方法。进行配置文件读取及spring容器中对象的实例化初始化。
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(getServletContext().getContextPath()) + '/' + getServletName());
}
}
//将ServletContext和ServletConfig都绑定到servlet子上下文对象中
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//最后初始化子容器,和上面根容器初始化一样
wac.refresh();
}
总体流程方法流程图调用如下
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
- 首先启动tomcat,它会去扫描解析我们的web.xml文件
- 当我们配置 load-on-startup为1时,tomcat就会在启动时去创建我们DispatcherServlet。
- tomcat会根据你配置的路径servlet-class,来创建制定的类,这里是通过DispatcherServlet无参构造方法去创建。
- 在创建DispatcherServlet时,会调用父类HttpServletBean重写的init初始化方法。在方法里面会调用initServletBean。
- 在initServletBean方法中会调用HttpServletBean的子类FrameworkServlet的initWebApplicationContext方法进行spring容器创建,然后在调用createWebApplicationContext方法,在该方法中spring会确定你使用的是哪个容器(这个容器可以自己配置,默认是XMLwebApplicationContext),然后在实例化,通过解析web.xml来确定spring容器是扫描哪个文件下面的bean。
- 最后就是configureAndRefreshWebApplicationContext方法来调用Spring核心refresh()方法。进行配置文件读取及spring容器中对象的实例化初始化。
在上面当中我们一共创建了3个context,
ServletContext: 全局唯一,tomcat启动该web项目了创建
Spring的context: 由servletcontext的监听器contextloaderlistener创建,默认读取servletcontext.xml配置。同时该cntext与servletcontext互相关联,该context被注册到servletcontext中以webapplicationcontext属性存在。
SpringMVC的context: SpringMVC初始化DispacherServlet时创建的上下文,该context为Spring的context的子容器,可以访问Spring容器中的内容(子容器可以访问父容器中的内容,但是反过来不行)。
经过上面步骤,DispatcherServlet父类中的流程已经全部走完,这几个步骤主要的功能就是生成了SpringMVC容器,并将其与ServletContext、Spring容器相关联。
在上面调用refresh()方法后,会进行我们springbean的初始化和创建,以及进行springmvc容器初始化工作,此时需要注意在refresh初始化方法中有一个finishRefresh方法,次方法中有一个pushEvent方法,此方法会触发spring封装的观察者模式的广播操作,从而通过我们设置的SourceFilteringListener监听器进行springmvc九大内置对象的初始化操作,初始化方法在DispatcherServlet类中的initStrategies方法。