(Spring原始碼解析)一步一步分析,springMVC專案啟動過程(一)
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()方法的具體實現,載入配置初始化容器。