源代码计划: Spring 5.6 源码解析
该版本是以 Spring 5.6 为基础,从 bean 的创建流程、AOP 的核心对象创建、事务的创建加载整个 spring 框架的深度解析。
与 源代码计划: Spring 源码深度解析 倾向于以书本为线索的不同之处在于,该版本更多的是案例的演示与 debug 的整个流程。
前言
在之前的关于 spring 源码的另一篇 随笔 当中,以 <spring 源码深度解析> 一书为开篇,跟随章节内容研究过关于 spring 3.2 版本最原始的容器启动加载而原型。
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
@Test
public void testSimpleLoad(){
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
System.out.println(myTestBean.getTestStr());
}
}
在 5.6 版本当中笔者换另一种资源加载的方式进行启动,研究继承实现关系。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application-peppa.xml");
Student bean = application.getBean(Student.class);
System.out.println(bean.getName());
}
}
在前的 3.2 版本开篇曾提到过 spring 的核心功能有两个大类。 XmlBeanDefinitionReader 与 DefaultListableBeanFactory ,而中前者用于加载解析 XML 文件而后者用于创建注册 Bean 。那就看看,ClassPathXmlApplicationContext 具体是怎么实现 解析 与 注册 的呢?
从图中的 ClassPathXmlApplicationContext 基础关系类图中可以看到,AbstractApplicationContext 是一个绑定解析配置文件与容器实例化的实现类。
XML 配置文件的加载
经过对 ClassPathXmlApplicationCintext 构造方法的重载调用,直到 super(parent) 处对一系列父类的构造方法进行调用。直到 AbstartctApplicationContext 的构造方法,在一系列的父类构造的调用当中有大量的初始化变量。这些变量都是可以关注的。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 调用父类的构造方法,进行相关的对象创建的初始化等操作(对父类的一些构造方法不要忽略,会有一些额外的属性填充工作)
super(parent);
setConfigLocations(configLocations); // 设置应用程序上下文的资源配置路径 ${jdbc.url},${jdbc.username} 解析路径
if (refresh) {
refresh();
}
}
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() { // 用于解析当前系统的资源【xml,配置文件都属于资源】
this.resourcePatternResolver = getResourcePatternResolver(); // 创建资源模式处理器(refresh()方法就在此类当中)
}
protected ResourcePatternResolver getResourcePatternResolver() {
// 创建一个资源模式解析器 (其实就是用于解析 xml 配置文件)
return new PathMatchingResourcePatternResolver(this);
}
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) { // 在当前的 spring 项目当中,看不到【父子容器】的概念,如果进入到 spring-mvc 的时候就会出现父子容器
Environment parentEnvironment = parent.getEnvironment(); // 如果父容器不为空,获取父容器的环境对象
if (parentEnvironment instanceof ConfigurableEnvironment) { // 如果当前的环境对象是一个可配置的环境对象
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); // 进行相关的一个合并工作
}
}
}
这里的 AbstartApplicationContext 的构造方法当中的 setParent 方法用于设置父类的处理父类的容器,但是在当前的 Spring 项目当中是没有父类的容器的。因此这里 this.parent 也就为 null 。
在返回到 AbstarctXmlApplicationContext 类的属性初始化当中有一个 validating 变量,该变量的属性值为 true 。而该变量的主要作用是设置 xml 加载的配置文件内部的 验证模式 默认是 XSD。而关于 XML 文件的验证加载方式在之前的 源代码计划: Spring 源码深度解析 中同样也提到过,其中提到关于 手动指定 与 自动验证 。
private boolean validating = true; // 设置 xml 文件的验证标志, 默认是 true [验证配置文件加载的表示 xsd ?]
/**
* Initialize the bean definition reader used for loading the bean
* definitions of this context. Default implementation is empty.
* <p>Can be overridden in subclasses, e.g. for turning off XML validation
* or using a different XmlBeanDefinitionParser implementation.
* @param reader the bean definition reader used by this context
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
/**
* Set whether to use XML validation. Default is {@code true}.
* <p>This method switches namespace awareness on if validation is turned off,
* in order to still process schema namespaces properly in such a scenario.
* @see #setValidationMode
* @see #setNamespaceAware
*/
public void setValidating(boolean validating) {
this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
this.namespaceAware = !validating;
}
继续执行回到 AbstartctApplicationContext 的子类 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++) { // 这里可能有多个配置文件路径
// 思考【我们有没有在配置文件文件中写过 ${jdbc.url},${jdbc.username}】 既然名字是这样解析的,那么 xml 配置文件中的 是不是也是这样的呢 ? 【功能复用】
this.configLocations[i] = resolvePath(locations[i]).trim(); // 解析给定的路径【比如解析 spring-${username}.xml这方式】
}
}
else {
this.configLocations = null;
}
}
该方法的主要作用就是将我们通过 ClassPathXmlApplicationContext( "application-peppa.xml") 传入的 xml 多配置文件处理成一个 String 类的配置文件数组。
但其中还有另外一个细节是关于 resolvePath 方法的。这里的 resolvePath 方法的任务完成通配符进行解析,例如: 将 application-${user.name}.xml 解析成 application-peppa.xml 。
protected String resolvePath(String path) {
// getEnvironment() 第一个获取当前系统环境变量的值,为替换做准备
// resolveRequiredPlaceholders(path) 解析必要的占位符[${xxxx}],进行替换
return getEnvironment().resolveRequiredPlaceholders(path);
}
首先, 通配符中的变量属性都是来自于当前的环境,因此必定是先要通过 getEnviroment() 获取当前的运行环境,后面的 resolveRequiredPlaceholders(path) 方法进行具体的解析 。
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) { // 首次进入当前的环境为空
this.environment = createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
public AbstractEnvironment() {
// 在创建环境对象的时候,调用父类的构造方法。定制化属性资源,在父类当中是一个空的实现, 在调用的子类的时候具体实现。
customizePropertySources(this.propertySources);
}
最终在 getSystemProperties() 与 etSystemEnvironment() 方法当中获取系统的属性。因此在进行对象创建的时候,父类初始化的时候就已经完成对于环境的加载创建。
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); // 获取系统属性
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));// 获取系统环境
}
XML 配置文件的路径解析
回到具体的解析方法 resolveRequiredPlaceholdes 当中
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
// 占位符的助手 [前缀 ${ 后缀 } 分隔符 :]
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper); // 开始解析
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
获取对象属性当中的解析占位符的具体符号,通过 doResolvePlaceolders 方法完成具体的解析工作。
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix); // 获取占位符前缀 [${] 的下标位置
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex); // 查找占位符结束的下标位置
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); // 截取占位符中的属性
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // 递归调用截取第一个占位符包含的字符串可能会包含 os-${username}}
// Now obtain the value for the fully resolved key...
String propVal = placeholderResolver.resolvePlaceholder(placeholder); // 从系统中获取对应的解析到的属性
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); // 解析到占位符
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); //占位符替换
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
//删除集合当中的解析元素 spring-${username}.xml
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
关于该方法当中有几个关键的调用这里提一下:
-
findPlaceholderEndIndex () 该方法主要是找到结束的匹配索引下标位置
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + this.placeholderPrefix.length(); // 占位符开始的位置 + 前缀索引的长度 int withinNestedPlaceholder = 0; while (index < buf.length()) { if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { // 判断当前的下一个字符是否出现了后缀 [}] if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { // 如果没有出现则判断是否出现新的占位符 [{] ,该种情况主要用于判断是否出现类似于 [spring-${os-${username}}.xml] 占位符嵌套 withinNestedPlaceholder++; index = index + this.simplePrefix.length(); } else { index++; } } return -1; } -
parseStringValue() 这里有一个递归的调用,这主要用于处理 {user.name-{user.host}} 这种通配符中嵌套通配符的方式的。
-
placeholderResolver.resolvePlaceholder(placeholder); 从系统当中获取指定的属性值
而这里有进行了一次通配符解析的调用,主要用于处理,系统环境属性当中是否存在通配符的这种情况。
最终删除原有配置文件集合当中的为解析前的文件。
refresh 方法
完成了关于 XML 配置文件的路径加载后,开始进行核心的处理 refresh 方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh(); // 准备 Refresh 刷新 (前戏)
// Tell the subclass to refresh the internal bean factory.
// 创建容器对象: DefaultListableBeanFactory
// 加载 xml 配置文件的属性到当前工程当中, 最重要的就是 BeanDefinition 的属性填充
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory); //准备 BeanFactory Bean工厂 (前戏)
try {
// 子类覆盖方法做的处理,此处我们自己一般不做任何的工作,但是可以查到 web 中的代码,是由具体的实现的
// Allows post-processing of the bean factory in context subclasses. (子类扩展:后置增强处理器)
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context. (实例化并且执行已经注册的 BFPP beans )
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.(注册)
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource(); // 国际化
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
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();
}
}
}
refresh 当中一共有 12 个核心方法分别是:
-
prepareRefresh() // 完成刷新前的准备工作
-
obtainFreshBeanFactory(); //创建 DefaultListableBeanFactory 容器对象,加载 XML 配置文件到当前工具,完成 BeanDefinition 的属性填充 -
prepareBeanFactory(beanFactory); //准备 BeanFactory 工厂
-
postProcessBeanFactory(beanFactory); //后置增强处理器,子类具体实现
-
invokeBeanFactoryPostProcessors(beanFactory); //实例化以及注册的 BFPP
-
registerBeanPostProcessors(beanFactory); // 注册具体的 Bean
-
initMessageSource(); //国际化处理
-
initApplicationEventMulticaster(); //初始化时间监听器
-
onRefresh(); //初始化特殊的 Bean 空实现
-
registerListeners(); //注册监听器
-
finishBeanFactoryInitialization(beanFactory); // 实例化剩下的非懒加载对象
-
finishRefresh(); //完成整体的刷新
prepareRefresh 容器的刷新工作
protected void prepareRefresh() {
// Switch to active.
this.startupDate = System.currentTimeMillis(); // 设置容器启动的时间
this.closed.set(false); // 设置容器的关闭标志位
this.active.set(true); // 设置容器的激活标志位(活跃的原子标志位) 【 当前容器的关闭标志位为 false 激活标志位 true 表示该容器目前正在运行 】
if (logger.isDebugEnabled()) { // 日志记录
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// Initialize any placeholder property sources in the context environment.
// 空实现留给子类覆盖 【初始化资源属性】
// 如果这里我们自己扩展我们会怎么做 ? 继承父类 ClassPathXmlApplicationContext 重写 initPropertySources 方法, 比如对刚才解析当中获取的系统属性进行验证
initPropertySources();
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
// 之前已经在解析配置文件路径的时候已经创建并获取过系统环境属性,所以这里的 Environment 不为空
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
// 创建一些简单的集合类
// earlyApplicationListeners 应用程序监听器 earlyApplicationEvents 应用程序监听事件 这两个集合在初期是没有对象的
// 因此这里需要添加给分别创建对象
if (this.earlyApplicationListeners == null) { // 监听器集合
// 这里虽然初始状态监听器集合与监听事件集合都是为空的
// 但是在 SpringBoot 当中的 this.applicationListeners 默认的监听器集合不为空
// 因为在 SpringBoot 当中的 spring.factories 配置文件当中,有 15 个 listener 而这些 listener 会在加载的时候回提前读取加载到容器内
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
// 如果监听器集合不为空,则清空 监听器集合
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>(); // 监听事件集合
}
initPropertySources() 模式是空实现,留给子类进行扩展,可以通过自定义一个 ClassPathXmlApplicationContext 重写 initPropertySources() 方法。
getEnvironment().validateRequiredProperties() 验证必须的属性,这里的验证的是
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); // 创建一个异常类
// 如果在自定义的 MyClassPathXmlApplicationContext 当中已经设置过 getEnvironment().setRequiredProperties("peppa");
// 则 this.requiredProperties 的值不为 NULL 便会将属性添加到集合当中
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) { // 会从系统中寻找,如果没有找到则会添加一个异常
ex.addMissingRequiredProperty(key);
}
}
// 异常信息: "The following properties were declared as required but could not be resolved: " + getMissingRequiredProperties();
if (!ex.getMissingRequiredProperties().isEmpty()) { // 异常默认为 0 ,如果系统中在上一个 if 判断中不存在就会添加到集合当中,此时就会报错
throw ex;
}
}
可以看到这里的 this.requiredProperties 的属性值是没有的,那么这个属性是在什么时机设置的呢?
在 AbstractApplicationContext 当中,有一个 getEnvironment() 方法返回一个当前的成员属性 this.environment 而这里的 this.enveronment 指向
@Nullable
private ConfigurableEnvironment environment;
我们可以通过在自定义的 AbstractApplicationContext 当中调用 getEnveronment 方法获取该属性,并且通过该属性的
public void setRequiredProperties(String... requiredProperties) {
Collections.addAll(this.requiredProperties, requiredProperties);
}
方法完成对 this.requireProperties 的属性赋值。
@Override
protected void initPropertySources() {
System.out.println("调用扩展的 intiPropertySources 方法");
// 获取当前系统环境并添加 peppa 属性
getEnvironment().setRequiredProperties("user.name");
}
该方法 getEnvironment().validateRequiredProperties() 的具体作用,其实就通过代码段中的注释一样,设置的属性值会在加载后在当前的系统环境中进行查找,如果没有查找到则会抛出一个定义的异常信息。
接下来可以看到创建了一个监听器集合 eaelyApplicationLIstenes 而该监听器集合当中的内容为 NULL ,只是创建了一个监听器集合的对象。
但是在 springBoot 当中这里并不为 NULL 会有 15 个 listener 加载到该集合容器当中。
以上就完成了 容器前的所有刷新准备工作。
obtainFreshBeanFactory 创建容器对象
检查 FactoryBean 工厂并进行销毁验证
// Tell the subclass to refresh the internal bean factory.
// 创建容器对象: DefaultListableBeanFactory
// 加载 xml 配置文件的属性到当前工程当中, 最重要的就是 BeanDefinition 的属性填充
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 如果存在则【销毁 bean 工厂】 初始化 beanFactory 并进行 xml 文件读取,并将得到的 beanFactory 记录在当前实体的属性当中
// 为什么可能会有多余的 bean 工厂 ?
// 因为在刚刚启动的时候是以一个 ClassPathXmlApplicationContext 这样的方式启动 application
// 而在 web 项目当中我们是没有写过 new ClassPathXmlApplicationContext("xxx.xml") 这样的代码的
// 但是如果是一个 web 项目的时候,就会有一个 web 的工厂。我们是没有看到一个 main 方法的.我们是直接通过 TomCatServer 启动的
// spring 只是一个底子,上层有 spring-mvc spring-boot spring-cloud
// 刷新 bean 工厂 。思考【为什么要刷新 bean 工厂 ? 】
refreshBeanFactory();
return getBeanFactory();
}
refreshBeanFactory() 刷新 BeanFactory ,思考一下,这里初始化的时候是没有 BeanFactory 对象的那为什么要进行 Bean 工厂的刷新呢?
关于这一点,笔者是这样理解的, spring 是一个可进行扩展的框架,实际生产当中使用的 Spring MVC、Spring Security SpringBoot 、Spring Clould 都是以 spring 为基础的,虽然对于我们现在演示的 spring 基础的启动案例而言目前只有一个,但这并不代表在一些扩展上,也有且仅有一个工厂。
因此这里会有一个 refreshBeanFactory() 的方法调用,同时内部有一系列关于刷新的处理工作。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) { // 判断是否存在 beanFactory
destroyBeans(); // 销毁创建的 bean
closeBeanFactory(); // 关闭之前的 beanFacotry
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory(); // 创建 DefaultListableBeanFactory 工厂对象,该对象目前只是一个初始空值
beanFactory.setSerializationId(getId()); // 设置序列化 id (在调用最终的父类构造方法的时候分配一个默认的 id)
customizeBeanFactory(beanFactory); // 设置参数值 【自定义扩展可以修改 allowBeanDefinitionOverriding - allowCircularReferences】
// loadBeanDefinitions 这个方法当中涉及到非常复杂的重载方法调用,内容相当多
// 加载 bean 定义(读取 xml 配置文件中的信息)
// 这里将 工厂类 beanFactory 当作一个参数传入,最终要将 xml 配置文件当中的所有属性解析到 beanFactory 工厂类当中
// 因此就会涉及到很多的解析工作
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
-
hasBeanFactory() 检查当前的是否存在 spring 工厂,如果存在则进行销毁。那么什么情况下会出现多个 SpringFactory 对象呢?
举一个我们一直使用并且实际生产当中的一个例子,不知道读者们是否还记得,对于我们最原始的 Spring MVC 当时是怎么通过 XML 配置文件加载的呢?
在 MVC 的业务概念与架构中,我们将模块拆分成三层,也就是经常提到的
数据访问层(DAO),业务逻辑层(Service),控制器层(Controller),而对于每一层我们都是有一个单独的 XML 配置文件。并且在配置类对不同的 XML 分别进行引用,而在根加载目录AnnotationConfigApplicationContext,则会对 XML 配置分别进行解析加载。由父容器 AnnotationConfigApplicationContext 对其三个模块分别进行管理,同时通过refresh与close来完成子容器的创建和关闭。同时不单单只是只有在 MVC 当中存在多容器,我们在
Test模块或者通过多个不同的端口启动服务的时候,也会同时存在多个 SpringFacotory,另外在容器运行过程当中,如果通过动态的创建也是同样可以生成。protected final boolean hasBeanFactory() { return (this.beanFactory != null); }我滴上帝,感谢互联网,感谢人工智能,让俺找到了正确的思路。
-
destroyBeans() 销毁 Bean 工厂
关于 Bean 工厂的销毁,这里读者们可以想一下哈,如果是你要实现这段代码,你会怎么进行处理呢?
首先必定是获取当前的 BeanFactory 工厂对象咯,既然 BeanFacotry 是用来创造 Bean 对象的,是一个 Bean 的容器,那么就将该容器当中的所有缓存数据全部清除,并且对存放注册 bean 映射关系的集合清理就好咯,那么 spring 源码当中是这么实现的吗?
其实 spring 源码的实现与读者们的想法很有默契的不谋而合,。
- 第一步,获取当前的 SpringFacotry 对象
protected void destroyBeans() { getBeanFactory().destroySingletons(); }-
第二步,清楚单例缓存数据预映射关系 (注意哈,这里是单例哦,因为只有单例对象是归属与容器管理的哦 )
在这里我们关注几个有趣的点
this.singletonsCurrentlyInDestruction = true这里首先对当前要进行销毁的单例对象进行加锁,并且标记为true 销毁,而这个动作在后续关于getSingleton获取缓存对象的时候也会对该属性进行判断,同时也会有一个 sysnchronized 锁。this.containedBeanMap.clear();包含的 Bean 集合 [容器当中 name-bean]this.dependentBeanMap.clear();Bean 的依赖集合 [容器当中 Bean 当中依赖的 Bean ,比如 Server 层 依赖 Dto 层,Controller 层依赖 Server 层 ]this.dependenciesForBeanMap.clear();Bean 依赖项目的依赖项集合 [依赖项的依赖项,比如 Controller 依赖 Server 而 Server 依赖 Dto ]而这里只是清除了集合映射关系,而容器当中的 bean 缓存对象并未被清除。
public void destroySingletons() { if (logger.isTraceEnabled()) { logger.trace("Destroying singletons in " + this); } synchronized (this.singletonObjects) { this.singletonsCurrentlyInDestruction = true; } String[] disposableBeanNames; synchronized (this.disposableBeans) { disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet()); } for (int i = disposableBeanNames.length - 1; i >= 0; i--) { destroySingleton(disposableBeanNames[i]); } this.containedBeanMap.clear(); this.dependentBeanMap.clear(); this.dependenciesForBeanMap.clear(); clearSingletonCache(); }-
第三步,清楚缓存信息
clearSingletonCache();对容器的 一二三 级缓存以及已经注册的 bean 进行清理。protected void clearSingletonCache() { synchronized (this.singletonObjects) { this.singletonObjects.clear(); //一级缓存 this.singletonFactories.clear(); //三级缓存 this.earlySingletonObjects.clear(); //二级缓存 this.registeredSingletons.clear(); //已经注册的 Bean this.singletonsCurrentlyInDestruction = false; } }
开始创建 BeanFactory 工厂对象
DefaultListableBeanFactory beanFactory = createBeanFactory();
protected DefaultListableBeanFactory createBeanFactory() {
// 创建了一个在之前类图看到的 denfaultListableBeanFactory
// getInternalParentBeanFactory() 获取父类的构造方法
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
protected BeanFactory getInternalParentBeanFactory() {
return (getParent() instanceof ConfigurableApplicationContext ?
((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent());
}
public ApplicationContext getParent() {
return this.parent;
}
可以看到在 getInternalParentBeanFactory() 方法调用当中,获取的是父类的 Bean 工厂,很显然我们这里没有父子容器,就暂时没有父类的 Bean 工厂。
所以回到 DefaultLisableBeanFactory() 的构造方法当中。
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
在 该方法的 super() 方法当中调用了 AbstractAutowireCapableBeanFactory 的父类构造,创建了一个 AbstractBeanFactory 抽象工厂对象,同时在对父类调用的同时对内部的一些成员属性进行一系列的初始化,后续我们会展开对每一个属性进行解释。
这里还需要注意的一点是关于 ignoreDependencyInterface() 的忽略,也就是说实现 BeanNameAware ,BeanFactoryAware , BeanClassLoaderAware 的接口并不进行提前的注入。而 ignoreDependencyInterface() 方法就是忽略实现这些接口的对象,不自动的注入到容器当中,将会在接下来进行详细的解释,包括为什么要进行提前的忽略。
public AbstractAutowireCapableBeanFactory() {
super();
// 忽略要依赖的接口 (在后面统一的进行处理)
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
设置序列化 id,而这里而 id 在进行父类构造方法调用时,在创建 AbstractAppplicationContext 类的时候就已经进来初始化。
beanFactory.setSerializationId(getId());
private String id = ObjectUtils.identityToString(this); // 启动容器的时候分配的唯一 id 值,在之前的 creatBeanFactory 的时候有一个 序列化 id 该就是唯一的启动启动 id
定制 beanFacotry,如何在 xml 配置文件当中设置了 lookup-method 或者 replaced-method 属性时,此时便会调用内部的这两个方法。
customizeBeanFactory(beanFactory); // 设置参数值 【自定义扩展可以修改 allowBeanDefinitionOverriding - allowCircularReferences】
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 在设置 xml 文件当中的标签时 lookup-method / replaced-method
// 如果属性 allowBeanDefinitionOverriding 不为空, 设置给 beanFactory 对象属性相应属性,是否允许覆盖同名称的不同定义的对象
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 如果属性 allowCircularReferences 不为空, 设置 beanFactory 对象响应属性, 是否允许 bean 之间循环依赖
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
同样这两个属性,也是可以通过在启动类的 beanFactroy 当中进行设置的。
public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
}
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
super.setAllowBeanDefinitionOverriding(false);
super.setAllowCircularReferences(false);
super.customizeBeanFactory(beanFactory);
super.addBeanFactoryPostProcessor(new MyBeanPostProcessor());
}
loadBeanDefinitions 解析 xml 配置文件信息到 beanFactory 当中
该是 refreshBeanFactory 刷新 bean 工厂当中的核心重要方法,主要的任务就是将 xml 配置文件当中的所有 bean 信息解析到当前的 beanFactory 当中。而这其中会涉及到 Xml 的配置文件解析的详细步骤。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 这里将 beanFactory 对象,变成了一个 XmlBeanDefinitionReader 的对象 思考【涉及到那个涉及模式】- 适配器模式
// 虽然在后面的使用当中使用的是 XmlBeanDefinitionReader 但实际内部还是在使用 beanFactory
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment()); // 设置环境对象 【方便后续调用系统属性值】
beanDefinitionReader.setResourceLoader(this); // 设置资源加载器 【加载 xml 资源】
// 设置了一个实现 EntityResolver 接口的类, 用于读取本地 xsd 或者 dtd 文件,用于解析。
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // ResourceEntityResolver 资源本地解析器
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
//前面几部已经获取了环境属性,设置 xml 加载解析方式的格式 xsd & dtd 的解析路径,初始化好了资源解析器, 接下来就是加载 xml 配置文件路径并完成解析工作
loadBeanDefinitions(beanDefinitionReader); // 重载调用
}
解析前期的准备工作
-
首先将之前的
beanFactory对象包装成了XmlBeanDefinitionReader -
设置了当前的系统环境
-
设置当前的
resourceLoader资源加载器 -
设置当前的用于处理 xsd 与 dtd 格式文件的处理类
public ResourceEntityResolver(ResourceLoader resourceLoader) { super(resourceLoader.getClassLoader()); this.resourceLoader = resourceLoader; }public DelegatingEntityResolver(@Nullable ClassLoader classLoader) { this.dtdResolver = new BeansDtdResolver(); // 创建了一个 dtd 解析器 this.schemaResolver = new PluggableSchemaResolver(classLoader); // 接触性 xsd 对象【注意:getSchemaMappings 的值读取时机】 } -
initBeanDefinitionReader设置默认解析方式 xsd or dtd,而默认是 xsd 加载initBeanDefinitionReader(beanDefinitionReader);protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) { reader.setValidating(this.validating); }public void setValidating(boolean validating) { this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE); this.namespaceAware = !validating; }private boolean validating = true; // 设置 xml 文件的验证标志, 默认是 true [验证配置文件加载的表示 xsd ?]/** * Indicates that the validation mode should be auto-guessed, since we cannot find * a clear indication (probably choked on some special characters, or the like). */ public static final int VALIDATION_AUTO = 1; /** * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration). */ public static final int VALIDATION_DTD = 2; /** * Indicates that XSD validation should be used (found no "DOCTYPE" declaration). */ public static final int VALIDATION_XSD = 3;-
loadBeanDefinitions之前的几步,相当于是对解析前的一系列的初始化准备工作,而从现在开始将进行对 xml 配置文件的真正解析工作。这里会读取我们之前在启动类添加的配置文件路径,而该路径在进行初始化调用的时候,就已经完成了对于通配符的解析,并赋值给赖当前的
this.configLocations对象。因此当前的configLocations不为 null.
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // 执行 configResources or configLocations 取决于ClassPathXMLApplicationContext 启动类当中传入的参数具体是 String or Resource 类型 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { // 调用父类的构造方法,进行相关的对象创建的初始化等操作(对父类的一些构造方法不要忽略,会有一些额外的属性填充工作) super(parent); setConfigLocations(configLocations); // 设置应用程序上下文的资源配置路径 ${jdbc.url},${jdbc.username} 解析路径 if (refresh) { refresh(); } }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++) { // 这里可能有多个配置文件路径 // 思考【我们有没有在配置文件文件中写过 ${jdbc.url},${jdbc.username}】 既然名字是这样解析的,那么 xml 配置文件中的 是不是也是这样的呢 ? 【功能复用】 this.configLocations[i] = resolvePath(locations[i]).trim(); // 解析给定的路径【比如解析 spring-${username}.xml这方式】 } } else { this.configLocations = null; } } -
loadBeanDefinition 的嵌套调用
从这里开始,会出现层层的嵌套调用,直到 doLoadBeanDefinitions
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 调用 DefaultResourceLoader 的 getResource 完成具体的 Resource 定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
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);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
-
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); -
public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); } -
@Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) 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))) { // a file pattern return findPathMatchingResources(locationPattern); } else { // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }当获取到当前配置资源的 Resource 对象后,开始调用
loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
对当前的 xml 配置文件资源进行编码格式重新调整创建一个新的 EncodedResource 对象
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
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);
}
// 通过属性来记录已经加载的资源
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());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
将当前加载的资源,记录到集合当前
// 通过属性来记录已经加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
通过文件流读取 resource 目录下的 xml 配置文件资源
InputStream inputStream = encodedResource.getResource().getInputStream()
开始真正的解析处理
doLoadBeanDefinitions(inputSource, encodedResource.getResource());
doLoadBeanDefinitions 开始解析
将加载的 xml 配置文件的 inputSource 流转化为 Document 文件对象,关于 Document 的构建,对于所有的 XML 都是通用。
//此处获取 xml 文件的 document 对象,这个解析过程是由 documentLoader 完成的
Document doc = doLoadDocument(inputSource, resource);
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 至于这里是如何将 XML 文件解析构建成一个 Document 对象的这里就不深究了,想看也可以吃自己去查找一下。
return builder.parse(inputSource);
}
设置解析的格式
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
registerBeanDefinitions(doc, resource); 这是这个关于 doLoadBeanDefinition 解析当中的重点,而该方法的主要作用就是将 Documnet 对象当中的元素进行逐个处理,完成对 xml 当中的 bean 的注册。