携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
基于 Spring Framework v5.2.6.RELEASE
在使用 XML 文件初始化 Spring 容器时,通常会用到以下的方式:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
user.sayHello();
其中,beans.xml
文件中定义了一些 Bean,在 ClassPathXmlApplicationContext
的构造方法中,通过读取 beans.xml 中的配置,创建了 Spring 容器。ClassPathXmlApplicationContext
是 ApplicationContext
的实现类,ApplicationContext
继承自 BeanFactory
。我们可以通过它的 getBean
方法,从 Spring 容器中获取 Bean 对象。
ClassPathXmlApplicationContext
的构造方法
我们从 ClassPathXmlApplicationContext
的构造方法开始,在源码中探索 XML 配置文件是如何加载进来的。
上面的代码中,我们使用了以下的构造方法:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
在这里,调用了另外一个构造方法,定义如下:
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
综合看以上的两个构造方法,这里使用了三个参数:
configLocations
是我们创建ClassPathXmlApplicationContext
时提供的配置文件路径,从方法签名中看出,这里可以提供多个配置文件。refresh
表示是否自动刷新上下文。这里刷新上下文还包括重新加载所有的 BeanDefinition、创建单例 Bean 等。这里的代码中调用方法时的值时true
。parent
是当前容器的父容器,这里代码中直接提供了null
。
接下来我们看方法体中的内容,主要有三句代码,我们一一来探索。
ClassPathXmlApplicationContext
构造方法中的内容
调用父类构造方法
第一步,以 parent
为参数调用了父类的构造方法。下图是 ClassPathXmlApplicationContext
类的继承关系图:
顺着父类构造方法的调用一路跟踪,我们可以找到以下代码:
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
这里最终调用到了 AbstractApplicationContext
的构造方法,其中有两行逻辑。
- 调用了无参构造方法
- 调用了
setParent(parent)
。
我们分别来看。
无参构造方法的代码如下:
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
这里初始化了 resourcePatternResolver
成员变量,PathMatchingResourcePatternResolver
是一个基于路径匹配的资源解析器。
之后的 setParent
方法源码如下:
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
这里将传进来的 parent
参数复制给了对应的成员变量。因为前面的代码中,实际传入的值为 null
,因此之后的 if 语句块中的内容我们先不看。
setConfigLocations
处理配置文件路径中的占位符
在 ClassPathXmlApplicationContext
构造方法的第二步,调用了 setConfigLocations
方法:
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
这里的主要步骤,是把我们实例化 ClassPathXmlApplicationContext
时传递的配置文件路径,放到了一个新创建的字符串数组中,并将其赋值给 configLocations
成员变量。
在放入新输入之前,还调用了 resolvePath
方法对文件路径字符串进行了处理,我们看一下这个方法的源码:
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
再通过 resolveRequiredPlaceholders
方法的接口定义,我们可以了解到,这里主要是为了处理文本中的 ${}
占位符。也就是说,这里给定的路径中可以包含 ${}
占位符,并且在创建 Spring 容器时,Spring 可以处理这些占位符,前提是,在当前的 Spring 环境中,能通过这些占位符找到匹配的值。
refresh 核心方法
在创建容器的构造方法中,refresh
是最核心的方法,这个方法在 AbstractApplicationContext
中定义。我们先看看 refresh
方法的源码:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 初始化上下文的信息
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 这里会调用模版方法,通过子类的实现,初始化 BeanFactory 并解析 XML 配置
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 对 BeanFactory 进行初识配置
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 这里默认是一个空的模版方法,子类可以在此处实现逻辑,来注册后处理器
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 执行 BeanFactory 的后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 注册 Bean 的后处理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 初始化消息源
initMessageSource();
// Initialize event multicaster for this context.
// 初始化时间广播器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 这里也是一个空的模版方法,子类可以在此处实现逻辑,在容器中的单例 Bean 初始化之前,来初始化一些特殊的 Bean
onRefresh();
// Check for listener beans and register them.
// 注册化监听器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 完成 BeanFactory 初始化,实例化所有非延迟加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 初始化结束,广播相应的事件
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 容器初始化的流程有大概的了解,就可以发现,在这个方法中,包含了 Spring 容器初始化的所有核心步骤。
本文先介绍这么多,后面会对 refresh()
方法的详细流程进行探索。