SpringMVC4.x 番外(1):Spring MVC 上下文容器初始化

MVC 應用配置實現

  1. 在目前的實現中Spring的上下文容器可以稱之爲RootApplicationContext,而MVC的上下文容器稱之爲WebApplicationContext或者ServletApplicationContext。兩者是父子容器的關係。

  2. Spring MVC 框架的核心類是DispatcherServlet,幾乎所有的工作都是圍繞DispatcherServlet展開的。每一個DispatcherServlet都有一個自己的WebApplicationContext。該上下文容器的創建與初始化是在 DispatcherServlet 類的創建和初始化過程中完成的。

  3. 由於Servlet3.x支持通過類完成應用容器初始化,創建DispatcherServlet一般會有兩種不同的方式:

Web.xml 配置實現

  1. Web.xml 配置實現
  • 定義DispatcherServlet對象
  • 通過初始化參數contextConfigLocation自定義配置文件,多個配置文件可以使用逗號、空格等分隔。如spring-mvc.xml
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

  1. 在指定的配置文件中(spring-mvc.xml),定義需要的Bean組件實現具體的功能。

Servlet3.x 容器實現

  1. Servlet3.x 中可以不使用Web.xml,實現動態註冊Servlet。MVC容器初始化以及DispatcherServlet 類的創建是在 AbstractDispatcherServletInitializer#registerDispatcherServlet方法中編碼完成,省去了配置定義DispatcherServlet的步驟

  2. 在創建MVC的上下文容器中,有需要實現的getServletConfigClasses方法,可以設置Spring MVC 的配置類,需要自定義指定該 ServletServletMappings映射

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.learning.springmvc.base.javaconfig", useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebMvcConfig extends WebMvcConfigurerAdapter {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/pages/", ".jsp");
    }
}

# Servlet3.x 初始化 
public class WebMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    # Spring 容器配置
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class};
    }

    # MVC 容器配置
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    # 配置Servlet 的Mappings映射
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

MVC 容器初始化

DispatcherServlet 創建

  1. 在Spring MVC 的設計中,每一個DispatcherServlet應該有屬於自己的上下文容器,DispatcherServlet有兩個構造函數,兩種構造正好對應着Web.xml配置和SpringServletContainerInitializer初始化類配置兩種不同的實現方式。
構造函數 應用初始化方式 解析
無參構造 Web.xml 創建DispatcherServlet對象,然後通過實現的接口方法initServletBean()創建和初始化容器WebApplicationContext
有參構造 SpringServletContainerInitializer 1、參數爲WebApplicationContext對象,即先創建了容器對象WebApplicationContext
2、創建DispatcherServlet對象,通過實現的接口方法initServletBean()初始化容器WebApplicationContext
  1. SpringServletContainerInitializer 方式創建上下文容器會先調用AbstractDispatcherServletInitializer#registerContextLoaderListener方法創建Spring的上下文容器對象、ContextLoaderListener對象,然後創建MVC的上下文容器以及DispatcherServlet對象
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
	
	    # 創建Spring的上下文容器以及ContextLoaderListener對象
		super.onStartup(servletContext);
		# 創建 MVC 上下文容器以及 DispatcherServlet 對象
		registerDispatcherServlet(servletContext);
	}
  1. 在創建 MVC 上下文容器以及 DispatcherServlet 對象過程中,簡單總結爲如下步驟
編號 操作 解析
1 創建Servlet 名稱 默認名稱爲 dispatcher
2 創建 MVC 上下文容器 1、在創建容器對象的方法中,可以獲取容器相關的配置類。其方法getServletConfigClasses,框架中沒有實現,需要應用擴展使用。
2、創建的該上下文容器,類型爲AnnotationConfigWebApplicationContext,沒有設置任何屬性,如contextClasscontextConfigLocation
3 創建DispatcherServlet對象 1、可以獲取DispatcherServlet對應的映射,其方法getServletMappings,框架中沒有實現,需要應用擴展使用
2、把創建的容器對象作爲參數創建 DispatcherServlet對象,並且把創建的該對象動態註冊到ServletContext
4 自定義容器設置 獲取自定義接口ApplicationContextInitializer的實現,目前該功能沒有任何實現。
5 動態註冊過濾器以及其他自定義組件 ServletContext中動態註冊Filter、自定義組件等。框架本身沒有具體的實現,需要通過覆蓋getServletFilterscustomizeRegistration方法自定義實現
	protected void registerDispatcherServlet(ServletContext servletContext) {
		# 默認servlet 名稱爲 dispatcher
		String servletName = getServletName();
		
        # 創建MVC上下文容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
        
        # 創建`DispatcherServlet`對象
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		
		# 設置初始化類 ApplicationContextInitializer。沒有任何實現
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        # 動態把該Servlet 註冊到 `servletContext` 上下文
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		Assert.notNull(registration,
				"Failed to register servlet with name '" + servletName + "'." +
				"Check if there is another servlet registered under the same name.");

		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());# 映射
		registration.setAsyncSupported(isAsyncSupported());

        # 動態註冊 Filter,可以重寫 getServletFilters 實現
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
        # 動態自定義註冊其他的,重寫 customizeRegistration 方法實現
		customizeRegistration(registration);
	}

DispatcherServlet 初始化

在這裏插入圖片描述

  1. HttpServlet初始化調用了HttpServletBean#init方法,該方法在Servlet初始化中被調用,主要工作就是MVC容器的初始化工作
  • 該方法還會獲取Servlet中的init參數,並且創建一個BeanWrapper對象,然後由子類真正執行BeanWrapper的初始化工作。由於HttpServletBean的子類都沒有覆蓋其initBeanWrapper方法,所以創建的BeanWrapper對象沒有任何作用
	public final void init() throws ServletException {

		// 設置初始化參數 
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			... ...
		}

		// 初始化Web 上下文容器,初始化 Servlet這個Bean
		initServletBean();
        ... ...
	}
	
	@Override
	protected final void initServletBean() throws ServletException {
	    # 初始化Web 上下文容器
		this.webApplicationContext = initWebApplicationContext();
		
		# 初始化 Servlet,這個方法暫時實現爲空。
		initFrameworkServlet();
	}
	
  1. 在方法initWebApplicationContext()中完成的MVC上下文容器創建初始化過程,簡單總結爲如下4個步驟:
編號 步驟 分析
1 設置父容器 如果MVC容易已經存在,則從 ServletContext 中獲取key爲org.springframework.web.context.WebApplicationContext.ROOT的Spring上下文容器,並把他設置爲MVC上下文容器的父容器
2 初始化容器 如果MVC容易已經存在,並判斷容器是否成功初始化(isActive狀態),如未初始化,則執行容器的初始化工作
3 創建容器 判斷上下文容器對象是否已經創建,如果沒有則創建一個新的容器對象
4 Serevlet個性化刷新工作 該方法內部用來初始化MVC框架的核心組件,由於在MVC上下文容器初始化時使用spring 的事件機制完成了個性化刷新工作(refreshEventReceived狀態),所以這邊一般不會再次執行。
5 保存容器 把初始化的MVC容器作爲一個屬性存入ServletContext中,key爲 org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherdispatcher 爲默認的Servlet的名稱。
	protected WebApplicationContext initWebApplicationContext() {
	    # 從 ServletContext 中獲取Spring上下文容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
        
        # 如果MVC容易已經存在
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						# 把應用上下文容器設置成 Web 上下文容器的父容器
						cwac.setParent(rootContext);
					}
					# 配置和刷新(初始化)Web 上下文容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		# 如果MVC容易不存在
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
            // 該方法在DispatcherServlet中覆蓋重寫,用於初始化mvc內部核心組件.其核心方法在於 DispatcherServlet#initStrategies() 方法
            // 其實在 刷新(初始化)MVC上下文容器時利用spring的事件機制,已經初始化過一遍,這邊不會走。
			onRefresh(wac);
		}

		if (this.publishContext) {
			# 把Web上下文容器,存在 ServletContext 中
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
            ... ...
		}

		return wac;
	}
	
  1. 容器創建方法createWebApplicationContext只有在使用Web.xml 配置方式時纔會被調用。
  • 先獲取創建上下文容器的類型,默認是XmlWebApplicationContext,然後利用反射直接創建。
  • 設置上下文容器相關的屬性,如父容器、contextConfigLocation
  • 初始化刷新該容器對象
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
		# 獲取創建上下文容器的類型
		Class<?> contextClass = getContextClass();
		... ...
		
		# 反射創建
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        # 設置各種屬性參數
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());

        # 初始化容器
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
  1. 上下文容器配置與初始化方法configureAndRefreshWebApplicationContext()是整個過程的核心。所有初始化的工作部分都是在這邊完成的,主要有:
編號 任務名稱 具體事項
1 設置容器的唯一Id 1、可以使用初始化參數contextId自定義
2、默認值爲一個組合值,爲WebApplicationContext類的全路徑名稱加上ServletName的值。默認容器ID爲org.springframework.web.context.WebApplicationContext:/dispatcher
2 設置一般屬性 屬性包括 ServletContextServletConfigNamespaceApplicationListener,值得關注的爲添加了自定義的ContextRefreshListener容器刷新監聽器。
3 初始化佔位符屬性源 這個操作在容器初始化(刷新操作)始終會被執行。這邊執行的原因在於確保servlet屬性源在刷新操作之前的任何後處理或初始化中都處於適當的位置
4 後置處理 在刷新並激活給定的上下文容器作爲此該Seervlet的上下文容器之前,對其進行後處理。默認實現爲空,需要子類擴展實現
5 自定義容器設置 1、在容器初始化刷新之前自定義創建的容器
2、該自定義擴展功能專門由一個接口ApplicationContextInitializer實現
3、通過初始化屬性contextInitializerClassesglobalInitializerClasses,指定自定義的ApplicationContextInitializer接口實現類器
4、通過這個功能可以在容器對象初始化之前,擴展容器的功能
6 上下文初始化 調用AbstractApplicationContext#refresh方法完成初始化。具體見 Spring4.x 筆記(6):Ioc 容器高級-內部工作機制
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	
	    # 設置 contextId
		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());
			}
		}
        
        # 設置一般屬性
		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();
	}
  1. 上下文容器初始化刷新後,會提交一個容器刷新事件。而在DispatcherServlet父類自定義的容器刷新事件監聽中ContextRefreshListener,直接調用FrameworkServlet#onRefresh方法初始負責初始化內部組件,並且設置refreshEventReceived屬性爲true
	private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}
	
	public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}
	

至此整個DispatcherServlet以及其所屬的MVC上下文容器完成創建和初始化,可以接收HTTP請求了。

參考

  1. SpringMVC4.x 筆記(1):框架體系概述
  2. Spring4.x 番外(1):Spring 上下文容器初始化
  3. Spring4.x 筆記(2):Spring 的Ioc容器
  4. Spring4.x 筆記(6):Ioc 容器高級-內部工作機制
  5. Servlet 筆記(12):Servlet3.X 版本新特性
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章