springMVC项目启动过程,分析源码。
1、环境搭建,这步我就省略细节,只把我的大概环境说下:
windows 7 、jdk 8、maven-3.3.9、tomcat 8.5.11、IDEA 2017.1 x64版
具体环境安装,我就略过,可自行google、baidu安装教程,后续有空我加上一些安装教程链接。
2、首先打开IDEA新建Maven Project,基本项目结构自动生成了,
编写pom文件,以下是我的依赖的jar:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mvc</groupId>
<artifactId>MVCProject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.9.RELEASE</spring.version>
<mybatis.version>3.4.4</mybatis.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!-- common -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!-- 字节码 -->
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
<!-- 数据库连接池dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<webResources>
<resource>
<directory>web</directory>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
WEB-INF目录下的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">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--webAppRootKey 上下文参数-->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>spring.study</param-value>
</context-param>
<!--spring容器初始化配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</context-param>
<!--spring容器初始化监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath*:/config/log4j.xml</param-value>
</context-param>
<!--负责将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中-->
<listener>
<listener-class>
org.springframework.web.util.WebAppRootListener
</listener-class>
</listener>
<filter>
<filter-name>CharsetFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3、Spring配置加载方式
有两种配置,一个是ContextLoaderListener 通过监听器解析配置,一个是
DispatcherServlet ,通过springMVC的servlet
解析。也可以同时配置两种,但是不建议这么做,这篇文章介绍了两种加载配置的关系,以及解释了为什么不建议同时配置。
我只配置了ContextLoaderListener,加载spring配置。以下是启动tomcat,进入ContextLoaderListener源码,这个Listener实现了ServletContextListener,和继承一个ContextLoader,实现方法contextInitialized()是在监听的servlet容器启动时调用的,
里面开始对Spring ApplicationContext容器做初始化处理,通俗点讲开始创建容器类(WebApplicationContext),把spring所有配置加载进去,所依赖的bean创建实例,默认参数等等初始化。可以debug看到event传入的是servletContextEvent对象,这是servlet启动的产生的事件对象,可以看到event获取当前应用的servletContext。
初始化实现在父类的ContextLoader中,initWebApplicationContext()方法做了哪些事情呢,请看下面:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//这里从ServletContext获取WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,这个key对应着Spring Root WebApplicationContext,
//这说明springWeb容器依赖于ServletContext存在。这里做了判断是否已经初始化web容器了,防止多个容器初始化冲突
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");
}
//spring容器初始化开始时间
long startTime = System.currentTimeMillis();
try {
//创建WebApplicationContext
if(this.context == null) {
//如果当前对象为null,则createWebApplicationContext,创建XmlWebApplicationContext初始化部分字段值
this.context = this.createWebApplicationContext(servletContext);
}
//判断context类是否实现了ConfigurableWebApplicationContext接口
if(this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
//判断容器是否没有激活(启动完成),底层通过AtomicBoolean(原子变量对象) 标识active
if(!cwac.isActive()) {
//加载并设置父容器
if(cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//整个WebApplicationContext加载配置和创建实例工厂等操作的调用的总方法
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将当前context对象保存到servletContext容器中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//获取当前线程的类加载器,绑定并引用对应的currentContext,不是ContextLoader的ClassLoader就保存到对应currentContextPerThread的Map里面
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if(ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if(ccl != null) {
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 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;
}
}
}
//加载容器WebApplicationContext对象
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//获取web应用配置的ServletContext类对象
Class<?> contextClass = this.determineContextClass(sc);
//这里是判断contextClass类型能否转换为此ConfigurableWebApplicationContext所表示的类型
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);
}
}
//查找并确定配置的WebApplicationContext实现类
protected Class<?> determineContextClass(ServletContext servletContext) {
//获取web.xml是否配置的WebApplicationContext类
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
//如果有则加载对应的实现类
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
//如果没有则加载spring默认配置的类
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
接下来看configureAndRefreshWebApplicationContext方法里面的实现:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String configLocationParam;
if(ObjectUtils.identityToString(wac).equals(wac.getId())) {
//获取是否web.xml配置的contextId
configLocationParam = sc.getInitParameter("contextId");
if(configLocationParam != null) {
wac.setId(configLocationParam);
} else {
//设置默认容器id为WebApplicationContext类名+“:”+项目相对路径
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//设置WebApplicationContext引用ServletContext容器
wac.setServletContext(sc);
//读取web.xml中contextConfigLocation配置文件路径
configLocationParam = sc.getInitParameter("contextConfigLocation");
if(configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//获取配置环境对象ConfigurableEnvironment,创建并获取系统相关环境参数,
//主要包含:systemEnvironment、systemProperties、JNDIProperty、servletConfigInitParams、servletContextInitParams的Property
ConfigurableEnvironment env = wac.getEnvironment();
if(env instanceof ConfigurableWebEnvironment) {
//初始化PropertySources,
((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
}
//如果有配置自定义的web容器初始化类(需继承实现ApplicationContextInitializer接口),然后在调用这些对象的initialize方法
//再对ConfigurableWebApplicationContext刷新(refresh)之前进行自定义添加的初始化工作,
//一般不需要配置(注意这个过程是刷新之前,所有bean对象还没加载)
this.customizeContext(sc, wac);
//ConfigurableWebApplicationContext正式开始加载解析配置,实例化beanFactory加载bean等操作,这个方法实现包含容器所有需要加载
//对象,可以说是spring初始化完成的主要方法。
wac.refresh();
}
这部分分析了ContextLoader这个类创建容器的过程,主要是为Spring容器确定容器实现类然后创建初始化,和相关对象创建,
环境变量上下文获取,ConfigLocation配置获取
下一篇我将分析AbstractApplicationContext的refresh()方法的具体实现,加载配置初始化容器。