天天看點

spring mvc 源碼分析之父子容器問題spring mvc 源碼分析前言為什麼要弄兩個ioc容器?一個不可以嗎存在兩個容器 父容器是spring ioc容器自容器是springmvc ioc容器

spring mvc 源碼分析

spring mvc 源碼分析之父子容器問題

  • spring mvc 源碼分析
  • 前言
  • 為什麼要弄兩個ioc容器?一個不可以嗎
  • 存在兩個容器 父容器是spring ioc容器自容器是springmvc ioc容器
    • 父容器初始化(程式啟動時就初始化)
    • 子容器初始化(spring mvc 初始化 第一次調用時初始化)
    • 加載流程圖
    • 總結

前言

我們知道在spring啟動的時候,會将我們配置的需要立即初始化的單例bean進行預設的初始化加載到記憶體中,也就是spring ioc容器中,當我們需要去使用時,可以直接向spring容器索要,而不需要自己去建立對象,考慮對象之間的複雜關聯等。那麼在spring mvc中是否也有這樣的一個容器呢?答案是肯定的,存在一個類似的容器,這個時候我們就會有兩個疑問

1. 為什麼要弄兩個ioc容器?一個不可以嗎?

2. 是否會存在 父子的關系還是都是平等的?

3. 存在父子的關系那麼在源碼中是如何初始化的?

為什麼要弄兩個ioc容器?一個不可以嗎

因為spring mvc 是個前段架構,不一定隻用于和spring 整合,是以是很有必要設計一個自己管轄的ioc容器。

存在兩個容器 父容器是spring ioc容器自容器是springmvc ioc容器

通過之前搭建的測試用例開始 調試

先在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">

// 監聽器
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:springmvc.xml</param-value>
		</init-param>

	</servlet>
	
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>
           

父容器初始化(程式啟動時就初始化)

斷點到ContextLoaderListener#contextInitialized(ServletContextEvent event) 注入根WebApplicationContext裡面就包含spring的ioc容器。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		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!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					// 設定父上下文,設定應用程式上下文id等 如果是父容器會傳回null對象去設定父容器的Parent
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					// 配置和重新整理web應用程式上下文
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

           

設定父上下文,設定應用程式上下文id等 如果是父容器會傳回null對象去設定父容器的Parent

spring mvc 源碼分析之父子容器問題spring mvc 源碼分析前言為什麼要弄兩個ioc容器?一個不可以嗎存在兩個容器 父容器是spring ioc容器自容器是springmvc ioc容器

配置和重新整理web應用程式上下文

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}

		customizeContext(sc, wac);
		// 開始重新整理
		wac.refresh();
           

wac.refresh(); 重新整理方法 會進入到經典的spring 加載方法裡面

AbstractApplicationContext#refresh()

@Override
	public void refresh() throws BeansException, IllegalStateException {
		// 對象鎖加鎖
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			/*重新整理之前的預處理
			表示在真正做refresh操作之前需要準備的事情
			設定Spring容器的啟動時間
			開啟活躍狀态 撤銷關閉狀态
			驗證環境資訊裡一些必須存在的屬性等*/
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 擷取BeanFactory :預設實作是 DefaultListableBeanFactory
			// 加載BeanDefition 并注冊到 BeanDefitionRegistry
			// 關鍵步驟
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// BeanFactory的預準備工作(前置處理)(BeanFactory進行設定 比如context的類加載器等)
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// BeanFactory的準備工作完成之後 進行的後置處理工作
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 執行個體化實作了BeanFactoryPostProcessor接口的bean,并調用接口方法
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 注冊BeanPostProcessor(bean的後置處理),在建立bean的前後等執行
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 初始化MessageSource元件(做國際化功能:消息綁定和消息解析)
				initMessageSource();

				// Initialize event multicaster for this context.
				// 初始化事件派發器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 子類重寫這個方法,在容器重新整理的時候可以自定義邏輯:如建立tomcat jetty等web伺服器
				onRefresh();

				// Check for listener beans and register them.
				// 注冊應用的監聽器。就是注冊實作了ApplicationListener接口的監聽器bean
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 初始化所有剩下的非懶加載的單例bean
				// 初始化建立非懶加載方式的單例bean的執行個體(屬性未設定)
				// 填充屬性
				// 初始化方法調用(比如調用afterPropertiesSet方法,init-method方法)
				// 調用beanPostProcessor(後置處理器)對執行個體bean進行後置處理
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 完成context的重新整理。主要調用LifecycleProcessor的onRefresh()方法,并且釋出事件(contextRefreshEvent)
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}
           

子容器初始化(spring mvc 初始化 第一次調用時初始化)

斷點到HttpServletBean#init()

先請求才會初始化

public final void init() throws ServletException {

		// Set bean properties from init parameters.
		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);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}
           

FrameworkServlet#initServletBean();

@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			// springMvc 容器初始化
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}
           

FrameworkServlet#initWebApplicationContext 進行spring mvc初始化和父容器設定到目前子容器中

protected WebApplicationContext initWebApplicationContext() {
		//  擷取根容器 rootContext
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						// 将根容器設定到子容器中
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			// 建立spring mvc的ioc容器
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}
           

FrameworkServlet#createWebApplicationContext 設定父容器配置和重新整理web應用程式上下文 調用經典refresh()方法

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		// spring mvc容器設定父容器
		wac.setParent(parent);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		// 配置和重新整理web應用程式上下文
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				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()));

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		// 開始重新整理
		wac.refresh();
	}
           

加載流程圖

spring mvc 源碼分析之父子容器問題spring mvc 源碼分析前言為什麼要弄兩個ioc容器?一個不可以嗎存在兩個容器 父容器是spring ioc容器自容器是springmvc ioc容器

總結

1. spring 啟動會立即初始化ioc容器 為父容器。

2. spring mvc 在發起第一次請求時會初始化 spring mvc容器 為子容器。

3. spring 容器初始化的範圍是除了controller層的,比如dao層 srever層。

4. spring mvc 初始化的範圍是controller層。

5. 子容器能通路父容器中的對象,父容器通路不到子容器中的對象。

6. 在controller層查找容器中的對象會先查找子容器再查找父容器,其他層都是直接從父容器中查找

7.父容器和子容器中不能存在相同的bean。