【有书共读】《Spring源码深度解读》第六章容器的功能扩展
经过之前的分析,我们已经了解了Spring是如何解析和加载的bean的。但是在上一篇文章结束的时候说到,一般我们写程序是不会用到BeanFactory来当做Spring的容器的,一般使用的是ApplicationContext作为Spring的容器。这两者之间有什么区别呢?
ApplicationContext和BeanFactory在Spring中都是用于加载bean的,但是ApplicationContext提供了许多扩展功能,换句话说,BeanFactory有的功能ApplicationContext全都有,而Application有的功能,BeanFactory却不一定有,所有在一般情况下我们写程序用到的都是ApplicationContext作为Spring的容器。
那么,ApplicationContext比BeanFactory多出了哪些功能呢?这就是我们下面要关注的问题。
首先,我们首先来看一看这两个不同的类在加载配置文件上的写法的不同:
使用BeanFactory方式加载XML:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
使用ApplicationContext方式加载XML:
ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactoryTest.xml");
与之前研究BeanFactory一样,我们这次还是从ClassPathXmlApplication作为切入点,开始对整体功能进行分析
package org.springframework.context.support;
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
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();
}
}
}
设置路径是必不可少的步骤,ClassPathXmlApplicationContext中可以将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能的实现都在refresh()中实现。
设置配置路径:
在ClassPathXmlApplicationContext中支持多个配置文件以数组方式同时传入:
设置了路径之后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说refresh函数中几乎包含了ApplicationContext中提供的全部功能,而且此函数中逻辑非常清楚明了,使我们很容易分析对应的层次及逻辑。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//准备刷新的上下文环境
prepareRefresh();
//初始化BeanFactory,并进行XML文件读取
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//对BeanFactory进行各种 功能填充
prepareBeanFactory(beanFactory);
try {
//子类覆盖方法做额外的处理
postProcessBeanFactory(beanFactory);
//激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
//注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean的时候
registerBeanPostProcessors(beanFactory);
//为上下文初始化Message源,即不同语言的消息体,国际化处理
initMessageSource();
//初始化应用消息广播器,并放入"applicationEventMulticaster"bean中
initApplicationEventMulticaster();
//留给子类来初始化其他的bean
onRefresh();
//在所有注册的bean中查找Listener bean,注册到消息广播中
registerListeners();
//初始化剩下的单实例(非惰性的)
finishBeanFactoryInitialization(beanFactory);
//完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出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 ***s in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
下面概括一下ClassPathXmlApplicationContext初始化的步骤,并从中解释一下它为我们提供的功能。
(1)初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证:在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,它可以在Spring启动的时候提前对必须的变量进行存在性验证。
(2)初始化BeanFactory,并进行XML文件读取:之前提到过ClassPathXmlApplicationContext包含着BeanFactory所提供的一切特征,那么在这一步骤中将会复用BeanFactory中的配置文件读取解析及其他功能,这一步之后,ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基础操作了。
(3)对BeanFatory进行各种功能填充:@Qualifer与@Autowired应该是大家非常熟悉的注解,这两个注解正是在这一步骤中增加的支持
(4)子类覆盖方法做额外的处理:Spring之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面就是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在Spring中随处可见,例如在本例中就提供了一个空的函数实现postProcessBeanFactory来方便程序员在业务上做进一步扩展。
(5)激活各种BeanFatory处理器
(6)注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候
(7)为上下文初始化Message源,即对不同语言的消息体进行国际化处理
(8)初始化应用消息广播器,并放入"applicationEventMulticaster"bean中
(9)留给子类来初始化其他的bean
(10)在所有注册的bean中查找listener bean,注册到消息广播器中
(11)初始化剩下的单实例(非惰性的)
(12)完成刷新过程,通知声明周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEven通知别人
环境准备:
prepareRefresh函数的主要是做些准备工作,例如对系统属性以及环境变量的初始化及验证。
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
//留给子类覆盖
initPropertySources();
//验证需要的属性文件是否都已经放入环境中
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
探索下各个函数的作用。
(1)initPropertySources:正符合Spring的开放式结构设计,给用户最大扩展Spring的能力。用户可以根据自身的需要重写initPropertySources方法,并在方法中进行个性化的属性处理及设置。
(2)validateRequiredProperties:则是对属性进行验证,那么如何验证呢?我们举个融合两句代码的小例子来帮助大家理解。
加载BeanFactory:
obtainFreshBeanFactory方法从字面理解是获取BeanFactory。之前说过,ApplicationContext是对BeanFactory在功能上的扩展,不但包含了BeanFactory的全部功能更在其基础上添加了大量的扩展应用,那么obtainFreshBeanFactory正是实现BeanFactory的地方,也就是经过这个函数后,ApplicationContext正式拥有了BeanFactory的全部功能。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建DefualtListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//为了序列化指定id,如果需要的话,让这个BeanFactory从id反序列化到BeanFactory对象
beanFactory.setSerializationId(getId());
//定制beanFactory相关属性,包括是否允许覆盖同名称的不同定义的对象以及
//设置@Autowired和@Qualifier注解解析器QualifierAnnotationAutowiredCandidateResolver
customizeBeanFactory(beanFactory);
//初始化DocumentReader,并进行XML文件读取及解析
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
详细分析上面的每个步骤
(1)创建DefaultListableBeanFactory。
在介绍BeanFactory的时候,不知道大家有没有印象,声明方式为:BeanFactory bf = new XmlBeanFactory("test.xml"),其中的XmlBeanFactory继承自DefaultListableBeanFactory,并提供了XmlBeanDefinitionReader类型的reader属性,也就是说DefaultListableBeanFactory是容器的基础。必须首先实例化,那么在这里就是实例化DefaultListableBeanFactory的步骤。
(2)指定序列化ID
(3)定制BeanFactory
(4)加载BeanDefinition
(5)使用全局变量BeanFactory类实例
因为DefaultListableBeanFactory类型的变量beanFactory是函数内的局部变量,所以要使用全局变量来记录结果。
功能扩展:
上面函数中主要进行了几个方面的扩展。
1.增加对SpEL语言的支持
2.增加对属性编辑器的支持
3.增加对一些内置类,比如EnvironmentAware、MessageSourceAware的信息注入。
4.设置了依赖功能可忽略的接口。
5.注册一些固定依赖的属性
6.增加AspectJ的支持
7.将相关环境变量及属性注册以单利模式注册。
下面我们来一一的看一下每个步骤的流程
BeanFactory的后处理:
BeanFactory作为Spring中容器功能的基础,用于存放所有已经加载的bean,为了保证程序上的高可扩展性,Spring针对BeanFactory做了大量的扩展,比如我们熟知的PostProcessor等都是在这里实现的。
初始化非延迟加载单例
完成BeanFactory的初始化工作,其中包括ConversionService的设置、配置冻结以及非延迟加载的bean的初始化工作。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// 冻结所有的bean定义,说明注册的bean定义将不被修改或任何进一步的处理
beanFactory.freezeConfiguration();
// 初始化剩下的单实例(非惰性的)
beanFactory.preInstantiateSingletons();
}
finishRefresh:
在Spring中还提供了Lifecycle接口,Lifecycle中包含了start/stop方法,实现此接口后Spring会保证在启动的时候调用其start方法开始生命周期,并在Spring关闭的时候调用stop方法来结束生命周期,通常用来配置后台程序,在启动后一直运行(如对MQ进行轮询等)。而ApplicationContext的初始化最后正是保证了这一功能的实现。
protected void finishRefresh() {
// Clear context-level resource ***s (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}