你不知道系列--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对象读取配置文件。

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