你不知道系列--Spring是如何加載配置文件

Spring如何加載配置文件

面試的時候經常會有面試官問Spring知識點,面試官問到Spring是如何加載配置文件的,流程清楚嗎?求職者:在web.xml中會指定pring配置文件路徑,就會實現加載了。面試官:那你能說說流程嗎?求職者:emmmm…這個時候就會很尷尬了。

任何事情不能侷限於表面,需要有求知意識,儘自己能力和理解去探求真相,當然這個也不是一蹴而就,是一個循序漸進的過程,重要的是在這條路上需要不斷求索。

spring加載配置文件實現流程

大家都知道,Spring配置Bean對象,默認都是在applicationContext.xml中,Spring啓動的時候會加載配置文件,來看一段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>

這個配置大家都很熟悉,在web.xml中配置監聽對象ContextLoaderListener,那這個對象中有什麼,爲什麼要監聽這個對象?我們先來看看這個對象中都有些什麼

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {


	public ContextLoaderListener() {
	}

	
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}

原來這個ContextLoaderListener是繼承了ContextLoader實現了ServletContextListener接口,這個玩法非常眼熟的嘛,定睛一看就是java設計模式中類的適配模式(目標target方法需要適配,接口類創建適配方法,定義Adapter適配類去實現接口方法),既然ContextLoaderListener是目標類有方法待適配,那我們就看看ServletContextListener 接口類有什麼適配方法

public interface ServletContextListener extends EventListener {

   
    default public void contextInitialized(ServletContextEvent sce) {}

    
    default public void contextDestroyed(ServletContextEvent sce) {}
}

ServletContextListener 接口類中有兩個適配方法分別爲初始化和銷燬方法,結合ContextLoaderListener 類的contextInitialized方法(上下文配置初始化方法),這裏目標類調用了initWebApplicationContext(event.getServletContext()),初始化WEB項目的applicationContext,還傳入了一個ServletContext對象,那這是想幹嘛嘞?我們來看看Adapter適配類ContextLoader 都做了些什麼

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!");
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		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
					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);
					}
					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.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 ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

這裏重點關注
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

把Spring配置文件對象ApplicationContext的WEB開發實現類WebApplicationContext設置到ServletContext中,依靠Servlet啓動來實現加載。

我們來梳理一下:

  • 衆所周知當服務啓動,會爲每一個web項目分配一個Servlet對象,ServletContextListener是Servlet監聽類(監聽Servlet啓動,銷燬方法)
  • ContextLoaderListener它通過類適配實現了ServletContextListener類的上下文初始化contextInitialized()方法
  • 當服務啓動,加載Spring配置文件ApplicationContext對象,其web開發中實現類 webApplicationContext會存入到ServletContext對象中
  • ContextLoaderListener則實現了服務啓動創建Servlet對象時,把Spring配置文件設置到Servlet上下文中,監聽webApplicationContext,從而知道Spring配置文件開始加載

總結:
這裏就是Spring配置文件實例化的流程,通過依賴Servlet的創建和銷燬,來實現Soring配置文件加載和銷燬,其實ContextLoaderListener是web組件,真正實例化的是Tomcat,Tomcat在啓動加載web.xml實例化並識別Lintenter配置,Spirng是維護了ApplicationContext對象,通過ApplicationContext對象讀取配置文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章