1. 程式人生 > >spring-webmvc啟動流程

spring-webmvc啟動流程

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初始化結束。