spring-webmvc啟動流程
阿新 • • 發佈:2019-05-30
webMVC啟動流程
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>springtest</display-name> <context-param> <!-- 初始化器 --> <param-name>globalInitializerClasses</param-name> <param-value>com.wt.test.webmvc.config.MyContextInitializer</param-value> </context-param> <!-- 配置spring容器監聽器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>com.wt.test.webmvc.config.MyDataServletContextListener</listener-class> </listener> <!-- 前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>com.wt.test.webmvc.config.MyDispatcherServlet</servlet-class> <!-- 載入springmvc配置 --> <init-param> <param-name>contextConfigLocation</param-name> <!-- 配置檔案的地址 如果不配置contextConfigLocation, 預設查詢的配置檔名稱classpath下的:servlet名稱+"-serlvet.xml"即:springmvc-serlvet.xml --> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> <init-param> <!-- 初始化器 --> <param-name>contextInitializerClasses</param-name> <param-value>com.wt.test.webmvc.config.MyContextInitializer</param-value> </init-param> <init-param> <param-name>contextId</param-name> <param-value>abcdefg</param-value> </init-param> <!-- 配置一個已經存在的容器,可以是上面的父容器,value值是容器在servletContext的Attrbute的key --> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 可以配置/ ,此工程 所有請求全部由springmvc解析,此種方式可以實現 RESTful方式,需要特殊處理對靜態檔案的解析不能由springmvc解析 可以配置*.do或*.action,所有請求的url副檔名為.do或.action由springmvc解析,此種方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,這是不對的。 --> <url-pattern>/</url-pattern> </servlet-mapping> <!-- post亂碼處理 --> <filter> <filter-name>CharacterEncodingFilter</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> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> ... 靜態檔案的解析用預設的default,都跟上面一樣配置,為了簡短這個web,下邊就不ctrl+v了 </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
springmvc啟動過程中,我們需要配置的web如上圖所示,這個基本上算是我們平時常用的比較完善的配置了。當然我們在做專案的時候完全可以省略掉ContextLoaderListener這個父容器的配置。因為我們的dispatcherServlet的init方法中是會初始化mvc容器的,也就是我們常說的子容器,contextLoaderListener載入的是父容器。
父容器的初始化
contextLoaderListener是實現了ServletContextListener的。我們直接來看contextInitialized(ServletContextEvent)方法。
//ContextLoaderListener.java,這貨繼承了ContextLoader public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); }
//ContextLoader.java private static final Properties defaultStrategies; static { ... //這個檔案與當前class同目錄 //它的值是org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } ... } public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { ..... //建立父容器context,預設是XmlWebApplicationContext,可以通過,這個值預設是 if (this.context == null) { this.context = createWebApplicationContext(servletContext); } ..... //將初始化完的context放到當前servletContxt當中, //key是WebApplicationContext.class.getName() + ".ROOT" servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ..... } protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); ..... return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } protected Class<?> determineContextClass(ServletContext servletContext) { // 初始化的父容器的class名字,通過web.xml的servlet的<init-param>屬性來配置contextClass的的值 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { ...... } else { //預設值,在這個類的靜態初始化塊中 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); }
從上面可以看出我們其實預設初始化了一個XmlWebApplicationContext型別的父容器,並且將其放到了servletContext的attribute當中。
子容器的初始化
子容器的初始化是在DispatcherServlet中完成的,springmvc對所有請求的攔截也是基於servlet的,這個類就是spring隊請求處理的核心,所有的請求都會到這裡,然後由它去分發。既然這貨是個servlet,那我們來看一看它的init方法,這個方法在他的父類HttpServletBean中。
// HttpServletBean.java
public final void init() throws ServletException {
...
//子類實現FrameworkServlet.java
initServletBean();
...
}
// FrameworkServlet.java
protected final void initServletBean() throws ServletException {
...
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
...
}
protected WebApplicationContext initWebApplicationContext() {
//這個地方獲取的就是我們之前初始化後放到servletContext的Attribute中的父容器XmlWebApplicationContext,有興趣的可以點進去看看
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
...
if (wac == null) {
// 這裡就是去找一個已經存在的容器,servlet的<init-param>屬性中配置contextAttribute值,
// 因為我們之前啟動了一個父容器,所以如果我們把父容器的key[WebApplicationContext.class.getName() + ".ROOT"]配在這個地方,我們就可以拿到父容器了。
wac = findWebApplicationContext();
}
if (wac == null) {
// 正常情況下初始化子容器,傳進去的rootContext就是之前初始化的父容器。
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 初始化requestMapping等,方法在DispacherServlet.java中,有興趣的自己去看
onRefresh(wac);
}
if (this.publishContext) {
// 生成子容器的名稱,
String attrName = getServletContextAttributeName();
// 將子容器也賦值給servletContext的Attribute
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
...
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
...
return wac;
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 子容器的型別,可以通過當前servlet的getContextClass方法獲得,我的xml檔案就是配置過的
Class<?> contextClass = getContextClass();
...
wac.setEnvironment(getEnvironment());
// 可以看到這裡我們設定了子容器的父容器為之前初始化的XmlWebApplicationContext
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
...
// 配置和重新整理容獲取並呼叫呼叫web.xml配置的ContextInitializers
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
//由子類實現,預設為空實現
postProcessWebApplicationContext(wac);
//獲取並呼叫web.xml配置的ContextInitializers
applyInitializers(wac);
...
}
protected void applyInitializers(ConfigurableApplicationContext wac) {
//獲取由web.xml <context-param>配置的initializer,key為globalInitializerClasses
String globalClassNames = getServletContext().getInitParameter("globalInitializerClasses");
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
//獲取由web.xml 的servlet配置的<init-param>配置的initializer,key為contextInitializerClasses
if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
//排序
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
public String getServletContextAttributeName() {
// 生成子容器名稱
return SERVLET_CONTEXT_PREFIX + getServletName();
}
從上面可以看出我們其實建立了的子容器也同樣放到了servletContext的attribute當中。
總的初始化流程可以總結如下:
1. 通過ContextLoaderListener建立父容器;
2. 將父容器放到servletContext的Attribute中;
3. 通過servlet的init()方法開始建立子容器;
3.1. 獲取servletContext中的父容器;
3.2. 通過配置的contextClass的值查詢是否已經存在一個容器;
3.3. 如果上一步沒有找到,則開始重新建立一個子容器;
3.4. 將父容器設定為子容器的parent;
3.5. 重新整理子容器
3.5.1 呼叫子類擴充套件方法postProcessWebApplicationContext(wac);
3.5.2 查詢並呼叫web.xml配置的contextInitializers;
4. 通過onRefresh(wac)方法初始化requestMapping等;
5. 將初始化的子容器也丟給servletContext的Attribute;
6. 執行子類擴充套件的initFrameworkServlet()方法,如果沒有實現就是空的;
7. springmvc初始化結束。