面试的时候经常会有面试官问Spring知识点,面试官问到Spring是如何加载配置文件的,流程清楚吗?求职者:在web.xml中会指定pring配置文件路径,就会实现加载了。面试官:那你能说说流程吗?求职者:emmmm…这个时候就会很尴尬了。
任何事情不能局限于表面,需要有求知意识,尽自己能力和理解去探求真相,当然这个也不是一蹴而就,是一个循序渐进的过程,重要的是在这条路上需要不断求索。
大家都知道,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启动来实现加载。
我们来梳理一下:
总结:
这里就是Spring配置文件实例化的流程,通过依赖Servlet的创建和销毁,来实现Soring配置文件加载和销毁,其实ContextLoaderListener是web组件,真正实例化的是Tomcat,Tomcat在启动加载web.xml实例化并识别Lintenter配置,Spirng是维护了ApplicationContext对象,通过ApplicationContext对象读取配置文件。