阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。
引子
系列1 - bean 标签解析:
4、xml配置文件解析之【默认】命名空间【标签】的解析.md
5、xml配置文件解析之【自定义】命名空间【标签】的解析.md
系列2 - bean 获取: getBean() 做了什么
一句话概括:
【只读取配置内容,并注册管理】
书接上回,上文终止余如下图所示的位置:
既然要真刀真枪的将 xml 文件的标签解析了,那么我们不能继续无实物表演了,下边的例子是从spring 5.x 源码里随机找打的一个 xml 案例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans default-lazy-init="true" default-autowire="constructor" default-merge="true"
default-init-method="myInit" default-destroy-method="myDestroy">
<import resource="beanEventsImported.xml"/>
<alias name="testBean" alias="testBeanAlias1"/>
<alias name="testBean" alias="testBeanAlias2"/>
<bean id="testBean" class="org.springframework.tests.sample.beans.TestBean">
<constructor-arg type="java.lang.String" value="Rob Harrop"/>
<property name="friends">
<ref bean="testBean2"/>
</property>
<property name="doctor">
<bean class="org.springframework.tests.sample.beans.NestedTestBean">
<constructor-arg type="java.lang.String" value="ACME"/>
</bean>
</property>
</bean>
<bean id="testBean2" class="org.springframework.tests.sample.beans.TestBean">
<property name="name" value="Juergen Hoeller"/>
<property name="spouse">
<bean class="org.springframework.tests.sample.beans.TestBean">
<property name="name" value="Eva Schallmeiner"/>
</bean>
</property>
</bean>
</beans>
上述作为案例的:beanEvents.xml 文件中包含了,我们在 "默认命名空间" 下所关注的四种标签都有,用来做本文的案例简直再完美不过了。
先来个简单的介绍:
beans:
alias:
bean:
import:
从给出的xml文件案例里也能看出,import就是导入了一个外部定义的 "bean.xml"。
对它的解析会变成对这个外部引入的 "bean.xml" 的递归解析。
接下来进入 parseDefaultElement 方法内部,见下图:
方法代码的4个分支,代表的就是 4 中默认命名空间标签的解析,这里重点关注的只有最重要的: bean 标签。
这里用红色裱起来了,就说明又到了:关键的代码简单,事情不简单环节了。
4.1 标记的第一行:
委托 BeanDefinitionDelegate 类对象,解析 bean 标签的上的属性。
返回值类型:BeanDefinitionHolder 顾名思义,可以把它看作一个 bean 标签配置的相关属性的容器,
至于是哪些属性,后续展开。
4.2 标记第二行:
<bean id="test" class="test.xxx.TestXxx">
<mybean:user userage="22" />
</bean>
4.3 标记第三行
4.4 标记第四行
本章前边提到的四行代码会对应后续的: 【第5 ~ 第8 章】
如果后边忘记了,可以回过头来看看。
本节对应的是 4.1 所标注的那一行代码
这里实际上还没进入正题:
下边说回正题:
上图中,依稀可见这么一行代码:
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
这里创建的 BeanDefinition 对象,就是用来承载,我们bean的配置内容解析结果的。
就像你配置的 xml对象,你自己能直接读懂,但是要让 spring 容器去读它时,spring容器会将这个 xml 文本内容进行翻译, 而bd [BeanDefinition] 承载的就是翻译结果。
顺着createBeanDefinition() 方法进去,最终会发现 bd的类型固定是: GenericBeanDefinition,下边是它的类图:
我们还记得 XmlBeanFactory 有个重要的接口:
PS 而到目前为止,我们的所有篇幅都在介绍 这个翻译过程, 且还没介绍完。
至于为什么敢说它承载了 xml的翻译结果,请看下图所示的 AbstractBeanDefinition 类的所有成员变量,请问是不是很眼熟呢?
拓展讲点东西,AbstractBeanDefinition 实际上有三个子类:
已知 xml 中可以定义: 父/子 bean的关系
瞅瞅:父子bean的例子
被标注的代码
// 硬编码解析默认的bean属性 所有元素 "属性" 解析
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
详情:
/**
* Apply the attributes of the given bean element to the given bean * definition.
* @param ele bean declaration element
* @param beanName bean name
* @param containingBean containing bean definition
* @return a bean definition initialized according to the bean element attributes
*/
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
// 是否单例
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
// 作用范围 public class ?
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
// 是否抽象abstract
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
// 懒加载 属性 延迟加载
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
// 是否 autowrie 自动装配
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
// depends-on 依赖 属性 依赖检查 spring 3.0 以后弃用
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// 自动装配条件、前提 属性 值为 false 时,该bean不会被作为其他bean自动自动装配的候选者,
// 但是它自身自动装配时,可以使用别的bean作为它自己的候选者
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if (isDefaultValue(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// primary 属性 (初级、初始)
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
// 初始化方法 属性。。。
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
}
else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
// 注销、关闭方法 属性
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { // factory-method 属性
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { // factory-bean 属性
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
我们重新把那张图请回来:
看原谅色的注解
1)、 meta:
元数据, 看图中案例,就是些键值对形式配置的数据;
元数据并非 bean 配置的 class 的属性,它是一个额外的声明;
需要使用时,通过 BeanDefinition 的 getAttribute(key) 方法获取。
2)、 lookup-method:
这个属性用得实际是使用比较少,看下边的案例:
id="lookUpTest" 的bean配置的 class是抽象类:LookUpTest.java(理论上来说抽象类是不能被实例化的)。
但是按下边的方法配置后,你会发现:
当你通过容器调用如下代码:并不会报错:
LookUpTest obj = (LookUpTest) new XmlBeanFactory("lookUpTest.xml").getBean("lookUpTest")
obj.execute();
这里execute() 内部的抽象方法 getUser() 会获取到xml 中配置的id="userBean"的 java.User对象
如果你了解设计模式,就知道这里的妙用了
3)、 replaced-method:
上一节提到的 lookup-method 动态替换抽象方法返回的bean;
而replaced-method 动态替换 已经实现的方法 逻辑;
看看上图的案例:
它会把 executeBean 的 doSomething() 方法的逻辑替换为:doSomethingReplacer中重写的方法逻辑
但是它有个要求:被用来替换的,doSomethingReplacer所属的类必须实现如下接口
4)、 constructor:
property:
图一出,就不用介绍干啥用的了吧?
qualifier:
至于它,就更简单了,可以认为它是,我们在给bean注入属性的时候,指定过滤条件
@Qualifier("integerRepo")
private Repository<?> integerRepositoryQualifierProvider;
qualifier,甚至还支持通过属性进行过滤:
如下所示 的是 qualifier 的三种配置方式:
<beans>
<!-- 只通过BeanName=foo222 过滤 -->
<bean id="foo" class="java.lang.String">
<qualifier value="foo222" />
</bean>
<!-- Bean名称 + Bean类型过滤,常见于多态场景下 -->
<bean class="org.springframework.beans.factory.xml.QualifierAnnotationTests$Person">
<qualifier type="QualifierAnnotationTests.SimpleValueQualifier" value="curly"/>
</bean>
<!-- Bean类型 + Bean属性过滤 UserBean.name="moe" && UserBean.age="15" -->
<bean class="org.springframework.beans.factory.xml.QualifierAnnotationTests$Person">
<property name="name" value="Moe Jr."/>
<qualifier type="QualifierAnnotationTests.MultipleAttributeQualifier">
<attribute key="name" value="moe"/>
<attribute key="age" value="15"/>
</qualifier>
</bean>
</beans>
下图是 qualifier 的一般使用场景:
上边说完了他们的用法,下边我们说说他们是怎么被,spring 从xml配置文件中识别出来的:
meta:
如图所示,就是通过 key - value 获取 meta标签配置的键值对,
依赖 [key, value] 生成了 BeanMetadataAttribute 实例,最后注入了BeanDefinition中
<?xml version="1.0" encoding="UTF-8"?>
<bean class="org.xxx.xml.QualifierAnnotationTests$Person">
<meta key="name" value="moe"/>
<meta key="age" value="42"/>
</bean>
lookup-method:
解析的代码跟meta的解析大同小异。
解析的结束动作,可以视为:就是简单的拿到 lookup-method 标签注入的:抽象方法名称、bean名称
然后通过,MethodOverrides 属性间接注入的 BeanDefinition 中
replaced-method:
constructor:
构造函数参数解析,逻辑稍微长了一丢丢,这里主要是因为,构造函数参数可以设置顺序。
如果设置了顺序[index],那么需要校验 数字是否合规,数字是否重复等等问题,最终将解析的结果注入到了 BeanDefinition 中。
property:
跟上边的解析过程大同小异
qualifier:
到此,bean 标签下的,默认标签、元素的解析完成了
下图所示的是:第四章所述的第二段代码,bean 标签下的自定义标签的解析。
下一篇文章将细讲,这里只做简单的介绍。
浅浅的说一下下图的行为:
1 根据标签识别其所属的命名空间
2 跳过 spring 默认命名空间下的标签
3 根据命名空间获取,该非默认命名空间标签的,处理器 【NamespaceHandler】
你或许会好奇,怎么突然蹦出个:NamespaceHandler啊,前文并没有任何地方提到它啊?
实际上,NamespaceHandler,由自定义标签的人提供,如果我们自定义了自己的标签
那么我们需要在 spring 解析配置前,去容器中注入我们自己开发的:NamespaceHandler
4 解析非默认命名空间标签 【下一篇文章细说,非默认命名空间 - 标签,的解析】
将前文解析到的 BeanDefinition 注册到,容器中。
经过前文介绍可知, XmlBeanFactory 继承了一个接口,没错,就是下图所述的接口:
这个接口负责对 BeanDefinition 信息的管理。
继续跟踪上图中的
下边代码中最核心的一句代码:
this.beanDefinitionMap.put(beanName, beanDefinition);
这就是说:BeanDefinitionRegistry 通过 Map 以键值对形式管理beanDefinition 的直接证据。
// 通过 beanName 注册/记录
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 空校验
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 是否是AbstractBeanDefinition 子类
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
// 注册前最后一次校验,不同于xml校验
// 它是对 类定义的(AbstractBeanDefinition) methodOverrides属性的校验
// 校验其是否于工厂方法并存、 是否存在(??)
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 检查是否已经注册
if (existingDefinition != null) {
// 已注册
if (!isAllowBeanDefinitionOverriding()) {// 不允许覆盖,抛出异常
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + existingDefinition + "] bound.");
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {// bean 的应用(??范围??) 变化
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isWarnEnabled()) {
logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {// 关键信息变化,不能视作同一个bean ??
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 类定义BeanDefinition 维护到线程安全的 Map 中
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
// 还未注册过
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition); // 维护map
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
再看事件监听器,没错它是从 XmlBeanDefinitionReader 中注入的,你看世界又闭环了。
【PS * 到了这里,你可能已经忘了 XmlBeanDefinitionReader是谁了,不急我来给你重新介绍下:】
你看下图的setter 方法,这里明显是支持注入自定义监听器的,没有自定义则直接注入默认的监听器。
到此,本文终于结束了,spring 默认命名空间下的标签已经解析结束了。
至此,BeanDefinitionRegistry 中已经注册好了 BeanDefinition 的信息了;后续,在 getBean() 的流程中, BeanDefinitionRegistry 中注册的 BeanDefinition 将再次粉墨登场。
<如果你不关注:第三方定义的命名空间怎么解析;你可以跳过下一篇文章,直接看getBean 到底干了啥了>
回顾下,本文提到了spring 的4个标签,我们用了绝大部分的篇幅在讲, bean 标签及其子标签、属性的解析,是因为另外的三个标签不重要吗?
————————
beans: 它的实质就是递归调用 bean 标签的解析过程
alias: 为bean注册别名,方便同一个bean可以通过不同的beanName 来引用,代码也简单就不展开了
bean: 老熟人了,不解释
import: 下图就是 import 标签的解析,看看标注的那行代码?是不是一眼顶针? 小黑子在每个地方都会非常的显眼。
至于 XmlBeanFactory 不需要介绍了吧?再问紫纱。
————————
如下截图里从左到右的几个类,也是XmlBeanFactory 解析的大致流程。
这里看第三个类的名字? 直译过来,这不是我们一直反复念叨的:
既然有了默认 标签解析类,那会不会存在一个:自定义标签解析器类呢? 它会不会也跟 DefaultBeanDefinitionDocumentReader 一样,实现了: BeanDefinitionDocumentReader 接口呢?
亦或者自定义标签的解析 也借助: DefaultBeanDefinitionDocumentReader 来完成,但是解析,为了解析自定义标签,我们会不会对: DefaultBeanDefinitionDocumentReader 上,做点别的配置呢?
带着上述两个猜想,我们进入下一章节的旅行,在该章节中,我们将亲自解开上述问题的答案。