基于最新Spring 5.x,对ClassPathXmlApplicationContext IoC容器的refresh()初始化方法进行了深度解析,包括refresh()方法概述、prepareRefresh()方法以及obtainFreshBeanFactory()方法中的XML资源的加载过程,以及< beans/>标签的解析过程!
上一篇文章:Spring IoC容器初始化源码(1)—setConfigLocations设置容器配置信息中,我们主要讲解了ClassPathXmlApplicationContext IoC容器初始化的setConfigLocations方法的过程和源码,在结尾的时候,我们就说了,这不过是一道开胃小菜而已,真正的容器初始化是在refresh()方法中实现的。
现在我们就来学习这个refresh()方法的过程和源码,本篇文章主要内容是refresh()方法概述、prepareRefresh方法以及obtainFreshBeanFactory方法的前半部分的源码,比如XML资源的加载过程,以及< beans/>标签的解析。
在本系列文章的学习过程中,我们会时不时的指示出哪一部分源码对应着哪一部分学习过的特性,请注意,阅读源码之前一定要至少熟悉Spring IoC的功能,我们最开始的文章就是学习的如何去使用Spring的。并且,如果你看了这几篇源码解析的文章,你会发现其实我们之前学习的很多规则比如beanName生成规则、自动注入的规则等等都是不完整的,阅读源码我们能够进行查漏补缺。
refresh()方法的过程和源码的学习是一道“硬菜”,大部分其他文章都是泛泛而谈。需要说明的是,Spring的源码非常非常的多,很有很多的功能点比如各种后处理器的功能,各种标签的解析等等源码,本文只是引导性的解析了一两个特例,但即使如此,相比于大多数文章来说本文讲解的可以说是非常非常的深入了。
Spring IoC容器初始化源码 系列文章
Spring IoC容器初始化源码(1)—setConfigLocations设置容器配置信息
Spring IoC容器初始化源码(2)—prepareRefresh准备刷新、obtainFreshBeanFactory加载XML资源、解析<beans/>标签
Spring IoC容器初始化源码(3)—parseDefaultElement、parseCustomElement解析默认、扩展标签,registerBeanDefinition注册Bean定义
Spring IoC容器初始化源码(4)—<context:component-scan/>标签解析、spring.components扩展点、自定义Spring命名空间扩展点
Spring IoC容器初始化源码(5)—prepareBeanFactory、invokeBeanFactoryPostProcessors、registerBeanPostProcessors方法
Spring IoC容器初始化源码(6)—finishBeanFactoryInitialization实例化Bean的整体流程以及某些扩展点
Spring IoC容器初始化源码(7)—createBean实例化Bean的整体流程以及构造器自动注入
Spring IoC容器初始化源码(8)—populateBean、initializeBean实例化Bean以及其他依赖注入
< context:property-placeholder/>标签以及PropertySourcesPlaceholderConfigurer占位符解析器源码深度解析
三万字的ConfigurationClassPostProcessor配置类后处理器源码深度解析
基于JavaConfig的AnnotationConfigApplicationContext IoC容器初始化源码分析
文章目录
- Spring IoC容器初始化源码 系列文章
- 1 refresh方法概述
- 2 synchronized同步
- 3 prepareRefresh刷新前准备工作
- 4 obtainFreshBeanFactory获取新BeanFactory
- 4.1 refreshBeanFactory刷新工厂
- 4.2 hasBeanFactory是否存在beanFactory
- 4.3 destroyBeans销毁bean
- 4.4 closeBeanFactory关闭beanFactory
- 4.5 createBeanFactory创建beanFactory
- 4.6 customizeBeanFactory配置beanFactory
- 4.7 loadBeanDefinitions加载Bean定义
- 5 小结
1 refresh方法概述
ClassPathXmlApplicationContext的简单uml类图如下:
创建ClassPathXmlApplicationContext对象时调用的refresh()方法实际上就是容器真正执行初始化的方法。另外,这个方法为什么不叫做init()之类的,而要被命名为refresh()方法呢?因为我们的核心上下文容器即使在初始化之后,还能“刷新”,即销毁原来的容器然后重新初始化,而刷新的方法也是调用的这个refresh()方法。
因此,我们可以这么说:refresh()方法用于加载或者刷新容器的所有配置信息,这些配置可能来自基于Java的配置、XML 文件、properties文件等等地方。
要在“一个”refresh()方法中完成核心IoC容器的初始化操作,要做的事(代码量)肯定非常多,但是Spring却把每一步设计的井井有条,以至于不会让人太难懂,阅读高质量的Spring源码还有一个好处就是这种能够一定程度上的提升我们的编码水平。下面让我们带着疑问一起来看看Spring是如何初始化IoC容器的,以及所谓的“初始化”到底初始化了哪些东西,并且介绍一些Spring留下的扩展点。
refresh()方法是在AbstractApplicationContext类中实现的,源码如下:
/**
* AbstractApplicationContext类的方法
* <p>
* 加载或者刷新容器的所有配置信息,这些配置可能来自基于Java的配置、XML 文件、properties文件等等地方。
*
* @throws BeansException 如果bean factory 无法实例化
* @throws IllegalStateException 如果已初始化,并且不支持多次刷新的尝试
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/*
* 1 初始化/刷新容器前的准备工作,设置Spring容器的启动日期和活动标志以及验证必须属性等工作
*/
prepareRefresh();
/*
* 2 销毁之前的BeanFactory(如果存在),创建新的BeanFactory,核心方法
* 随后加载、解析全部配置文件中的配置,最主要的是将配置文件中的bean定义封装成为BeanDefinition
* 如果开启了注解组件扫描,那么指定目录下使用了注解标注的类同样封装成为BeanDefinition
* 随后将BeanDefinition注册到BeanFactory的相关缓存中,为后续bean实例的初始化做准备(这一步并不会初始化Bean实例)。
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/*
* 3 对新获取的BeanFactory进行一系列配置,这也是applicationContext功能的扩展。
*/
prepareBeanFactory(beanFactory);
try {
/*
* 4 默认是一个空的实现,这是留给子类容器实现对beanFactory后续处理的扩展方法
*/
postProcessBeanFactory(beanFactory);
/*
* 5 实例化所有BeanFactoryPostProcessor(包括其子类 BeanDefinitionRegistryPostProcessor)后处理器
* 并调用相应的回调方法,此时所有 bean 定义——beanDefinition都将已加载,但尚未实例化任何普通 bean。
* 这允许我们重写beanDefinition或添加beanDefinition,修改beanFactory中的beanDefinition的任何可以修改的地方。
*/
invokeBeanFactoryPostProcessors(beanFactory);
/*
* 6 实例化和注册所有BeanPostProcessor后处理器
* 方便后续创建bean实例的时候的回调方法调用
*/
registerBeanPostProcessors(beanFactory);
/*
* 7 为此上下文容器初始化MessageSource消息资源,用于语言国际化处理
*/
initMessageSource();
/*
* 8 为此上下文容器初始化事件广播器,应用事件广播
*/
initApplicationEventMulticaster();
/*
* 10 默认是一个空的实现,这是留给子类容器实现的扩展方法
*/
onRefresh();
/*
* 11 实例化和注册所有Listener监听器
*/
registerListeners();
/*
* 12 实例化所有剩余的普通非延迟单例bean,核心方法
*/
finishBeanFactoryInitialization(beanFactory);
/*
* 13 最后一步:发布相应的事件,回调SmartLifecycle.start()容器生命周期方法
*/
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
//销毁当前beanFactory中所有缓存的单例bean
destroyBeans();
//设置此上下文的'active'标志为false
cancelRefresh(ex);
//将异常传播给调用方
throw ex;
} finally {
//清除常见内省缓存
resetCommonCaches();
}
}
}
可以看到,Spring将每一步都封装到一个方法中,然后通过多个方法的顺序调用来完成容器初始化的逻辑,很有条理!
现在,我们将从旧容器的关闭开始,直到新容器初始化完毕结束,并介绍途中的每一步、每一个方法的目的和源码!某些部分的源码很多调用链路也很深,但是不要怕,可以通过多级目录了解方法之间的关系,跟着源码注释向下看就行了。
2 synchronized同步
首先我们看到的就是整个初始化的一系列方法都是在一个synchronized块中进行的,实际上目前的synchronized锁进过锁升级之后优化得已经非常好了,并不比lock锁差。
synchronized的锁对象,就是AbstractApplicationContext中的一个普通Object对象,加上synchronized用于实现容器refresh、close(destroy)、registerShutdownHook等操作的同步,确保这些操作不会同时执行。
/** Synchronization monitor for the "refresh" and "destroy". */
private final Object startupShutdownMonitor = new Object();
3 prepareRefresh刷新前准备工作
prepareRefresh()方法用于初始化容器之前的准备工作,比如设置Spring容器的启动日期和活动标志以及验证必须属性等工作。
//--------AbstractApplicationContext的相关属性--------
/**
* 此上下文启动时的系统时间(毫秒)
*/
private long startupDate;
/**
* 原子变量,指示此上下文当前是否处于活动状态的标记
*/
private final AtomicBoolean active = new AtomicBoolean();
/**
* 原子变量,指示此上下文是否已关闭的标记
*/
private final AtomicBoolean closed = new AtomicBoolean();
/**
* 刷新容器前注册的本地监听器集合
*/
@Nullable
private Set<ApplicationListener<?>> earlyApplicationListeners;
/**
* 静态指定的监听器集合
*/
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
/**
* 早期发布事件集合
*/
@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;
/**
* AbstractApplicationContext类的方法
* <p>
* 设置Spring容器的启动日期和活动标志以及验证必须属性等工作。
*/
protected void prepareRefresh() {
//切换到活动状态
//设置启动时间
this.startupDate = System.currentTimeMillis();
//设置关闭标记为false
this.closed.set(false);
//设置活动标记为true
this.active.set(true);
//确定日志调试已开启
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
} else {
logger.debug("Refreshing " + getDisplayName());
}
}
/*
* 在上下文环境中初始化任何其他属性源,默认空方法,这是留给子类的实现的扩展点
* 非web容器什么也不做,web容器就会重写该方法
* 我们也可以自定义子类,重写该方法并添加必须属性,下面的步骤就会校验
*/
initPropertySources();
/*
* 检验标记为必须存在的所有属性值在环境变量中是否可解析(是否存在/不为null)
* 如果对应的value为null,那么抛出MissingRequiredPropertiesException异常
*/
getEnvironment().validateRequiredProperties();
//下面的不必关心
// 存储早期的应用监听器列表(此前已经初始化的监听器)
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
} else {
//将本地应用程序监听器重置为预刷新状态
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// 初始化早期事件集合
this.earlyApplicationEvents = new LinkedHashSet<>();
}
3.1 initPropertySources扩展点方法
initPropertySources方法的目的是初始化任何其他属性源,或者对属性源设置必须属性,添加环境变量,设置profile激活环境等操作。
由AbstractApplicationContext类提供的方法,默认是一个空的实现,这是Spring留给子类容器实现的扩展点,非web子类容器默认什么也不做,web子类容器就重写了该方法,被用来在propertySources中尝试替换(“servletContextInitParams”,ServletContextPropertySource)和(“servletConfigInitParams”,ServletConfigPropertySource)这两个Servlet相关的属性源。
非web环境下我们也可以自定义子类容器,重写该方法并添加必须属性,下面的步骤就会校验必要属性是否存在!
/**
* AbstractApplicationContext类的方法
* <p>
* 初始化任何其他属性源,或者对属性源设置必须属性等操作
*/
protected void initPropertySources() {
//默认情况下做操作任何操作。
}
3.1.1 测试initPropertySources
下面测试设置环境以及设置必备属性的功能。 测试类:
/**
* @author lx
*/
public class FirstSpringSource {
public FirstSpringSource() {
System.out.println("FirstSpringSource init");
}
public void methodCall(){
System.out.println("methodCall");
}
public String str;
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "FirstSpringSource{" +
"str='" + str + '\'' +
'}';
}
}
三套profile环境,spring-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--定义bean-->
<bean class="com.spring.source.FirstSpringSource" name="firstSpringSource">
<property name="str" value="不设置profile"/>
</bean>
</beans>
spring-config-dev.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" profile="dev">
<!--dev环境-->
<!--定义bean-->
<bean class="com.spring.source.FirstSpringSource" name="firstSpringSource">
<property name="str" value="dev"/>
</bean>
</beans>
spring-config-uat.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" profile="uat">
<!--uat环境-->
<!--定义bean-->
<bean class="com.spring.source.FirstSpringSource" name="firstSpringSource">
<property name="str" value="uat"/>
</bean>
</beans>
一个自定义的容器:
/**
* 测试必备属性
* @author lx
*/
public class RequiredPropertieClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
public RequiredPropertieClassPathXmlApplicationContext(String s) {
super(s);
}
/**
* 子类容器的扩展
*/
@Override
protected void initPropertySources() {
//获取环境
ConfigurableEnvironment environment = getEnvironment();
//设置必须属性名集合
environment.setRequiredProperties("aa", "bb");
}
}
测试:
@Test
public void initPropertySources() {
System.setProperty("config", "config");
//前面讲过,也可以通过JVM属性设置要激活的profile
System.setProperty("spring.profiles.active", "dev");
//必备属性,如果注释掉这两个属性,那么启动报错
System.setProperty("aa", "aaa");
System.setProperty("bb", "bbb");
//初始化容器,加入多套环境
RequiredPropertieClassPathXmlApplicationContext ac = new RequiredPropertieClassPathXmlApplicationContext("spring-${config}.xml",
"spring-${config}-dev.xml", "spring-${config}-uat.xml");
System.out.println(ac.getBean("firstSpringSource"));
}
我们自定义的容器中,重写了initPropertySources方法,要求必须具有aa和bb属性,如果我们将设置环境变量的代码注释掉,将会启动报错:
org.springframework.core.env.MissingRequiredPropertiesException: The following properties were declared as required but could not be resolved: [aa, bb]
另外,initPropertySources方法中设置的activeProfile的优先级高于JVM环境变量设置的优先级。
3.2 validateRequiredProperties校验属性
getEnvironment()方法我们在上一篇文章就讲过,用于获取容器的环境变量对象,这里的validateRequiredProperties()方法则是用于校验指定的必须存在的属性是否都有对应的属性值,即是否都存在。
该方法是ConfigurablePropertyResolver接口提供的方法,实际调用的是AbstractEnvironment以及AbstractPropertyResolver的实现。
/**
* AbstractEnvironment的方法
* <p>
* 校验指定的必须存在的属性是否都有对应的属性值,即是否都存在。
*
* @throws MissingRequiredPropertiesException 如果任何必需的属性无法解析(不存在)
*/
@Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
//继续调用AbstractPropertyResolver的同名方法
this.propertyResolver.validateRequiredProperties();
}
/**
* AbstractPropertyResolver的属性,表示启动时必须存在的属性名集合
*/
private final Set<String> requiredProperties = new LinkedHashSet<>();
/**
* AbstractPropertyResolver的方法
* <p>
* 校验指定的必须存在的属性是否都有对应的属性值,即是否都存在。
*/
@Override
public void validateRequiredProperties() {
//初始化一个MissingRequiredPropertiesException异常对象
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
//遍历必须存在的属性名集合
for (String key : this.requiredProperties) {
//从属性源中尝试获取每一个属性,如果为null,即没有获取到
if (this.getProperty(key) == null) {
//异常实例添加这个key
ex.addMissingRequiredProperty(key);
}
}
//如果异常实例中的key不为null,那么说明存在没有值的属性,那么抛出异常
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
这里的getProperty(key)方法,我们在上面的setConfigLocations方法中就见识过了,用于从属性源集合中的全部属性源中查找指定key对应的value,最终实际上就是调用PropertySourcesPropertyResolver的getProperty方法,区别就是它会解析value中的嵌套占位符。
4 obtainFreshBeanFactory获取新BeanFactory
obtainFreshBeanFactory方法是非常重要的一个方法,将会销毁之前的BeanFactory(如果存在),创建新的BeanFactory,随后加载、解析全部配置文件中的配置,最主要的是将配置文件中的bean定义封装成对应的Java的bean定义对象——BeanDefinition(如果开启了component-scan注解组件扫描,那么也会将base-package指定目录下使用了注解标注的类同样封装成为BeanDefinition),随后将BeanDefinition注册到BeanFactory的相关缓存中(主要是beanDefinitionNames、beanDefinitionMap、aliasMap),为后续bean实例的初始化做准备(这一步并不会初始化Bean实例)。
obtainFreshBeanFactory方法内部调用了refreshBeanFactory以及getBeanFactory方法,这两个方法都是抽象方法,要求子类实现。
/**
* AbstractApplicationContext的方法
* <p>
* 在子类的实现中刷新beanFactory
*
* @return 最新的beanFactory
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//关闭以前的 beanFactory(如果存在),并初始化一个新的 beanFactory
refreshBeanFactory();
//返回最新初始化的beanFactory
return getBeanFactory();
}
非web应用下,两子类AbstractRefreshableApplicationContext和GenericXmlApplicationContext都提供了refreshBeanFactory方法的不同实现,而我们目前学习的IoC容器ClassPathXmlApplicationContext又是属于AbstractRefreshableApplicationContext的子类,因此我们直接看AbstractRefreshableApplicationContext提供的实现。
4.1 refreshBeanFactory刷新工厂
首先,如果以前存在BeanFactory,则会销毁内部的所有单例bean缓存并关闭工厂,随后会初始化一个新的 BeanFactory,然后解析XML文件,将bean定义解析为BeanDefinition存入新BeanFactory的相关缓存中。
/**
* AbstractRefreshableApplicationContext的属性
*/
@Nullable
private volatile DefaultListableBeanFactory beanFactory;
/**
* AbstractRefreshableApplicationContext类中的refreshBeanFactory方法
* <p>
* 首先会尝试关闭以前的 beanFactory(如果存在),并初始化一个新的 beanFactory,
* 然后解析XML文件,获取bean的定义存入beanFactory中。
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
//beanFactory是否不为null,即是否已存在
if (hasBeanFactory()) {
//销毁beanFactory中的所有Bean
destroyBeans();
//关闭beanFactory
closeBeanFactory();
}
try {
//创建一个新的DefaultListableBeanFactory实例
DefaultListableBeanFactory beanFactory = createBeanFactory();
//设置序列化ID,允许此 beanFactory进行序列化以及反序列化
beanFactory.setSerializationId(getId());
//设置beanFactory相关属性,包括是否允许覆盖同名称的不同定义的对象以及是否允许循环依赖等等
customizeBeanFactory(beanFactory);
//核心方法,解析XML文件,加载bean定义(BeanDefition)
loadBeanDefinitions(beanFactory);
//为beanFactory属性赋值,新的beanFactory初始化完毕
this.beanFactory = beanFactory;
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
4.2 hasBeanFactory是否存在beanFactory
通过判断AbstractRefreshableApplicationContext的beanFactory属性是否不为null来判断此容器中是否已存在一个beanFactory。
/**
* AbstractRefreshableApplicationContext类的方法
* <p
* 确定此容器中是否已存在beanFactory
*/
protected final boolean hasBeanFactory() {
//如果beanFactory不为null那么返回true
return (this.beanFactory != null);
}
4.3 destroyBeans销毁bean
AbstractRefreshableApplicationContext的父类AbstractApplicationContext类的方法,如果此前已存在beanFactory,则销毁容器中所有缓存的单例Bean,并且此过程中会进行销毁回调。
这里的destroyBeans仅仅是一个方法的调用,内部方法的逻辑还是子类实现的。
/**
* AbstractRefreshableApplicationContext的父类AbstractApplicationContext的方法
* <p>
* 销毁容器中所有缓存的单例Bean,并且此过程中会进行销毁回调
*/
protected void destroyBeans() {
//获取beanFactory实例,然后调用destroySingletons方法销毁所有单例bean
//并且还会进行销毁回调(如果设置了)
getBeanFactory().destroySingletons();
}
4.3.1 getBeanFactory获取bean工厂
返回容器内部的beanFactory(实际类型是DefaultListableBeanFactory),调用该方法时要求beanFactory不能为null。
外层obtainFreshBeanFactory方法最后返回的工厂就是调用该方法获取的!
/**
* AbstractRefreshableApplicationContext的方法
*
* @return 返回容器内部的BeanFactory(DefaultListableBeanFactory是实际类型),调用该方法时要求BeanFactory不能为null
*/
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
//返回当前容器内部的beanFactory
DefaultListableBeanFactory beanFactory = this.beanFactory;
//如果beanFactory为null(没有初始化或者被关闭了),那么抛出IllegalStateException异常
if (beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return beanFactory;
}
4.3.2 destroySingletons销毁单例bean
我们说过ApplicationContext扩展了BeanFactory,但是这不代表BeanFactory的实现没有作用了,虽然它们属于BeanFactory的两个分支,但实际上ApplicationContext容器初始化过程中的核心Bean定义的加载、注册、销毁等功能还是使用此前的BeanFactory的古老实现类DefaultListableBeanFactory的源代码来实现的,因此ApplicationContext容器的内部还持有一个BeanFactory容器,实现了代码的复用,当然DefaultListableBeanFactory也可以单独作为容器使用。现在我们进入原始BeanFactory的uml体系:
这里的destroySingletons方法实际上就是DefaultListableBeanFactory类的方法,用于销毁当前beanFactory中所有缓存的单例bean,并且此过程中会调用相关销毁回调方法。
/**
* DefaultListableBeanFactory的方法
* <p>
* 销毁当前beanFactory中所有缓存的单例bean,并且进行销毁回调(如果设置了)
* 清除相关缓存
*/
@Override
public void destroySingletons() {
//调用父类的方法销毁单例bean,并且进行销毁回调(如果设置了)
super.destroySingletons();
//清空DefaultListableBeanFactory类自己的manualSingletonNames属性集合
//即清空所有手动注册的bean,所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean
updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
//删除有关按类型映射的任何缓存,即清空allBeanNamesByType和singletonBeanNamesByType集合
clearByTypeCache();
}
4.3.2.1 super.destroySingletons销毁所有单例bean
super就是指的DefaultListableBeanFactory的父级类DefaultSingletonBeanRegistry,该类同时作为SingletonBeanRegistry接口的默认实现,翻译成中文就是“单例bean注册表”。专门用于单例(singleton)bean实例的管理:注册/存储、销毁/移除、获取,相当于一个大缓存,从源码可以知道,实际上IoC容器的“缓存”在Java中就是一个个的集合而已,同时该类用于完成一些扩展操作,比如bean的销毁回调、Dependent依赖关系的顺序销毁。
super.destroySingletons()方法的大概步骤如下:
- 首先将singletonsCurrentlyInDestruction改为true,表示正在销毁单例bean;
- 随后遍历disposableBeans,并且一个个的销毁对应beanName的bean。如果定义了depends-on顺序,那么被其他bean依赖的bean在其他bean被销毁后才被销毁。销毁一个bean的同时会执行它的销毁回调方法。这里的销毁,实际上就是从缓存中移除。
- 最后将依赖缓存以及单例缓存的容器全部清空。
//-----DefaultSingletonBeanRegistry类中的相关重要属性-----
/**
* 指示我们目前是否在销毁单例bean的标记,默认false,销毁bean时会被改为true,销毁完毕之后会改为false
* <p>
* 用来控制在销毁的时候不能获取单例bean,将会抛出BeanCreationNotAllowedException异常
*/
private boolean singletonsCurrentlyInDestruction = false;
//-----单例bean 注册表-----
/**
* 单例对象的缓存,beanName 到 bean instance映射的map
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 早期单例对象的缓存,beanName 到 bean instance映射的map
* singletonFactory 制造出来的单例的缓存,用于解决循环依赖
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* 单例factory缓存,beanName 到 ObjectFactory映射的map
* ObjectFactory中可以获取单例bean实例,用于解决循环依赖
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 已注册的单例beanName set集合,按注册顺序存入单例名
*/
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/**
* beanName 到支持销毁回调的实例例映射的map,用于bean销毁的回调支持
* 实际上value是一个DisposableBeanAdapter对象
*/
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
/**
* 外部beanName到被包含在外部bean的所有内部beanName set集合映射的map,这实际上和内部bean有关
*/
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
//-----bean之间的依赖关系的缓存,比如depends-on属性、@DependsOn注解、@Autowired等-----
/**
* bean name到依赖该bean的bean name的set集合映射的map
*/
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
/**
* bean name到创建该bean所需依赖的bean name的set集合映射的map
*/
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
/**
* DefaultListableBeanFactory类的父类DefaultSingletonBeanRegistry的方法
* <p>
* 销毁当前BeanFactory中的所有单例bean
*/
public void destroySingletons() {
if (logger.isTraceEnabled()) {
logger.trace("Destroying singletons in " + this);
}
//synchronized同步
synchronized (this.singletonObjects) {
//是否在销毁单例Bean的标记,改为true,表示销毁bean操作的开始
this.singletonsCurrentlyInDestruction = true;
}
//临时记录disposableBeanName的数组
String[] disposableBeanNames;
//synchronized同步
synchronized (this.disposableBeans) {
//获取disposableBeans集合中的所有的beanName存入数组
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
//遍历上面的disposableBeanNames数组
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
//销毁每一个BeanName对应的Bean
destroySingleton(disposableBeanNames[i]);
}
//清空其他依赖关系的缓存容器
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
//清空注册的单例bean相关缓存容器
clearSingletonCache();
}
4.3.2.1.1 destroySingleton销毁一个单例bean
内部调用了父类的同名destroySingleton方法!
/**
* DefaultListableBeanFactory的方法
* <p>
* 销毁指定beanName对应的Bean
*/
@Override
public void destroySingleton(String beanName) {
//调用父类的destroySingleton方法
super.destroySingleton(beanName);
//当前的beanName从手动注册bean名称集合manualSingletonNames缓存中移除
removeManualSingletonName(beanName);
//删除有关按类型映射的任何缓存,即清空allBeanNamesByType和singletonBeanNamesByType集合
clearByTypeCache();
}
4.3.2.1.1.1 super.destroySingleton销毁一个单例bean
销毁指定beanName对应的Bean,实际上就是将该beanName对应的disposableBeans缓存键值对从disposableBeans集合中移除,随后尝试进行销毁回调!
/**
* beanName 到支持销毁回调的实例例映射的map,用于bean销毁的回调支持
* 实际上value是一个DisposableBeanAdapter对象
*/
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
/**
* DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry的方法
* <p>
* 销毁指定beanName对应的Bean
*/
public void destroySingleton(String beanName) {
//从单例bean缓存中删除给定名称的已注册单例bean(如果有)。
removeSingleton(beanName);
//从disposableBeans缓存中移除对应beanName的bean,获取移除的对象disposableBean
DisposableBean disposableBean;
//加锁
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
//销毁bean,并且进行销毁回调
destroyBean(beanName, disposableBean);
}
4.3.2.1.1.1.1 removeSingleton移除单例缓存
从此工厂的相关单例缓存中删除具有给定名称的 bean缓存。
//-----DefaultSingletonBeanRegistry的单例bean注册表相关属性-----
/**
* 单例对象的缓存,beanName 到 bean instance映射的map
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 早期单例对象的缓存,beanName 到 bean instance映射的map
* singletonFactory 制造出来的单例的缓存,用于解决循环依赖
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* 单例factory缓存,beanName 到 ObjectFactory映射的map
* ObjectFactory中可以获取单例bean实例,用于解决循环依赖
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 已注册的单例beanName set集合,按注册顺序存入单例名
*/
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/**
* DefaultSingletonBeanRegistry的方法
* <p>
* 从此工厂的相关单例缓存中删除具有给定名称的 bean
*
* @param beanName beanName
*/
protected void removeSingleton(String beanName) {
//加锁
synchronized (this.singletonObjects) {
//主要从四个单例缓存中移除
this.singletonObjects.remove(beanName);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.remove(beanName);
}
}
4.3.2.1.1.1.2 destroyBean按依赖顺序销毁bean
按照DependsOn的顺序,被其他bean依赖的bean在其他bean被销毁后才被销毁,并将beanName从依赖关系的缓存集合中移除,同时还会尝试执行销毁回调方法。
这些特性我们在IoC学习的时候就讲过了,下面是它们的源码。
//-----DefaultSingletonBeanRegistry的相关属性-----
/**
* 外部beanName到被包含在外部bean的所有内部beanName set集合映射的map,这实际上和内部bean有关
*/
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
//-----bean之间的依赖关系的缓存,比如depends-on属性、@DependsOn注解、@Autowired的依赖、自动注入的依赖等-----
/**
* bean name到依赖该bean的bean name的set集合映射的map
*/
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
/**
* bean name到创建该bean所需依赖的bean name的set集合映射的map
*/
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
/**
1. DefaultSingletonBeanRegistry的方法
2. <p>
3. 按照depends-on的顺序,被其他bean依赖的bean在其他bean被销毁后才被销毁(如果设置了,那么同时执行销毁回调方法)
4. 5. @param beanName beanName
6. @param bean 对应的DisposableBean实例,实际类型为DisposableBeanAdapter
*/
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// Trigger destruction of dependent beans first...
Set<String> dependencies;
//返回依赖于这个beanName的所有beanName集合,这集合中的beanName对应的bean将先一步销毁
synchronized (this.dependentBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
dependencies = this.dependentBeanMap.remove(beanName);
}
//beanName集合不为null
if (dependencies != null) {
if (logger.isTraceEnabled()) {
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
}
//循环该集合,继续调用上面的destroySingleton方法
for (String dependentBeanName : dependencies) {
destroySingleton(dependentBeanName);
}
}
//到这一步,表示依赖该bean的bean都销毁了
//如果DisposableBean实例不为null,表示该bean定义了销毁回调的逻辑
if (bean != null) {
try {
//那么执行我们设置的销毁回调方法
//bean实例类型为DisposableBeanAdapter,内部包装了bean实例以及需要进行销毁回调的方法
bean.destroy();
} catch (Throwable ex) {
//这个回调逻辑不会抛出异常
if (logger.isWarnEnabled()) {
logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex);
}
}
}
/*从containedBeanMap缓存中移除要销毁的bean,并且递归移除它的包含内部bean集合*/
Set<String> containedBeans;
synchronized (this.containedBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
containedBeans = this.containedBeanMap.remove(beanName);
}
if (containedBeans != null) {
for (String containedBeanName : containedBeans) {
destroySingleton(containedBeanName);
}
}
/*从其他bean的依赖项中移除当前已销毁的 bean*/
synchronized (this.dependentBeanMap) {
for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Set<String>> entry = it.next();
Set<String> dependenciesToClean = entry.getValue();
dependenciesToClean.remove(beanName);
if (dependenciesToClean.isEmpty()) {
it.remove();
}
}
}
/*移除已销毁的bean的所依赖的bean*/
this.dependenciesForBeanMap.remove(beanName);
}
4.3.2.1.1.1.2.1 destroy销毁bean回调
进行销毁回调的时候,获取的bean是一个DisposableBeanAdapter对象,内部包装了bean实例以及需要进行销毁回调的方法。支持@PreDestroy注解、XML的destroy-method属性、DisposableBean接口、AutoCloseable接口、"close"以及"shutdown"方法推断等等销毁方法的回调。
我们这里大概只是讲解DisposableBeanAdapter的destory的方法,至于这个DisposableBeanAdapter是怎么来的,后面我们会讲到,当我们学到那里的时候,我们就基本上学完了ClassPathXmlApplicationContext IoC容器的大概初始化过程了,只不过这个过程会经历“九九八十一难”,不知道会有多少小伙伴能坚持下去呢?大概没有十分之一吧!
方法中的某些属性,我们同样在后面会讲,记住它的回调顺序为:
- @PreDestroy注解标注的方法回调;
- DisposableBean接口的destroy方法回调;
- XML的destroy-method属性指定或者Spring自动推断的方法回调;
- 补充:对于同一个方法不会重复调用!
/**
* DisposableBeanAdapter的方法
* 销毁回调,顺序为:
* 1 @PreDestroy注解标注的方法回调
* 2 DisposableBean接口的destroy方法回调
* 3 XML的destroy-method属性指定或者Spring自动推断的方法回调
*/
@Override
public void destroy() {
/*
* 1 @PreDestroy注解标注的方法回调
*
* 实际上是通过DestructionAwareBeanPostProcessor后处理器的postProcessBeforeDestruction方法进行回调的
* 常见的InitDestroyAnnotationBeanPostProcessor后处理器的该方法
*/
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
//
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
/*
* 2 DisposableBean的destroy方法回调
*/
if (this.invokeDisposableBean) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
}
try {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((DisposableBean) this.bean).destroy();
return null;
}, this.acc);
} else {
//调用destroy方法
((DisposableBean) this.bean).destroy();
}
} catch (Throwable ex) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
} else {
logger.warn(msg + ": " + ex);
}
}
}
/*
* 3 XML的destroy-method属性指定或者Spring自动推断的方法回调
*/
if (this.destroyMethod != null) {
//反射调用
invokeCustomDestroyMethod(this.destroyMethod);
} else if (this.destroyMethodName != null) {
Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
if (methodToInvoke != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
}
}
}
4.3.2.1.1.1.2.2 测试销毁回调
测试类:
/**
* @author lx
*/
public class Destroy {
/**
* 实现了DisposableBean接口
*/
public static class Destroy1 implements DisposableBean {
public void destroy() throws Exception {
System.out.println("destroy1销毁回调");
}
public void destroy2() throws Exception {
System.out.println("destroy1销毁回调");
}
}
/**
* 定义了destroy-method属性
*/
public static class Destroy2 {
public void destroy() throws Exception {
System.out.println("destroy2销毁回调");
}
}
/**
* 没有定义回调方法
*/
public static class Destroy3 {
public void destroy() throws Exception {
System.out.println("destroy3销毁回调");
}
}
}
配置文件:
<!--实现了DisposableBean接口,依赖destroy2-->
<bean class="com.spring.source.Destroy.Destroy1" name="destroy1" depends-on="destroy2"/>
<!--配置了destroy属性-->
<bean class="com.spring.source.Destroy.Destroy2" name="destroy2" destroy-method="destroy"/>
<!--没有配置回调函数-->
<bean class="com.spring.source.Destroy.Destroy3" name="destroy3"/>
测试:
@Test
public void destroy() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
ac.refresh();
}
使用debug能明显的看出端倪:
可以看到,实现了DisposableBean接口或者定义了destroy-method属性(实际上包括使用@PreDestroy注解)的bean都被加入到disposableBeans的map中,而这个value实际上是一个DisposableBeanAdapter适配器实例,这个实例包装了bean实例以及其他关于回调方法的信息,回调的时候实际上就是调用该实例的destroy方法,内部会顺序调用:反射调用@PreDestroy注解标注的方法->调用DisposableBean接口实例的destroy方法->反射调用destroy-method属性指定的方法。也就是说,你可以对同一个bean配置三种销毁回调,它们都会被调用,但是如果指向同一个方法,那么只会调用一次回调方法。
本次案例的调用结果:
destroy1销毁回调
destroy2销毁回调
DisposableBeanAdapter的destroy方法源码:
/**
* DisposableBeanAdapter的destroy方法
*/
@Override
public void destroy() {
/*1 反射调用@PreDestroy注解标注的方法*/
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
/*2 调用DisposableBean接口实例的destroy方法*/
if (this.invokeDisposableBean) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
}
try {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((DisposableBean) this.bean).destroy();
return null;
}, this.acc);
}
else {
((DisposableBean) this.bean).destroy();
}
}
catch (Throwable ex) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
}
}
}
/*3 反射调用destroy-method属性指定的方法*/
if (this.destroyMethod != null) {
invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
if (methodToInvoke != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
}
}
}
4.3.2.1.1.2 removeManualSingletonName移除手动注册beanName
DefaultListableBeanFactory中的manualSingletonNames缓存,用于按注册顺序手动注册的单例的beanName列表,后面我们还会遇到这个缓存。
所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean在注册bean定义完成之后,Spring会手动注册一些bean,比如前面说的环境变量:“environment”、“systemProperties”。
该方法内部使用了Java8的新特性lambda表达式!
/**
* DefaultListableBeanFactory的属性
* 按注册顺序手动注册的单例的beanName列表缓存
* <p>
* 所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean
* 在注册bean定义完成之后,Spring会手动注册一些bean,比如前面说的环境变量:"environment"、"systemProperties"
*/
private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
/**
* DefaultListableBeanFactory的方法
* <p>
* 当前的beanName从manualSingletonNames缓存中移除
* 实际上是调用updateManualSingletonNames方法,我们在前面讲的destroySingletons()方法中就是见识过这个方法
* updateManualSingletonNames方法方法用于更新manualSingletonNames集合的数据
*
* @param beanName 要移除的beanName
*/
private void removeManualSingletonName(String beanName) {
//action操作,表示移除给定beanName,lambda表达式的应用
//condition操作,判断集合是否包含给定beanName,lambda表达式的应用
updateManualSingletonNames(set -> set.remove(beanName), set -> set.contains(beanName));
}
/**
* DefaultListableBeanFactory的方法,java8 lambda表达式的应用
* <p>
* 更新工厂内部的手动注册的单例bean的名字集合
* 所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean
*
* @param action 更新操作
* @param condition action更新操作的先决条件(如果该条件不适用,则可以跳过该更新操作)
*/
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
/*如果已经有其他任何bean实例开始初始化了*/
if (hasBeanCreationStarted()) {
//加锁防止并发操作集合
synchronized (this.beanDefinitionMap) {
//断言如果满足条件(lambda)
if (condition.test(this.manualSingletonNames)) {
//新建集合
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
//接收一个更新操作(lambda)
action.accept(updatedSingletons);
//新集合替代原集合
this.manualSingletonNames = updatedSingletons;
}
}
}
/*不需要加锁*/
else {
//断言如果满足条件(lambda)
if (condition.test(this.manualSingletonNames)) {
//接收一个更新操作(lambda)
action.accept(this.manualSingletonNames);
}
}
}
4.3.2.1.2 clearSingletonCache清理单例bean缓存
清空注册的单例bean相关缓存容器!
/**
* DefaultSingletonBeanRegistry的方法
* <p>
* 清空注册的单例bean相关缓存容器
*/
protected void clearSingletonCache() {
//synchronized同步
synchronized (this.singletonObjects) {
//清空四个单例缓存容器
this.singletonObjects.clear();
this.singletonFactories.clear();
this.earlySingletonObjects.clear();
this.registeredSingletons.clear();
//标志位改为false,表示销毁bean的操作结束
this.singletonsCurrentlyInDestruction = false;
}
}
4.4 closeBeanFactory关闭beanFactory
在销毁beanFactory中的所有单例bean以及相关缓存之后,调用closeBeanFactory方法关闭容器内部的beanFactory,即就是将beanFactory属性置为null,由于没有外部引用,beanFactory实例将会被GC回收。
/**
* AbstractRefreshableApplicationContext的方法
* <p>
* 关闭上下文内部的bean工厂,即就是将beanFactory属性置为null,beanFactory将会被GC回收
*/
@Override
protected final void closeBeanFactory() {
DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory != null) {
beanFactory.setSerializationId(null);
this.beanFactory = null;
}
}
4.5 createBeanFactory创建beanFactory
在关闭原来的beanFactory之后,新建一个beanFactory。
同时会忽略一批Aware感知接口的setter自动注入:BeanNameAware、BeanFactoryAware、BeanClassLoaderAware,后面的方法还会陆续注入一些接口!这三个感知接口将会在后面的initializeBean方法中被调用,用来获取一些属性或者变量!
/**
* AbstractRefreshableApplicationContext类的方法
* <p>
* 为此上下文容器创建内部 beanFactory,每次refresh都会尝试创建新的 beanFactory
*
* @return 此上下文容器内部的beanFactory,DefaultListableBeanFactory类型
*/
protected DefaultListableBeanFactory createBeanFactory() {
//根据父工厂创建一个beanFactory
//非web环境下,工厂的父工厂默认为null,web环境下,Spring的beanFactory就是Spring MVC的beanFactory的父工厂
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
/**
* DefaultListableBeanFactory的构造器,将会初始化相关属性容器
* <p>
* 使用给定父级创建新的beanFactory
*
* @param parentBeanFactory 父beanFactory
* 非web环境下,工厂的父工厂默认为null
* web环境下,Spring的beanFactory就是Spring MVC的beanFactory的父工厂
*/
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
//调用父类AbstractAutowireCapableBeanFactory的构造器
super(parentBeanFactory);
}
/**
* AbstractAutowireCapableBeanFactory的构造器,将会初始化相关属性容器
*/
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
/**
* AbstractAutowireCapableBeanFactory的构造器
*/
public AbstractAutowireCapableBeanFactory() {
//调用父类AbstractBeanFactory的构造器
super();
//忽略给定依赖接口的setter自动装配,主要是Aware接口
//如果存在接口A,类Aimpl实现A,添加忽略A之后,Aimpl将忽略和A中相同的setter方法的自动注入
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
/**
* AbstractBeanFactory的构造器,将会初始化相关属性容器
*/
public AbstractBeanFactory() {
}
/**
* AbstractAutowireCapableBeanFactory的属性
* <p>
* 要忽略setter自动注入的类型的列表
*/
private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>();
/**
1. AbstractAutowireCapableBeanFactory的方法
2. <p>
3. 忽略给定依赖接口的setter自动装配,这通常被应用程序上下文用于注册。
*/
public void ignoreDependencyInterface(Class<?> ifc) {
this.ignoredDependencyInterfaces.add(ifc);
}
4.6 customizeBeanFactory配置beanFactory
customizeBeanFactory方法用于配置beanFactory的两个属性:
- allowBeanDefinitionOverriding:在registerBeanDefinition注册bean定义的时候判断是否允许同名的BeanDefinition 覆盖,默认允许true,Springboot则对其进一步封装,默认不允许false。
- 该属性是针对XML配置的不同< beans/>中的同名bean,如果为true,则同名bean会覆盖,如果为false,则抛出异常。
- 如果在同一< beans/>配置中具有重复的id 或 name,或者对于外部类/静态内部类使用注解配置时的使用了相同的name,在解析时候会报错;如果是普通内部类使用了注解配置,或者采用@Bean配置,则允许bean的覆盖,不会报错。这些特性与该属性无关!
- allowCircularReferences:是否允许循环依赖引用,默认允许true。
- Spring可以自动处理setter方法和注解属性的循环依赖注入,但不能自动处理构造器以及dependsOn的循环依赖注入,并且要求循环依赖的bean不能都是prototye的。
想要自定义这两个属性,则通过容器调用对应的方法就行了,记得之后调用refresh刷新容器!
//-----AbstractRefreshableApplicationContext的属性-----
@Nullable
private Boolean allowBeanDefinitionOverriding;
@Nullable
private Boolean allowCircularReferences;
/**
* AbstractRefreshableApplicationContext的方法
* <p>
* 配置beanFactory是否允许 BeanDefinition 覆盖、是否允许循环引用。
*/
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 是否允许 BeanDefinition覆盖,默认为null
if (this.allowBeanDefinitionOverriding != null) {
//设置AbstractAutowireCapableBeanFactory的属性
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否允许循环引用,默认为null
if (this.allowCircularReferences != null) {
//设置AbstractAutowireCapableBeanFactory的属性
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
//AbstractAutowireCapableBeanFactory的属性
/**
* 是否自动尝试解决 bean 之间的循环依赖,默认为true
*/
private boolean allowCircularReferences = true;
/**
* 是否允许重新注册具有相同名称的不同定义,即覆盖,默认为true
*/
private boolean allowBeanDefinitionOverriding = true;
4.7 loadBeanDefinitions加载Bean定义
loadBeanDefinitions是一个核心方法,该方法主要工作如下:
1 通过XmlBeanDefinitionReader定位、解析XML配置文件得到Document对象。
2 将Document对象中的bean的定义解析为一个个BeanDefinition对象并注册到容器的缓存中。
loadBeanDefinitions方法由ClassPathXmlApplicationContext的直接父类AbstractXmlApplicationContext实现:
/**
* AbstractXmlApplicationContext的方法
* <p>
* 通过XmlBeanDefinitionReader加载bean的定义
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//为给定的 BeanFactory 创建新的XmlBeanDefinitionReader,用于加载解析配置文件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
/*配置XmlBeanDefinitionReader*/
//设置环境变量对象
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置资源加载器resourceLoader,用于加载XML文件到内存中成为Resource。这里设置的是父类AbstractBeanDefinitionReader的属性,
//将其设置为当前ClassPathXmlApplicationContext容器对象,因为容器也实现了ResourceLoader接口
beanDefinitionReader.setResourceLoader(this);
//设置SAX 实体解析器,用于分析Document
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//初始化beanDefinitionReader,允许子类提供自定义初始化方法
initBeanDefinitionReader(beanDefinitionReader);
/*
* 实际加载 bean 定义的方法,核心方法
*/
loadBeanDefinitions(beanDefinitionReader);
}
4.7.1 new XmlBeanDefinitionReaderXML读取器
XmlBeanDefinitionReader的构造器将会调用父类构造器,并且初始化registry 、resourceLoader和environment属性。
XML文件的加载是由内部的resourceLoader加载的,这个resourceLoader实际上就是PathMatchingResourcePatternResolver实例,支持Ant路径字符串模式匹配,但是随后会被beanDefinitionReader.setResourceLoader(this)方法覆盖这个属性,变成ClassPathXmlApplicationContext实例。
参数的beanFactory实际上就是当前上下文中的beanFactory实例。
/**
* XmlBeanDefinitionReader的构造器
* <p>
* 为给定的 BeanFactory 创建新的XmlBeanDefinitionReader
*/
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
//调用父类AbstractBeanDefinitionReader的构造器
super(registry);
}
/**
* AbstractBeanDefinitionReader的构造器
* 初始化reader的resourceLoader和environment
*
* @param registry
*/
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//初始化resourceLoader,实际上就是创建的DefaultListableBeanFactory实例
this.registry = registry;
//初始化resourceLoader
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
} else {
//将会创建一个PathMatchingResourcePatternResolver,支持模式匹配
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
//初始化environment
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
} else {
this.environment = new StandardEnvironment();
}
}
4.7.2 loadBeanDefinitions(beanDefinitionReader)加载Bean定义
loadBeanDefinitions最后调用同名loadBeanDefinitions(beanDefinitionReader)方法,用于继续加载bean 定义。
- 首先尝试获取配置文件的Resource数组configResources,该属性在ClassPathXmlApplicationContext中,默认为null。在前面的setConfigLocations方法中解析的是configLocations配置文件路径字符串数组,注意区分。
- 如果configResources不为null,随后调用XmlBeanDefinitionReader的loadBeanDefinitions方法,根据configResources加载bean 的定义。
- 如果configResources为null,那么获取配置文件路径字符串数组configLocations,在此前最外层的setConfigLocations方法中已经初始化了。非web容器第一次进来默认就是走的这个逻辑。
- 如果configLocations不为null,随后调用XmlBeanDefinitionReader的loadBeanDefinitions方法,根据configLocations加载bean 的定义。实际上该方法内部会从资源路径字符串处加载资源成为Resource,从还是会调用上面的loadBeanDefinitions(Resource)方法。
/**
* AbstractXmlApplicationContext类的方法
* <p>
* 使用给定的XmlBeanDefinitionReader加载bean的定义
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取配置文件的Resource数组,该属性在ClassPathXmlApplicationContext中,默认为null
//在前面的setConfigLocations方法中解析的是configLocations配置文件路径字符串数组,注意区分
Resource[] configResources = getConfigResources();
if (configResources != null) {
//调用reader自己的loadBeanDefinitions方法,加载bean 的定义
reader.loadBeanDefinitions(configResources);
}
//获取配置文件路径数组,在此前最外层的setConfigLocations方法中已经初始化了
//非web容器第一次进来默认就是走的这个逻辑
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//调用reader自己的loadBeanDefinitions方法,加载bean 的定义
//内部会从资源路径字符串处加载资源成为Resource,从还是会调用上面的loadBeanDefinitions方法
reader.loadBeanDefinitions(configLocations);
}
}
//--------获取configResources--------
/**
* ClassPathXmlApplicationContext的属性
* <p>
* 配置文件Resource资源数组
*/
@Nullable
private Resource[] configResources;
/**
* ClassPathXmlApplicationContext的方法
* <p>
* 获取配置文件Resource资源数组,在此前最外层的setConfigLocations方法中没有初始化
*/
@Override
@Nullable
protected Resource[] getConfigResources() {
return this.configResources;
}
//--------获取configLocations--------
/**
* AbstractRefreshableConfigApplicationContext的属性
* <p>
* 配置文件路径字符串数组,在此前最外层的setConfigLocations方法中已经初始化了
*/
@Nullable
private String[] configLocations;
/**
* AbstractRefreshableConfigApplicationContext的方法
* <p>
* 获取配置文件路径字符串数组
*/
@Nullable
protected String[] getConfigLocations() {
return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}
/**
* AbstractRefreshableConfigApplicationContext的方法
* <p>
* 默认配置文件资源位置
*/
@Nullable
protected String[] getDefaultConfigLocations() {
return null;
}
4.7.2.1 reader.loadBeanDefinitions(configLocations) 解析路径加载
非web容器第一次进来默认就是走的这个逻辑。调用XmlBeanDefinitionReader的loadBeanDefinitions方法,根据configLocations加载bean 的定义。在此前最外层的setConfigLocations方法中已经初始化了configLocations。
reader.loadBeanDefinitions(configLocations)方法实际上是XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader实现的:
/**
* AbstractBeanDefinitionReader的方法
* <p>
* 从指定的资源位置加载 bean 定义
*
* @param locations 资源路径字符串数组
* @return 找到的 bean 定义的数量
* @throws BeanDefinitionStoreException
*/
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
//用于计算找到的 bean 定义的数量的计数器
int count = 0;
//循环加载每一个配置文件路径下的配置文件,返回每一个配置文件的bean定义的计数
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
内部循环调用loadBeanDefinitions(location)方法:
/**
1. AbstractBeanDefinitionReader的方法
2. <p>
3. 从指定的资源路径加载 bean 定义。
4. 该路径字符串也可以是一个模式匹配,前提是此 bean definition reader的ResourceLoader是ResourcePatternResolver。
5. 6. @param locations 资源路径字符串
7. @return 找到的 bean 定义的数量
8. @throws BeanDefinitionStoreException
*/
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
4.7.2.1.1 reader.loadBeanDefinitions(location, null)解析路径加载
继续调用另一个loadBeanDefinitions(location, null)方法加载bean定义:
- 由于reader不能直接解析路径字符串,因此需要首先使用内部的resourceLoader的getResources方法加载指定路径字符串下(这个路径支持模式匹配)的XML文件资源到内存中成为Resource数组;
- 通常一个路径对应一个XML文件,则resources数组只有一个Resource元素;但是如果路径有模式匹配,并且匹配到多个资源文件,则resources数组可能有多个Resource元素。即要给XML文件对应一个Resource资源
- 随后继续调用调用AbstractBeanDefinitionReader的另一个loadBeanDefinitions方法,解析Resource资源数组,返回解析到的bean 定义数量。这个方法我们之前的"loadBeanDefinitions(beanDefinitionReader)"中就见过了,后面会深入解析。
/**
* AbstractBeanDefinitionReader的方法
* <p>
* 从指定的资源路径位置加载 bean 定义
*
* @param location 指定资源路径字符串
* @param actualResources 不需要关心,为null
*/
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
/*
* 1 获取resourceLoader,用来加载资源,实际上就是当前ClassPathXmlApplicationContext容器实例,支持路径字符串模式匹配
* 如果是web应用,则是XmlWebApplicationContext,如果是boot项目,则可能根本就不会走这个方法
*/
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
/*
* 如果支持资源Ant模式匹配,走这一条分支
* ClassPathXmlApplicationContext和XmlWebApplicationContext都是ResourcePatternResolver的实现
*/
if (resourceLoader instanceof ResourcePatternResolver) {
try {
/*
* 2 调用resourceLoader的方法,将给定的路径的XML文件资源解析为Resource资源对象数组
* 通常一个路径对应一个XML文件,则resources数组只有一个Resource元素
* 但是如果路径有模式匹配,匹配到多个资源文件,则resources数组可能有多个Resource元素
*/
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
/*
* 3 调用AbstractBeanDefinitionReader的另一个loadBeanDefinitions方法,解析Resource资源数组,返回解析到的bean 定义数量
* 这个方法我们之前的"loadBeanDefinitions(beanDefinitionReader)"中就见过了
*/
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
/*
* 如果不支持资源Ant模式匹配
*/
else {
// Can only load single resources by absolute URL.
/*通过绝对URL路径加载单个资源*/
Resource resource = resourceLoader.getResource(location);
//根据Resource加载bean定义
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
4.7.2.1.1.1 getResources加载资源
AbstractApplicationContext的getResources方法,实际上是委托内部的resourcePatternResolver去解析路径字符串的,实际类型是一个PathMatchingResourcePatternResolver对象,这个属性是在最开始的super(parent)方法中设置的,这里终于用上了。
如果具有"classpath*:"前缀,将会在本项目以及引入的jar包的类路径中查找资源。
/**
* AbstractApplicationContext的属性
* <p>
* 此上下文使用的资源模式解析器
* <p>
* 这个解析器是在最开始的super(parent)方法中设置的,我们此前已经介绍了,这里终于用上了
*/
private ResourcePatternResolver resourcePatternResolver;
/**
* AbstractApplicationContext重写的方法
* <p>
* 委托resourcePatternResolver解析Ant资源路径字符串,获取Resource资源数组
*
* @param locationPattern 本地路径,支持Ant模式匹配
* @return Resource资源数组
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
//委托resourcePatternResolver解析,实际类型是一个PathMatchingResourcePatternResolver对象
return this.resourcePatternResolver.getResources(locationPattern);
}
/**
* ResourcePatternResolver的常量
* <p>
* 路径前缀,和"classpath:"不同的是,它将检索给定路径名称下的所有匹配资源,包括所有引入的Jar文件
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
1. PathMatchingResourcePatternResolver的方法
2. <p>
3. 解析资源路径字符串,获取Resource资源数组
4. 路径支持Ant模式匹配:
5. "*" 表示任意数量的字符,或者任意一级路径(必须存在这一级路径)
6. "**" 表示任意几级路径,可以没有路径
7. "?" 表示任意一个字符,必须存在
8. 9. @param locationPattern 本地路径
10. @return Resource资源数组
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
/*
* 如果当前路径以"classpath*:"开头,这表示将加载本项目以及引入的jar包的类路径下的资源
*/
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
//判断路径中是否具有模式通配符"*"、"?"、"{ }",如果有那么使用模式匹配
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
//如果没有,那么在指定路径下匹配
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
/*
* 否则,表示将只加载本项目路径下的资源
*/
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// 使用模式匹配
return findPathMatchingResources(locationPattern);
} else {
//查找单个文件资源
return new Resource[]{getResourceLoader().getResource(locationPattern)};
}
}
}
路径字符串还支持Ant模式匹配:
- “*” :表示任意数量的字符(可以没有),或者任意一级路径(必须存在这一级路径);
- “**” :表示任意数量的字符(可以没有),或者任意几级路径(可以没有路径);
- “?” :表示任意一个字符(必须存在一个字符)。
Ant测试:
@Test
public void ant() {
AntPathMatcher antPathMatcher = new AntPathMatcher();
//false
System.out.println(antPathMatcher.match("/*/", "/xx/yy/"));
//true
System.out.println(antPathMatcher.match("/**/", "/xx/yy/"));
//false
System.out.println(antPathMatcher.match("xx/*/yy", "xx/yy"));
//true
System.out.println(antPathMatcher.match("xx/**/yy", "xx/yy"));
//true
System.out.println(antPathMatcher.match("xx/a**/yy", "xx/a/yy"));
//true
System.out.println(antPathMatcher.match("xx/a*/yy", "xx/a/yy"));
//false
System.out.println(antPathMatcher.match("xx/a?/yy", "xx/a/yy"));
//true
System.out.println(antPathMatcher.match("xx/a?/yy", "xx/ab/yy"));
//false
System.out.println(antPathMatcher.match("xx/a?/yy", "xx/abc/yy"));
}
4.7.2.2 reader.loadBeanDefinitions(resources)解析Resource加载bean
在上面的代码中,使用resourceLoader将指定字符串路径下的XML文件加载到内存中变成Resource资源之后,继续调用reader的另一个loadBeanDefinitions(resources)方法,根据这些Resource资源数组,解析内部的bean定义,最后返回找到的bean 定义数量。
/**
* AbstractBeanDefinitionReader的方法
*
* @param resources 解析XML文件加载到内存中的Resource资源数组
* @return 找到的bean 定义的数量
*/
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
//循环解析每一个资源
for (Resource resource : resources) {
//调用XmlBeanDefinitionReader的方法解析资源
count += loadBeanDefinitions(resource);
}
return count;
}
内部同样是循环解析resource,但是这里循环中调用的loadBeanDefinitions(resource)方法是XmlBeanDefinitionReader自己实现的。
/**
* AbstractBeanDefinitionReader的方法
*
* @param resources 解析XML文件加载到内存中的Resource资源数组
* @return 找到的bean 定义的数量
*/
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
//循环解析每一个资源
for (Resource resource : resources) {
//调用XmlBeanDefinitionReader的方法解析资源
count += loadBeanDefinitions(resource);
}
return count;
}
/**
* XmlBeanDefinitionReader的方法
* <p>
* 从指定的 XML 文件对应的Resource资源中加载 bean 定义。
*
* @param resource XML文件加载到内存中的Resource资源
* @return 找到的bean 定义的数量
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//对resource资源进行编码,随后调用XmlBeanDefinitionReader的另一个方法
return loadBeanDefinitions(new EncodedResource(resource));
}
继续向下调用,loadBeanDefinitions(encodedResource)方法,在该方法中调用另一个核心方法doLoadBeanDefinitions继续加载bean定义。实际上Spring中真正的核心方法一般都是以do开头的,后面我们会遇到更多的以do开头的方法。
/**
1. XmlBeanDefinitionReader的方法
2. <p>
3. 从指定的 XML 文件对应的Resource资源中加载 bean 定义。
4. 5. @param encodedResource 编码后的resource资源
6. @return 找到的bean 定义的数量
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
//通过ThreadLocal获取当前线程的配置文件资源set集合
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
//添加失败,那么抛出异常,表明循环加载了
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//获取输入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
/*
* 核心方法,加载bean 定义,返回找到的bean 定义的数量
*/
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
//移除已被加载的resource
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
//防止内存泄漏
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
4.7.3 doLoadBeanDefinitions解析/注册bean定义
核心解析方法就是doLoadBeanDefinitions方法,主要做两件事:
- 使用sax解析,根据DocumentLoader和entityResolver调用doLoadDocument方法继续解析resource资源,使其成为一个Document文档对象。
- 随后调用registerBeanDefinitions方法继续解析document,并对解析出来的bean定义进行注册,返回找到的bean定义数量。
有了Document,就能很方便的解析XML中的标签了,因为Document封装了XML文件中标签,属性,值等等数据,形成一个树形状,称为DOM树,根标签就是DOM树的根节点。一个Document对应着一个XML的抽象,里面的各种该标签和属性都对应着一个Java中的对象,比如Element对象就是标签的抽象。
Spring使用的XML的解析的方式是Jaxp解析器下的SAX解析,不需要额外jar包,涉及到的类比如Document、Element等等是位于JDK的rt.jar中,Jaxp解析器还有dom解析方式,另外还有dom4J解析器可以用来解析,但是这个需要额外的jar包。doLoadBeanDefinitions方法之后的许多方法都涉及到XML的解析,本文不作详细解释 ,如果有读者需要了解XML以及XML解析的(大部分Java开发者都不会做XML解析的工作),可以联系我,单独写几篇文章介绍XML的解析!
/**
* XmlBeanDefinitionReader的方法
* <p>
* 使用SAX解析,加载XML中的bean 定义
*
* @param inputSource 要从中读取数据的SAX 输入源
* @param resource XML 文件的资源描述符,一个Resource
* @return 找到的 bean 定义数量
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
/*
* 1 使用sax解析,使用DocumentLoader和entityResolver继续解析resource资源
* 使其成为一个Document文档对象,一个Document对应着一个XML
*
* Document是属于w3c中的东西(位于JDK的rt.jar中),有了Document,就能很方便的解析XML中的标签了
* 因为Document封装了XML文件中标签,属性,值等等数据,形成一个树形状,称为DOM树,根标签就是DOM树的根节点
*/
Document doc = doLoadDocument(inputSource, resource);
/*
* 2 继续解析doc文档,将里面的bean定义解析出来并注册到容器中,返回找到的bean定义数量
*/
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
//抛出一系列异常
// ………………
}
4.7.3.1 registerBeanDefinitions解析document注册Bean定义
registerBeanDefinitions方法将会创建一个documentReader对象,并用documentReader将doc里面的bean定义解析出来并注册到容器中,返回找到的bean定义数量。
/**
* XmlBeanDefinitionReader的方法
* <p>
* 使用BeanDefinitionDocumentReader分析 DOM 文档对象中包含的 bean 定义,并且对bean 定义进行注册
*
* @param doc DOM对象
* @param resource XML文件的资源描述符
* @return 找到的 bean 定义数量
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
/*
* 创建bean定义文档分析器,专门用于分析DOM文档中的bean定义
* 实际类型为DefaultBeanDefinitionDocumentReader
*/
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
/*
* getRegistry()获取bean定义的注册器(实际上就是上下文内部的beanFactory工厂实例)
* 随后调用getBeanDefinitionCount()方法返回目前注册表中已定义的 bean 数量
*/
int countBefore = getRegistry().getBeanDefinitionCount();
/*
* 核心方法,使用documentReader解析doc文档对象,然后将解析到的bean定义注册到容器中
*/
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
/*
* 获取最新的注册表中已定义的bean数量,减去最开始获取的数量countBefore,即得到本次解析找到的 bean 定义数量
*/
return getRegistry().getBeanDefinitionCount() - countBefore;
}
4.7.3.1.1 createReaderContext创建XmlReaderContext上下文
createReaderContext用于创建一个XmlReaderContext上下文,上下文对象主要用于存放在解析时会用到的一些上下文信息。
/**
* XmlBeanDefinitionReader的方法,创建XmlReaderContext上下文
* <p>
* 创建一个XmlReaderContext传递给文档读取器
*/
public XmlReaderContext createReaderContext(Resource resource) {
//新建一个XmlReaderContext对象
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* XmlBeanDefinitionReader的方法
* <p>
* 如果之前未设置,则创建一个默认的命名空间处理器解析器
*/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
//创建DefaultNamespaceHandlerResolver
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
/**
* XmlBeanDefinitionReader的方法
* <p>
* 创建默认的NamespaceHandlerResolver,实际类型为DefaultNamespaceHandlerResolver
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
/**
* DefaultNamespaceHandlerResolver的属性
* <p>
* 要查找映射文件的默认位置。可以存在于多个 JAR 文件中,即可以从jar包中加载。
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
/**
* DefaultNamespaceHandlerResolver的构造器
* <p>
* 使用默认映射文件位置创建一个DefaultNamespaceHandlerResolver
*
* @param classLoader 用于加载映射资源的ClassLoader,如果为null,则使用线程上下文类加载器
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
/**
* DefaultNamespaceHandlerResolver的构造器
* <p>
* 使用提供的映射文件位置创建一个新的DefaultNamespaceHandlerResolver
*
* @param classLoader 用于加载映射资源的ClassLoader,如果为null,则使用线程上下文类加载器
* @param handlerMappingsLocation 映射文件位置
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
/**
* XmlReaderContext的构造器
* <p>
* Construct a new {@code XmlReaderContext}.
*
* @param resource the XML bean definition resource
* @param problemReporter the problem reporter in use
* @param eventListener the event listener in use
* @param sourceExtractor the source extractor in use
* @param reader the XML bean definition reader in use
* @param namespaceHandlerResolver the XML namespace resolver
*/
public XmlReaderContext(
Resource resource, ProblemReporter problemReporter,
ReaderEventListener eventListener, SourceExtractor sourceExtractor,
XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) {
super(resource, problemReporter, eventListener, sourceExtractor);
this.reader = reader;
this.namespaceHandlerResolver = namespaceHandlerResolver;
}
上面有一个非常重要的属性namespaceHandlerResolver,默认是DefaultNamespaceHandlerResolver的实例,它被用来加载handlerMappings下的自定义命名空间以及该命名空间的NamespaceHandler处理器,存放到内部的handlerMappings映射集合中,而handlerMappings文件的默认地址和名字为"META-INF/spring.handlers"。在后面parseCustomElement方法的解析外部引入以及自定义的命名空间下的标签时,就是使用到对应命名空间的NamespaceHandler来解析的。
在使用spring-context依赖的情况下,默认会加载九个handlerMapping:
我们并没有在默认位置下设置映射文件,那么它是怎么加载到这些命名空间映射的呢?实际上这里的"META-INF/spring.handlers"路径也可以存在于多个 JAR 文件中,即可以从引入的jar包中的路径下加载!而我们引入的jar中就能找到这些命名空间的handlerMapping!
4.7.3.1.2 documentReader.registerBeanDefinitions解析标签节点
通过documentReader从给定的 DOM 文档中读取 bean 定义,并在给定的读取器上下文中(readerContext)将它们注册到注册表中。
/**
1. DefaultBeanDefinitionDocumentReader的方法
2. <p>
3. 从给定的 DOM 文档中读取 bean 定义,并在给定的读取器上下文中(readerContext)将它们注册到注册表中。
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//解析doc的根节点,并将解析到的bean定义注册到注册表中
doRegisterBeanDefinitions(doc.getDocumentElement());
}
内部调用doRegisterBeanDefinitions方法,这是真正的解析、加载bean定义的方法!
- 首先调用createDelegate创建一个新的BeanDefinition解析器,同时解析< beans/>根标签元素的各种属性。
- 随后判断当前< beans/>标签元素有没有设置profile属性,如果有的话,会继续检查环境变量中指定活动的profile(可以指定多个)有没有包含当前< beans/>标签设置profile属性值。如果没有,那么直接返回,不加载该< beans/>标签下的bean定义。
- 如果有,那么调用parseBeanDefinitions方法真正的开始继续向下解析< beans/>标签中的bean定义。在该方法之前和之后都留有扩展点方法preProcessXml(root) 和 postProcessXml(root),DefaultBeanDefinitionDocumentReader的子类可以自定义实现。
/**
* DefaultBeanDefinitionDocumentReader的属性,标签属性名常量
*/
public static final String PROFILE_ATTRIBUTE = "profile";
/**
* DefaultBeanDefinitionDocumentReader的方法
*
* <p>
* 真正的处理方法
* 从给定的文档根标签<beans />中查找、注册每个 bean 定义。
*/
protected void doRegisterBeanDefinitions(Element root) {
//获取bean定义的解析器对象delegate,首先获取目前的delegate作为parent
//因为 <beans/> 标签可以嵌套,也有父子关系,可能进行递归调用
BeanDefinitionParserDelegate parent = this.delegate;
/*
* 根据root和parent创建新的delegate,同时解析<beans/>根标签的属性
*/
this.delegate = createDelegate(getReaderContext(), root, parent);
//是否是默认命名空间 http://www.springframework.org/schema/beans的标签
if (this.delegate.isDefaultNamespace(root)) {
//获取当前<beans/>标签的profile属性的值
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//如果指定了profile属性
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//如果环境变量中指定活动的profile没有包含当前<beans/>标签的profile属性值
//那么不加载这个<beans/>标签下的bean定义直接返回
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//如果有,那么继续解析
//在开始处理 bean 定义之前的扩展点位。默认实现为空,子类可以重写此方法
preProcessXml(root);
//真正的从根元素开始进行bean定义的解析
parseBeanDefinitions(root, this.delegate);
//在完成 bean 定义处理后的扩展点位。默认实现为空,子类可以重写此方法
postProcessXml(root);
//赋值
this.delegate = parent;
}
/**
* DefaultBeanDefinitionDocumentReader的方法
* <p>
* 在开始处理 bean 定义之前的扩展点位。默认实现为空,子类可以重写此方法
*/
protected void preProcessXml(Element root) {
}
/**
* DefaultBeanDefinitionDocumentReader的方法
* <p>
* 在完成 bean 定义处理后的扩展点位。默认实现为空,子类可以重写此方法
*/
protected void postProcessXml(Element root) {
}
4.7.3.1.2.1 createDelegate创建bean定义解析器
createDelegate方法除了创建bean定义解析器之外,另一个最重要的功能就是解析并初始化< beans/>根标签的一些属性,这些属性可以是自己设置的,如果没有设置那么可以从父< beans/>标签中继承。
这些属性会被初始化并保存,在后面解析< beans/>根标签里面的< bean/>标签时,如果< bean/>标签没有设置某些属性,那么将使用这里初始化的属性,比如,如果内部的< bean/>标签没有设置lazy-init属性,那么默认使用外部< beans/>标签中的default-lazy-init属性。
/**
* DefaultBeanDefinitionDocumentReader的方法
* <p>
* 创建bean定义的解析器,解析<beans/>根节点元素的属性
*
* @param readerContext readerContext
* @param root 根节点元素
* @param parentDelegate 父解析器
* @return 新创建的delegate
*/
protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
//创建解析器
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
//设置解析器默认值,即解析<beans/>根标签的属性
delegate.initDefaults(root, parentDelegate);
//返回解析器
return delegate;
}
4.7.3.1.2.1.1 initDefaults解析< beans/>根标签属性
初始化解析器默认值,即解析< beans/>根标签节点元素的属性,支持继承父< beans/>根标签的属性,将解析结果存储到当前delegate对象内部的名为"defaults"的DocumentDefaultsDefinition属性中。
将会解析< beans/>根标签的default-lazy-init、default-merge、default-autowire、default-autowire-candidates、default-init-method、default-destroy-method等属性。
/**
* DefaultBeanDefinitionDocumentReader的方法
* <p>
* 创建bean定义的解析器,解析<beans/>根节点元素的属性
*
* @param readerContext readerContext
* @param root 根节点元素
* @param parentDelegate 父解析器
* @return 新创建的delegate
*/
protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
//创建解析器
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
//设置解析器默认值,即解析<beans/>根标签的属性
delegate.initDefaults(root, parentDelegate);
//返回解析器
return delegate;
}
/**
* BeanDefinitionParserDelegate的方法
* <p>
* 初始化默认的初始化<beans/>根标签的属性,支持继承父<beans/>根标签的属性
*/
public void initDefaults(Element root, @Nullable BeanDefinitionParserDelegate parent) {
//重要方法,初始化<beans/>根标签的属性,支持继承父<beans/>根标签的属性
populateDefaults(this.defaults, (parent != null ? parent.defaults : null), root);
//默认注册事件回调,默认eventListener为EmptyReaderEventListener,它的方法都是空实现,留给子类重写
this.readerContext.fireDefaultsRegistered(this.defaults);
}
//---------BeanDefinitionParserDelegate的相关属性-------
/*需要提前初始化的BeanDefinitionParserDelegate的一些属性,就是<beans/>标签的属性名常量*/
public static final String DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init";
public static final String DEFAULT_MERGE_ATTRIBUTE = "default-merge";
public static final String DEFAULT_AUTOWIRE_ATTRIBUTE = "default-autowire";
public static final String DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = "default-autowire-candidates";
public static final String DEFAULT_INIT_METHOD_ATTRIBUTE = "default-init-method";
public static final String DEFAULT_DESTROY_METHOD_ATTRIBUTE = "default-destroy-method";
/**
* autowire默认值常量
*/
public static final String AUTOWIRE_NO_VALUE = "no";
/**
* false值常量
*/
public static final String FALSE_VALUE = "false";
/**
* <beans/>根标签的默认值要保存的地方
*/
private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition();
/**
* BeanDefinitionParserDelegate的方法
* <p>
* 初始化<beans/>根标签的属性,支持继承父<beans/>根标签的属性
*
* @param defaults 当前<beans/>根标签的默认值要保存的地方
* @param parentDefaults 父<beans/>根标签的默认值
* @param root 当前<beans/>根标签元素
*/
protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {
//获取当前<beans/>根标签的default-lazy-init属性值lazyInit
String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);
//如果是默认值或者是""
if (isDefaultValue(lazyInit)) {
//如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为false
lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);
}
defaults.setLazyInit(lazyInit);
//获取当前<beans/>根标签的default-merge属性值merge
String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE);
//如果是默认值或者是""
if (isDefaultValue(merge)) {
//如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为false
merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE);
}
defaults.setMerge(merge);
//获取当前<beans/>根标签的default-autowire属性值autowire
String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE);
//如果是默认值或者是""
if (isDefaultValue(autowire)) {
//如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为no
autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE);
}
defaults.setAutowire(autowire);
//如果设置了default-autowire-candidates属性值(这个属性没有默认值)
if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) {
defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE));
}
//否则,如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为no
else if (parentDefaults != null) {
defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates());
}
//如果设置了default-init-method属性值(这个属性没有默认值)
if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) {
defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE));
}
//否则,如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为no
else if (parentDefaults != null) {
defaults.setInitMethod(parentDefaults.getInitMethod());
}
//如果设置了default-destroy-method属性值(这个属性没有默认值)
if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) {
defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE));
}
//否则,如果父<beans/>根标签不为null,那么设置为父<beans/>根标签的同名属性值,否则默认设置为no
else if (parentDefaults != null) {
defaults.setDestroyMethod(parentDefaults.getDestroyMethod());
}
//设置配置源source属性,即当前root元素节点对象
defaults.setSource(this.readerContext.extractSource(root));
}
/**
* 某些属性的默认值常量
*/
public static final String DEFAULT_VALUE = "default";
/**
1. 某个属性的值是否是默认值或者是""
*/
private boolean isDefaultValue(String value) {
return (DEFAULT_VALUE.equals(value) || "".equals(value));
}
4.7.3.1.2.2 parseBeanDefinitions解析< beans/>根标签下的bean定义
parseBeanDefinitions(root, this.delegate)用于从给定的< beans/>根标签元素中读取子标签并封装为为bean定义(BeanDefinition),这里又分两种情况:
- 对于Spring的默认命名空间http://www.springframework.org/schema/beans下的标签:< import/>、< alias/>、< bean/>、< beans/>,使用parseDefaultElement方法解析;
- 对于其他命名空间,比如http://www.springframework.org/schema/context、http://www.springframework.org/schema/aop等等下面的扩展标签:< mvc/>、< task/>、< context/>、< aop/>等,使用parseCustomElement方法解析。
/**
1. DefaultBeanDefinitionDocumentReader的方法
2. <p>
3. 从给定的<beans/>根标签元素中读取子标签并解析,注意<beans/>标签可以嵌套
4. 5. @param root 文档的<beans/>根标签元素
5. @param delegate 文档解析器
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
/*
* 如果属于默认命名空间下的标签 <import/>、<alias/>、<bean/>、<beans/>
*/
if (delegate.isDefaultNamespace(root)) {
//获取标签下的所有直接子节点
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//标签之间的空白换行符号/n也会算作一个Node节点 -> DeferredTextImpl,标签之间被注释的语句也会算作一个Node节点 -> DeferredCommentImpl
//这里需要筛选出真正需要被解析的标签元素节点,即Element -> DeferredElementNSImpl,因此XML中标签之间的注释在一定程度上也会增加遍历以及判断成本
if (node instanceof Element) {
Element ele = (Element) node;
//属于默认命名空间下的标签 <import/>、<alias/>、<bean/>、<beans/>
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
//其他扩展标签,mvc/>、<task/>、<context/>、<aop/>等。
else {
delegate.parseCustomElement(ele);
}
}
}
}
/*
* 其他命名空间的扩展标签,<mvc/>、<task/>、<context/>、<aop/>等。
*/
else {
delegate.parseCustomElement(root);
}
}
5 小结
本次我们学习了refresh()方法中的前两步:prepareRefresh()方法以及obtainFreshBeanFactory()方法的部分代码。
prepareRefresh()
方法- 用于初始化/刷新容器前的准备工作,设置Spring容器的启动日期和活动标志以及验证必须属性等工作。
- 它的一个扩展点是initPropertySources()方法,可以设置环境变量中的必要属性。
obtainFreshBeanFactory()
方法- 首先会判断如果此前存在BeanFactory,那么将会其中的销毁全部单例bean缓存,并且按照顺序:@PreDestroy注解标注的方法、DisposableBean接口的destroy方法、XML的destroy-method属性指定或者Spring自动推断的方法进行销毁方法的回调,并且对于同一个方法不会重复调用!随后会创建新的BeanFactory替代原来的BeanFactory
- 使用SAX解析加载全部XML配置文件,最终成为一个Document文档树。
- 解析继续解析DOM文档,本次我们讲了的< beans/>根标签的属性解析源码,以及< beans/>根标签下的子标签的大概解析逻辑:默认命名空间的默认标签使用parseDefaultElement方法解析,其他命名空间下的扩展标签使用parseCustomElement方法解析。
- XML文件中的每一个bean定义解析成为一个Java中的BeanDefinition对象,随后注册到注册表的相应的缓存中去。
XML配置文件加载、解析流程为:
从流程中我们可以看到,obtainFreshBeanFactory的流程还剩下< beans/>标签下的具体子标签的解析以及bean定义的注册没有讲解。
下篇文章我们将讲解obtainFreshBeanFactory流程的 剩下部分,即bean定义的具体加载流程,其中就有parseDefaultElement、parseCustomElement、registerBeanDefinition这三个核心方法,以及自定义标签扩展点。由于各种标签和源码很多,我们将选取重要的标签进行示例解析!
相关文章:
https://spring.io/
Spring 5.x 学习
Spring 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!