spring version : 4.3.x
上一篇中梳理了 bean 加载的整体过程,但是对于过程中涉及到的具体细节没有进行深入探究,我们顺着调用链一层层往下走,最终找到了位于 DefaultBeanDefinitionDocumentReader 类中真正解析配置的方法:parseBeanDefinitions(root, delegate),本篇我们将从这里开始继续向下探索,去追寻解析的具体过程。
Spring 为开发者提供了许多配置用的标签,比如 beans、bean、import、alias等,这些标签统称为 默认标签(个人觉得翻译成内置标签更加合理),同时 Spring 还支持开发者自己定义标签,parseBeanDefinitions 方法中的逻辑就是判断当前标签是默认标签还是自定义标签,并调用相应的方法对标签进行解析(源码如下),本篇我们主要探索默认标签的解析过程,对于自定义标签则留到下一篇进行讲解。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
// 解析默认标签(beans标签)
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 解析默认标签(子级嵌套)
this.parseDefaultElement(ele, delegate);
} else {
// 解析自定义标签(子级嵌套)
delegate.parseCustomElement(ele);
}
}
}
} else {
// 解析自定义标签
delegate.parseCustomElement(root);
}
}
本文,我们主要关注 parseDefaultElement(ele, delegate) 方法,该方法用于对默认标签进行解析,整个方法的逻辑很清晰,判断当前的标签类型,然后调用对应的解析器去做解析处理:
private void parseDefaultElement(Element element, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(element, IMPORT_ELEMENT)) {
// 处理import标签,import用于引入其他的xml配置文件
this.importBeanDefinitionResource(element);
} else if (delegate.nodeNameEquals(element, ALIAS_ELEMENT)) {
// 处理alias标签
this.processAliasRegistration(element);
} else if (delegate.nodeNameEquals(element, BEAN_ELEMENT)) {
// 处理bean标签
this.processBeanDefinition(element, delegate);
} else if (delegate.nodeNameEquals(element, NESTED_BEANS_ELEMENT)) {
// 处理beans标签,即在<beans/>中再嵌套<beans/>
this.doRegisterBeanDefinitions(element);
}
}
方法按照标签类型分而治之,下面逐个来探究各个标签的解析过程。
一. bean 标签的解析过程
<bean/> 标签是配置中使用最多的标签,所以我们首先从这里开挖,该标签对应的处理方法是 processBeanDefinition(element, delegate),先来纵观一下整个方法的逻辑:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 1. 解析bean元素:id, name, alias, class
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 2. 如果默认标签下有自定义标签,则进行解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 3. 注册解析得到的 BeanDefinitionHolder
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);
}
/*
* 4. 发出响应事件,通知相关监听器,这个bean已经加载完了
*
* 这里的实现只是为了扩展
* spring自己并没有对注册实现做任何逻辑处理
*/
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
方法所描述的过程可以分成四步进行:
- 调用 BeanDefinitionParserDelegate 解析各个默认标签元素,将配置语义转换成 BeanDefinition 对象,并包含在 BeanDefinitionHolder 中返回。
- 如果步骤 1 中的返回结果不为空,则检查当前标签下是否有自定义标签元素,若存在的话则进行解析。
- 注册由前两步得到的 BeanDefinitionHolder 对象。
- 发布事件消息,通知相应的监听者默认标签已经解析完毕的事件。
1.1 默认标签元素解析
默认标签元素的解析位于 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement(Element ele) 方法中,源码如下:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return this.parseBeanDefinitionElement(ele, null);
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 获取id
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取name
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
// 如果配置了多个name(以逗号、分号,或者空格分隔),则解析成list
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
// 没有配置id属性,但配置了至少一个name,则以第一个name作为id
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
// 检查name和alias的在容器中的唯一性
this.checkNameUniqueness(beanName, aliases, ele);
}
// 解析标签
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) { // 木有beanName
try {
// 如果不存在beanName,那么按照命名规则自动生成一个
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// 注册一个alias
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]");
}
} catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 封装获取到的信息到BeanDefinitionHolder返回
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
上面的解析过程概括如下:
- 获取 id 和 name 属性,并检查属性值在整个配置中的唯一性。
- 解析其它属性元素,封装成 GenericBeanDefinition 对象。
- Spring 会以 id 或者第一个 name 值作为 bean 的唯一标识,如果发现没有设置对应的 beanName,则会按照命名规则自动生成一个。
- 将解析得到的 beanDefinition 实例、beanName、以及 alias 列表封装到 BeanDefinitionHolder 中返回。
整个过程中最核心的步骤是第 2 步,在这里你将会看到所有 bean 属性的解析过程,先上源码:
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {
// 入栈,异常时用于打印异常链
this.parseState.push(new BeanEntry(beanName));
// 获取class属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
// 获取parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
// 创建BeanDefinition对象,采用GenericBeanDefinition实现类
AbstractBeanDefinition beanDefinition = this.createBeanDefinition(className, parent);
// 解析默认bean的各种属性元素
this.parseBeanDefinitionAttributes(ele, beanName, containingBean, beanDefinition);
beanDefinition.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析元数据子标签 <meta key="" value=""/>
this.parseMetaElements(ele, beanDefinition);
// 解析lookup-method子标签
this.parseLookupOverrideSubElements(ele, beanDefinition.getMethodOverrides());
// 解析replaced-method子标签
this.parseReplacedMethodSubElements(ele, beanDefinition.getMethodOverrides());
// 解析构造函数参数
this.parseConstructorArgElements(ele, beanDefinition);
// 解析property子标签
this.parsePropertyElements(ele, beanDefinition);
// 解析qualifier子标签
this.parseQualifierElements(ele, beanDefinition);
beanDefinition.setResource(this.readerContext.getResource());
beanDefinition.setSource(this.extractSource(ele));
return beanDefinition;
} catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
} catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
} catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
} finally {
this.parseState.pop();
}
return null;
}
上面的过程会先获取配置的 class 和 parent 属性值,然后基于这两个值来创建最初的 BeanDefinition 对象:
protected AbstractBeanDefinition createBeanDefinition(String className, String parentName) throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader());
}
本质上调用的是工具类 BeanDefinitionReaderUtils 的 createBeanDefinition 方法:
public static AbstractBeanDefinition createBeanDefinition(
String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName); // parentName不一定存在
if (className != null) {
if (classLoader != null) {
// 如果classLoader不为空,则用此classLoader进行加载
bd.setBeanClass(ClassUtils.forName(className, classLoader));
} else {
// 如果classLoader为空,则仅记录class的全称类名
bd.setBeanClassName(className);
}
}
return bd;
}
由上述源码可以看到,这里创建的 BeanDefinition 对象是 GenericBeanDefinition 类型,并设置对象的 parentName 属性,如果有传入外围类的类加载器,则基于同一 JVM 的类加载器获取 class 引用,否则记录 bean 的 className,后续再进行实例化。
1.1.1 解析 bean 的所有属性元素
接下来 Spring 会去解析 <bean/> 标签中的各种属性,并依据这些属性值配置去设置 GenericBeanDefinition 对象的相应域,这个过程位于方法 parseBeanDefinitionAttributes 中:
public AbstractBeanDefinition parseBeanDefinitionAttributes(
Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition beanDefinition) {
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton标签已经过时,使用scope替代
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
} else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // 解析scope属性
beanDefinition.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
} else if (containingBean != null) {
// 当前bean没有指定scope,如果传入了containingBean,则继承其scope
beanDefinition.setScope(containingBean.getScope());
}
// 解析abstract属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
beanDefinition.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
// 解析lazy-init属性(false、true、default)
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) { // default,继承 default-lazy-init
lazyInit = this.defaults.getLazyInit();
}
// 如果没有设置或设置成为其他的值都会被设置为false
beanDefinition.setLazyInit(TRUE_VALUE.equals(lazyInit));
// 解析autowire属性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
beanDefinition.setAutowireMode(this.getAutowireMode(autowire));
// 解析dependency-check属性(3.0 版本之后已经不再推荐使用该属性)
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
beanDefinition.setDependencyCheck(this.getDependencyCheck(dependencyCheck));
// 解析depends-on属性
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
// 多个depends以逗号,分号,或空格分隔
beanDefinition.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// 解析autowire-candidate属性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
} else {
beanDefinition.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// 解析primary属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
beanDefinition.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
// 解析init-method属性
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
if (!"".equals(initMethodName)) {
beanDefinition.setInitMethodName(initMethodName);
}
} else {
if (this.defaults.getInitMethod() != null) {
beanDefinition.setInitMethodName(this.defaults.getInitMethod());
beanDefinition.setEnforceInitMethod(false);
}
}
// 解析destroy-method属性
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
beanDefinition.setDestroyMethodName(destroyMethodName);
} else {
if (this.defaults.getDestroyMethod() != null) {
beanDefinition.setDestroyMethodName(this.defaults.getDestroyMethod());
beanDefinition.setEnforceDestroyMethod(false);
}
}
// 解析factory-method属性
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
beanDefinition.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
// 解析factory-bean属性
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
beanDefinition.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return beanDefinition;
}
整个方法解析了 <bean/> 所有的属性,包括常用和不常用的,虽然方法很长,但是都是在做一件事情:判断是否配置了对应的属性,如果配置了则进行解析并设置 beanDefinition 对象中的对应值,对于部分具备继承性质的属性,如果没有配置则沿用上层配置的值,下面选择几个不是特别常用的标签来举例说明一下其用意。
- abstract
abstract 属性与 parent 属性组合使用,让配置具备了继承性,如果多个 bean 在配置上存在大量的重复,这个时候就可以考虑使用继承的配置,抽象出重复的属性配置在父 bean 中,而子 bean 则配置特有的属性,如下:
<bean name="myAbstractBean" class="org.zhenchao.bean.MyInheritBean" abstract="true" p:name="y450" p:price="4999"/>
<bean name="whitePc" parent="myAbstractBean" p:color="white"/>
<bean name="blackPc" parent="myAbstractBean" p:color="black"/>
通过 abstract="true"
将父 bean 置为抽象,然后在子 bean 中利用 parent 进行引用,这样相同的属性只需要配置一份即可。
- autowire-candidate
在自动装配时,有时候往往存在多个候选的自动装配对象,Spring 在无法确定正确的装配候选者时就会抛出 UnsatisfiedDependencyException 异常,这个时候我们可以将某个或多个候选者配置为 autowire-candidate=false
,从而剥夺其候选者的身份。
- primary
primary 的功能类似于 autowire-candidate,后者是剥夺某个 bean 的自动装配候选者身份,但是如果存在多个候选者时,一个个的配置会比较麻烦,这个时候我们可以使用 primary 属性将某个候选 bean 设置为 primary=true
,这个时候就让该 bean 在候选时具备了最高优先级。
1.1.2 解析子标签元素
- 解析 meta 标签
meta 标签使用的不多,这个配置最终会记录到封装 bean 的 BeanDefinition 实例中,假设有配置如下:
<bean id="my-bean" class="org.zhenchao.bean.MyBean">
<meta key="my-bean-meta" value="meta-element"/>
</bean>
那么我们可以通过如下方式从 bean 对应的 BeanDefinition 实例中获取到对应的 meta 值:
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("my-bean");
Assert.assertEquals("meta-element", beanDefinition.getAttribute("my-bean-meta"));
通过源码我们可以清晰看到 meta 配置解析和记录过程,解析的过程位于方法 parseMetaElements(ele, beanDefinition) 中:
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
// 获取当前节点的所有子标签
NodeList nodes = ele.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// 提取meta标签:<meta />
if (this.isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
// 使用key, value构造BeanMetadataAttribute
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(this.extractSource(metaElement));
// 记录信息
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
由上述方法我们可以看到,容器是将 meta 配置值利用 BeanMetadataAttribute 封装成一个对象,并记录到 BeanMetadataAttributeAccessor 实例的 attributes 中,这是一个 map 型的数据结构,而我们之所以可以从 BeanDefinition 实例中拿到配置值,是因为 BeanDefinition 实现了 BeanMetadataAttributeAccessor 所实现的接口 AttributeAccessor。
- 解析 lookup-method 标签
lookup-method 标签一般用于希望在一个 singleton 对象中获取一个 prototype 对象的场景,假如我们在一个 singleton 对象中寄希望每次调用 get 方法时获取一个 prototype 类型的新对象,因为外围 bean 是 singleton 的,其属性也都只有一份,所以不可能每次都返回 prototype 类型属性的新对象,这个时候我们就可以使用 lookup-method 标签:
public class MyBean {
private MyPrototypeBean prototypeBean;
public MyPrototypeBean getPrototypeBean() {
return this.prototypeBean;
}
public MyBean setPrototypeBean(MyPrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
return this;
}
}
MyBean 为 singleton 类型,虽然 MyPrototypeBean 是 prototype 的,但是我们每次调用 getPrototypeBean() 仍然是返回同一个对象,这个时候就可以使用 lookup-method 标签:
<!--原型bean-->
<bean id="my-prototype-bean" class="org.zhenchao.bean.MyPrototypeBean" scope="prototype"/>
<bean id="my-bean" class="org.zhenchao.bean.MyBean">
<!--lookup-method-->
<lookup-method name="getPrototypeBean" bean="my-prototype-bean"/>
</bean>
lookup-method 标签让 getPrototypeBean() 方法每次都会去调用一遍 prototype 对象,从而每次都返回新的对象。该标签的解析位于 parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) 方法中:
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nodes = beanEle.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// <lookup-method />
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
String beanRef = ele.getAttribute(BEAN_ELEMENT);
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(this.extractSource(ele));
overrides.addOverride(override);
}
}
}
该方法的逻辑主要是解析配置封装成 LookupOverride 对象,并将该对象添加到 MethodOverrides 的 overrides 属性中,这是一个线程安全的 Set 集合,最终的实现还是通过覆盖目标 bean 的对应方法,以实现每次调用该方法时都创建一个引用 bean 的新实例。
- 解析 replaced-method 标签
replaced-method 如其字面意思一样,可以替换一个方法的实现,如果希望替换 MyBean 的如下方法:
public void myOriginMethod() {
System.out.println("call my bean method");
}
我们首先需要定义一个实现了 MethodReplacer 接口的 bean,并实现 reimplement(Object obj, Method method, Object[] args) 方法:
public class MyReplacedBean implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("call replaced method");
return obj;
}
}
然后利用 replaced-method 配置:
<bean id="my-replacer" class="org.zhenchao.bean.MyReplacedBean"/>
<!--replaced-method-->
<replaced-method name="myOriginMethod" replacer="my-replacer"/>
这样在调用 myBean 的 myOriginMethod() 方法时,本质上是在调用 MethodReplacer 的 reimplement(Object obj, Method method, Object[] args) 方法。该标签的解析位于 parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) 方法中:
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nodes = beanEle.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// <replaced-method />
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// 获取arg-type子标签,用于指定参数类型
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
// eg. <arg-type match="String"/> or <arg-type>String</arg-type>
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(this.extractSource(replacedMethodEle));
overrides.addOverride(replaceOverride);
}
}
}
方法逻辑类似于 lookup-method,不过 replaced-method 在替换方法时可能存在方法的多个重载版本,这个时候就需要通过参数类型来确认,所以源码中增加了对于参数类型配置的获取逻辑。
- 解析构造函数参数标签
构造函数标签 <constructor-arg/> 是我们常用的标签,也是三种依赖注入方式之一,使用示例:
<!--参数配置顺序并不能决定在构造方法中的匹配顺序-->
<constructor-arg index="0" name="id" type="long" value="100001"/>
<constructor-arg index="1" name="username" type="java.lang.String" value="zhenchao"/>
<constructor-arg index="2" name="password" type="java.lang.String" value="123456"/>
配置顺序与构造方法中参数定义顺序没有直接关系,所以大部分时候都可能会映射错误,这个时候我们可以凭借 index 标签和 type 标签从两个维度上做唯一性限制。对于 <constructor-arg/> 的解析过程位于 parseConstructorArgElements(ele, beanDefinition) 方法中,<constructor-arg/> 标签可以看做是 <property/> 标签的一种特殊形式,其实现上也复用了大量 <property/> 标签的解析过程,所以这里只点到 <property/> 的解析即止,具体对于 <property/> 的解析过程,在分析 <property/> 标签解析时再展开。解析方法的源码如下:
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
NodeList nodes = beanEle.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// <constructor-arg />
if (this.isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
this.parseConstructorArgElement((Element) node, bd);
}
}
}
这里的逻辑只是遍历子标签,如果是 <constructor-arg/> 标签,则调用方法 parseConstructorArgElement((Element) node, bd) 进行解析:
public void parseConstructorArgElement(Element ele, BeanDefinition beanDefinition) {
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); // index
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); // type
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // name
if (StringUtils.hasLength(indexAttr)) {
// 设置了index属性
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
} else {
try {
this.parseState.push(new ConstructorArgumentEntry(index));
// 解析标签元素,复用 <property/> 标签的解析过程
Object value = this.parsePropertyValue(ele, beanDefinition, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
// 如果设置了type属性
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
// 如果设置了name属性
valueHolder.setName(nameAttr);
}
valueHolder.setSource(this.extractSource(ele));
if (beanDefinition.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
// 不允许有重复的index
error("Ambiguous constructor-arg entries for index " + index, ele);
} else {
// 记录index对应的构造参数ValueHolder对象
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
} finally {
this.parseState.pop();
}
}
} catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
} else {
// 没有设置index属性
try {
this.parseState.push(new ConstructorArgumentEntry());
// 解析标签元素,复用 <property/> 标签的解析过程
Object value = this.parsePropertyValue(ele, beanDefinition, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
// 如果设置了type属性
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
// 如果设置了name属性
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// 依据type或name做参数映射
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
} finally {
this.parseState.pop();
}
}
}
构造参数标签的解析过程中首先会去获取 index、type,以及 name 三个标签,然后依据是否设置了 index 属性分成两部分,如果设置了 index 属性,那么整个解析过程如下:
- 解析构造参数标签元素,这里复用了 <property/> 标签的解析过程。
- 利用 ValueHolder 封装解析出来的元素值,并设置 type 和 name 属性值添加到 ValueHolder 对象中。
- 检查是否存在重复的 index,没有的话就以 index 为 key,将 ValueHolder 对象以 Map 的形式存储到 beanDefinition 实例中。
如果没有设置 index 属性,那么解析过程如下:
- 解析构造参数标签元素,这里复用了 <property/> 标签的解析过程。
- 利用 ValueHolder 封装解析出来的元素值,并设置 type 和 name 属性值添加到 ValueHolder 对象中。
- 以 type 或 name 去推断当前 ValueHolder 对象所对应的参数,并以 List 的形式存储到 beanDefinition 实例中。
上述步骤 3 的源码如下:
public void addGenericArgumentValue(ValueHolder newValue) {
Assert.notNull(newValue, "ValueHolder must not be null");
if (!this.genericArgumentValues.contains(newValue)) {
// ValueHolder 对象必须之前没有加载过
this.addOrMergeGenericArgumentValue(newValue);
}
}
private void addOrMergeGenericArgumentValue(ValueHolder newValue) {
if (newValue.getName() != null) {
// 如果在录的参数对象存在与当前相同的参数名称,则尝试merge操作
for (Iterator<ValueHolder> it = this.genericArgumentValues.iterator(); it.hasNext(); ) {
ValueHolder currentValue = it.next();
if (newValue.getName().equals(currentValue.getName())) { // name 相同
if (newValue.getValue() instanceof Mergeable) {
Mergeable mergeable = (Mergeable) newValue.getValue();
if (mergeable.isMergeEnabled()) {
newValue.setValue(mergeable.merge(currentValue.getValue()));
}
}
it.remove();
}
}
}
this.genericArgumentValues.add(newValue);
}
方法中会去检查当前 ValueHolder 对象是否之前有加载过,没有的话则判断当前参数是否设置了 name 属性,如果有设置且当前 ValueHolder 对象与已有的 ValueHolder 对象存在相同的 name,则尝试将这个两个对象做 merge 操作,最后记录 ValueHolder 对象到 genericArgumentValues 属性中,这是一个 List。
<constructor-arg/> 标签的解析过程还是相当复杂的,这里面有相当一部分逻辑复用了 <property/> 标签的解析过程,这里我们的关注点在于 <constructor-arg/> 标签的解析过程,<property/> 的解析下面细讲。
- 解析 property 标签
<property/> 应该是 <bean/> 中最常用的标签,配置方式相当多元化,并且包含丰富的子标签元素,该标签的解析位于 parsePropertyElements(ele, beanDefinition) 方法中:
public void parsePropertyElements(Element beanEle, BeanDefinition definition) {
NodeList nodes = beanEle.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (this.isCandidateElement(node) && this.nodeNameEquals(node, PROPERTY_ELEMENT)) {
// 是候选元素或者是 <property/> 标签
this.parsePropertyElement((Element) node, definition);
}
}
}
和 <constructor-arg/> 一样,第一步也是遍历所有的子标签,如果是 <property/>,则调用处理函数 parsePropertyElement((Element) node, definition) :
public void parsePropertyElement(Element ele, BeanDefinition definition) {
// 获取标签name属性
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
if (definition.getPropertyValues().contains(propertyName)) {
// name必须是唯一的
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
// 解析标签value配置
Object val = this.parsePropertyValue(ele, definition, propertyName);
PropertyValue propertyValue = new PropertyValue(propertyName, val);
// 解析标签内的<meta/>
this.parseMetaElements(ele, propertyValue);
propertyValue.setSource(this.extractSource(ele));
// 添加property元素,本质上添加到一个list中
definition.getPropertyValues().addPropertyValue(propertyValue);
} finally {
// 出栈
this.parseState.pop();
}
}
上述方法首先会去获取 <property/> 的 name 属性,并确保 name 在一个 bean 配置中的唯一性,然后调用 parsePropertyValue(ele, definition, propertyName) 来解析标签值,接着调用 parseMetaElements(ele, propertyValue) 方法来解析标签内的 meta 配置,这个与之前 <bean/> 标签的 meta 解析过程是一样的,唯一的区别在于这里将最终的解析结果存放到 PropertyValue 实例中,不过最终该实例还是交由 beanDefinition 实例持有。假设我们为 myBean 的 age 属性配置了 meta:
<bean id="my-bean" class="org.zhenchao.bean.MyBean">
<property name="age" value="26">
<meta key="age-meta-key" value="age-meta-value"/>
</property>
</bean>
那么我们可以通过如下方式获取到该 meta 值:
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("my-bean");
Assert.assertEquals("age-meta-value", beanDefinition.getPropertyValues().getPropertyValue("age").getMetadataAttribute("age-meta-key").getValue());
上述过程的核心在解析 <property/> 标签的 value 配置,即 parsePropertyValue(ele, definition, propertyName) 方法,这也是之前在深挖 <constructor-arg/> 配置时,我们留着没用分析的方法:
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element";
NodeList nodes = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// 跳过 description 和 meta
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) {
if (subElement != null) {
// 一个<property/>只能对应一种类型:ref, value, list等
error(elementName + " must not contain more than one sub-element", ele);
} else {
subElement = (Element) node;
}
}
}
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); // ref
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); // value
if ((hasRefAttribute && hasValueAttribute) // 同时存在ref和value
|| ((hasRefAttribute || hasValueAttribute) && subElement != null)) { // 包含ref或value,但是又包含子元素
error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {
// ref元素
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
} else if (hasValueAttribute) {
// value元素
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
} else if (subElement != null) {
// 包含子元素
return this.parsePropertySubElement(subElement, bd);
} else {
// 不是 ref、value,或子元素
error(elementName + " must specify a ref or value", ele);
return null;
}
}
<property/> 标签的配置方式分为三种:<property name="" value=""/>
、<property name="" ref=""/>
,以及 <property name=""></property>
,并且同时只能使用一种方式,上述方法也是针对这三种配置分而治之。
如果配置方式是 <property name="" ref=""/>
,则利用 ref 对应的值构造 RuntimeBeanReference 对象返回。
如果配置方式是 <property name="" value=""/>
,则利用 TypedStringValue 包装 value 对应的值返回。
而对于 <property name=""></property>
方式来说,因为配置的多元化,Spring 采用专门的方法 parsePropertySubElement(subElement, bd) 对其进行解析处理:
public Object parsePropertySubElement(Element ele, BeanDefinition bd) {
return this.parsePropertySubElement(ele, bd, null);
}
public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {
if (!this.isDefaultNamespace(ele)) {
// 自定义标签
return this.parseNestedCustomElement(ele, bd);
} else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
// bean 标签,嵌套解析 bean
BeanDefinitionHolder nestedBd = this.parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
// 解析 bean 标签中嵌套的自定义标签
nestedBd = this.decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
} else if (nodeNameEquals(ele, REF_ELEMENT)) {
// ref 标签
String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); // <ref bean="">
boolean toParent = false;
if (!StringUtils.hasLength(refName)) {
// 解析local
refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); // <ref local="">
if (!StringUtils.hasLength(refName)) {
// 解析parent
refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); // <ref parent="">
toParent = true;
if (!StringUtils.hasLength(refName)) {
error("'bean', 'local' or 'parent' is required for <ref> element", ele);
return null;
}
}
}
if (!StringUtils.hasText(refName)) {
error("<ref> element contains empty target attribute", ele);
return null;
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
ref.setSource(extractSource(ele));
return ref;
} else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
// 解析idref标签
return this.parseIdRefElement(ele);
} else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
// 解析value标签
return this.parseValueElement(ele, defaultValueType);
} else if (nodeNameEquals(ele, NULL_ELEMENT)) {
// 解析null标签
TypedStringValue nullHolder = new TypedStringValue(null);
nullHolder.setSource(extractSource(ele));
return nullHolder;
} else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
// 解析array标签
return this.parseArrayElement(ele, bd);
} else if (nodeNameEquals(ele, LIST_ELEMENT)) {
// 解析list标签
return this.parseListElement(ele, bd);
} else if (nodeNameEquals(ele, SET_ELEMENT)) {
// 解析set标签
return this.parseSetElement(ele, bd);
} else if (nodeNameEquals(ele, MAP_ELEMENT)) {
// 解析map标签
return this.parseMapElement(ele, bd);
} else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
// 解析props标签
return this.parsePropsElement(ele);
} else {
// 未知的元素类型
error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
return null;
}
}
上述方法的逻辑还是很清楚的,判断当前标签类型,然后利用对应的解析方法去专门解析处理,当前标签可以是自定义标签,也可以是默认标签,甚至是两种标签类型的多层嵌套,而 Spring 的实现也是采用方法的嵌套来处理这种复杂类型。
对于 ref 标签来说,我们通常使用的方式是 <ref bean=""/>
,实际上还有 <ref local=""/>
和 <ref parent=""/>
两种类型,区别如下:
<ref bean="ref-bean"/>
表示可以引用同一容器或父容器中的 bean,这是最常用的形式。<ref local="ref-bean"/>
表示只能引用同一配置文件中定义的 bean。<ref parent="ref-bean"/>
表示引用父容器中的 bean。
对应的解析方式也是逐类型检查的过程。对于剩余的类型而言,除了 idref、value 和 null,剩余的基本都是对集合类型的处理,整个逻辑都比较清楚,暂时先不深究,需要的时候再回来看吧。
- 解析 qualifier 标签
@Qualifier
在注解中使用较为频繁,qualifier 标签的作用与其相同,但是在配置中我们一般较少使用。qualifier 标签的解析逻辑位于方法 parseQualifierElements(ele, beanDefinition) 中:
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition definition) {
NodeList nodes = beanEle.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
// <qualifier/>
if (this.isCandidateElement(node) && this.nodeNameEquals(node, QUALIFIER_ELEMENT)) {
this.parseQualifierElement((Element) node, definition);
}
}
}
这里的逻辑还是遍历寻找的过程,一旦找到 qualifier 标签,则执行解析方法 parseQualifierElement((Element) node, definition) 中的逻辑:
public void parseQualifierElement(Element ele, AbstractBeanDefinition definition) {
// 获取type属性
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
if (!StringUtils.hasLength(typeName)) {
error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
this.parseState.push(new QualifierEntry(typeName));
try {
// 封装qualifier配置
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
qualifier.setSource(extractSource(ele));
String value = ele.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(value)) {
// value属性是可选的
qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
}
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 获取attribute子标签
if (this.isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
Element attributeEle = (Element) node;
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); // key
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); // value
if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
// 封装attribute配置
BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
attribute.setSource(extractSource(attributeEle));
// 存储到qualifier对象中
qualifier.addMetadataAttribute(attribute);
} else {
error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
return;
}
}
}
// 将qualifier对象存储到beanDefinition对象中
definition.addQualifier(qualifier);
} finally {
this.parseState.pop();
}
}
上述过程逻辑很简单,就是利用相应的类对 qualifier 标签及其子标签进行封装,然后记录到 beanDefinition 实例中的过程。
至此默认标签中的默认元素都已经解析完成,并设置到 GenericBeanDefinition 对象的相应属性中,该对象会包装到 BeanDefinitionHolder 对象中返回,最后回顾一下最开始我们出发的地方:
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
1.2 自定义标签元素解析
在具体开挖自定义标签的解析过程之前,我们先复习一下自定义标签的用法,可能很多人还从来没有自定义过属于自己的标签。这里我们自定义的标签与后面需要专门讲解的与默认标签对标的自定义标签不是一个概念,这里的自定义标签是嵌套在默认标签内的,更准确的说是一种 自定义属性标签。自定义的过程分成如下几个步骤:
- 创建标签实体类
- 定义标签的描述 XSD 文件
- 创建一个标签元素解析器,实现 BeanDefinitionDecorator 接口
- 创建一个 handler 类,继承自 NamespaceHandlerSupport
- 编写 spring.handlers 和 spring.schemas 文件
我们来举个例子演示整个自定义和使用过程,自定义一个简单版本的 <property/>,包含 name 和 value 两个属性,目的是将 value 值注入到 bean 对应的名为 name 值的属性中。
第一步,先定义一个 <property/> 对应的 POJO:
public class Property {
private String name;
private String value;
// 省略 getter 和 setter
}
第二步,定义标签的 XSD 文件,用以约束配置:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.zhenchao.org/schema/property"
xmlns:tns="http://www.zhenchao.org/schema/property"
elementFormDefault="qualified">
<element name="property">
<complexType>
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="value" type="string"/>
</complexType>
</element>
</schema>
第三步,创建标签元素解析器,这里需要实现 BeanDefinitionDecorator 接口,解析器用于对标签配置属性进行解析,并设置到 beanDefinition 实例的属性集合中,后续在 getBean 时容器会将属性集合中的值赋值给 bean 实例:
public class PropertyBeanDefinitionDecorator implements BeanDefinitionDecorator {
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
Element element = (Element) node;
if ("mytag:property".equals(element.getNodeName())) {
String name = element.getAttribute("name");
Assert.hasText(name, "The 'name' in 'mytag:property' is missing!");
String value = element.getAttribute("value");
Assert.hasText(value, "The 'value' in 'mytag:property' is missing!");
PropertyValue propertyValue = new PropertyValue(name, value);
definition.getBeanDefinition().getPropertyValues().addPropertyValue(propertyValue);
}
return definition;
}
}
第四步,创建一个继承自 NamespaceHandlerSupport 的 handler 类,用于注册上面定义的解析器:
public class PropertyNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionDecorator("property", new PropertyBeanDefinitionDecorator());
}
}
第五步,编写 spring.handlers 和 spring.schemas 文件,放于 META-INF 目录下面,内容如下:
spring.handlers
http://www.zhenchao.org/schema/property=org.zhenchao.handler.PropertyNamespaceHandler
spring.schemas
http://www.zhenchao.org/schema/property.xsd=META-INF/property.xsd
下面来看一下使用方式:
首先需要在头部定义标签命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mytag="http://www.zhenchao.org/schema/property"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.zhenchao.org/schema/property http://www.zhenchao.org/schema/property.xsd"
假设 org.zhenchao.bean.MyBean
中有一个 tag 属性,那么我们可以利用我们自定义的标签进行如下配置:
<bean id="my-bean" class="org.zhenchao.bean.MyBean">
<mytag:property name="tag" value="myCustomTagValue"/>
</bean>
当我们获取 MyBean 实例的 tag 属性值时,就能够得到我们配置的值,类似于标准 <property/> 标签的效果,不过 Spring 提供的 <property/> 标签功能要强大很多,这里只是做了一个自定义标签的定义方式的演示,实际开发中我们自定义的标签也会比这要复杂。当我们在使用 Spring 默认的标签配置时,如果发现配置异常复杂,这个时候就可以考虑是否使用自定义的标签来简化配置。不过笔者也不推荐为了自定义而自定义,自定义的标签给后来人阅读源码带来了很大的负担,增加了学习成本。
介绍完了自定义标签的定义和使用方式,我们继续来剖析 Spring 对于自定义标签解析过程的源码,解析过程位于 BeanDefinitionParserDelegate 的 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) 方法中:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return this.decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
// 1. 处理当前标签的属性
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
// 遍历所有的属性,如果是目标自定义标签则进行处理
Node node = attributes.item(i);
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}
// 2. 处理当前标签的子标签
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// 遍历所有的子标签,如果是目标自定义标签则进行处理
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) { // element
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
上述方法的逻辑分为两步处理,第一步处理当前标签所有的自定义属性,第二步处理当前标签的所有自定义子标签。所有的遍历过程处理都是调用的 decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) 方法逻辑:
public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
// 获取自定义标签的命名空间
String namespaceUri = this.getNamespaceURI(node);
if (!this.isDefaultNamespace(namespaceUri)) {
// 根据命名空间找到对应的 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
// 调用decorate方法处理自定义标签
return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
} else if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
} else {
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}
解析过程与我们定义自定义标签的过程相呼应,方法首先获取标签的命名空间,并以此来判断当前属性或标签是否是自定义的,如果是的话则获取对应的 NamespaceHandler,也就是我们之前自定义的 PropertyNamespaceHandler,我们在该类的 init() 方法中注册了我们的 Decorator 实例:
public void init() {
this.registerBeanDefinitionDecorator("property", new PropertyBeanDefinitionDecorator());
}
然后调用 handler 处理自定义的标签,这里本质上还是调用扩展 BeanDefinitionDecorator 的 decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) 方法,这样就将我们自定义的实现部分和 Spring 框架集成在了一起。
1.3 注册 BeanDefinitionHolder 对象
在将 bean 的默认标签和自定义标签都设置到 beanDefinition 实例中后,接下来就是注册 BeanDefinitionHolder 对象啦,前面我们多次提到这个类,还是来看看这个类里面到底有啥吧:
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
private final String[] aliases;
// 方法略
}
由源码可以看到 BeanDefinitionHolder 中存储了 beanDefinition 实例,以及 bean 对应的唯一 name 和多个别名,类似于一个收纳箱。
BeanDefinitionHolder 对象的注册过程位于工具类 BeanDefinitionReaderUtils 中,对应的方法是 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry):
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 以beanName作为唯一标识进行注册
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 建立alias与beanName之间的映射关系
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
在这个方法中,主要做了两件事情:1) 以 beanName 作为 key,注册 beanDefinition 实例,本质上就是以 map 进行内存存储;2) 建立别名与主名称之间的映射关系。
1.3.1 注册 BeanDefinition 实例
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");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
/*
* 校验AbstractBeanDefinition属性中的methodOverrides
* 验证methodOverrides是否与工厂方法并存或覆盖的方法根本不存在
*/
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
}
}
// 尝试获取beanName是否已经绑定了BeanDefinition
BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) { // 对应beanName已经绑定了BeanDefinition对象
if (!this.isAllowBeanDefinitionOverriding()) {
// 不允许覆盖绑定
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound.");
} else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
/*
* 定义bean的应用场景
* ROLE_APPLICATION:0, 用户
* ROLE_SUPPORT:1, 某些复杂配置的一部分
* ROLE_INFRASTRUCTURE:2, 完全内部使用
*/
// 新的bean的role应用场景变小,警告
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
// 注册,用map保存注册信息
this.beanDefinitionMap.put(beanName, beanDefinition);
} else { // 对应的beanName没有绑定相应的BeanDefinition
if (this.hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(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 (oldBeanDefinition != null || this.containsSingleton(beanName)) {
// 清空所有beanName对应的缓存
this.resetBeanDefinition(beanName);
}
}
方法中首先会判断 beanDefinition 是否是 AbstractBeanDefinition 类型实例,如果是的话则进一步验证其 methodOverrides 属性,防止出现与工厂方法并存或覆盖的方法根本不存在的情况。然后检查 beanName 是否已经绑定了 beanDefinition 实例,如果已经绑定且允许覆盖已有的实例,则执行覆盖操作,如果没有绑定,则直接绑定。这里的注册绑定,本质上就是以 beanName 为 key,以 beanDefinition 实例为 value 存储在 map 数据结构中,后续加载时,会从依据给定的 beanName 从中获取并对 bean 进行实例化操作。
1.3.2 建立别名与主名称之间的映射关系
注册过程的第二步就是建立别名与主名称之间的映射关系,如果把 beanDefinition 实例看做是文件的话,那么 beanName 可以看做是文件的硬链接,而 alias 则可以看做是软连接,是 beanName 的快捷方式。
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
if (alias.equals(name)) {
// 如果alias等于name,则将alias从map中删除
this.aliasMap.remove(alias);
} else {
String registeredName = this.aliasMap.get(alias); // 尝试获取alias对应的beanName
if (registeredName != null) {
if (registeredName.equals(name)) {
// 已经注册过,直接返回
return;
}
if (!this.allowAliasOverriding()) {
// 不允许覆盖
throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
}
}
// 检测name和alias之间是否构成环路,如果构成环路则抛出异常
this.checkForAliasCircle(name, alias);
// 不存在环路,直接注册,建立alias与name之间的映射关系
this.aliasMap.put(alias, name);
}
}
上述源码注释清晰说明了建立映射的过程,不再多做撰述。
1.4 发布事件消息
到这里,容器完成了对 <bean/> 标签的解析过程,考虑到一些应用可能需要感知这一事件,Spring 在最后设置了事件消息发布逻辑,以 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)) 的方式将消息通知到具体的监听者,不过 Spring 并没有给出具体的实现,需要的话可以自己扩展实现。
二. import 标签的解析过程
<import/> 也是我们比较常用的标签,尤其是在大型项目中,通过将各个模块的 Spring 配置独立开来,并在需要的地方通过 <import/> 标签引入,可以让配置更加的清晰,易于管理。该标签的解析过程位于 importBeanDefinitionResource(Element ele) 方法中:
protected void importBeanDefinitionResource(Element ele) {
String location = ele.getAttribute(RESOURCE_ATTRIBUTE); // 获取resource字段,<import resource="xx.xml"/>
if (!StringUtils.hasText(location)) {
this.getReaderContext().error("Resource location must not be empty", ele);
return;
}
// 解析路径中的系统属性,比如可能存在如${user.dir}的占位符
location = this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
boolean absoluteLocation = false;
try {
// 检测是不是绝对路径
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
// uri异常则以相对路径对待
}
if (absoluteLocation) {
// 当前是绝对路径
try {
// 加载beanDefinition,并返回加载的数目
int importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
} else {
// 当前是相对路径
try {
int importCount;
// Resource存在多个子类,各子类的createRelative实现不一样,这里先使用子类的方法尝试解析
Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 加载BeanDefinition,并返回加载的数目
importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
// 解析不成功,使用默认的解析器ResourcePatternResolver进行解析
String baseLocation = this.getReaderContext().getResource().getURL().toString();
importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException ex) {
this.getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
// 解析完成之后,通知所有的监听器
this.getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
<import/> 标签仅包含一个属性 resource,该属性指定了配置文件的路径,这里的路径可以是相对路径,或绝对路径,路径中还可能存在一些系统属性占位符,比如 ${user.dir}
,方法中先对系统属性进行了处理,然后判定当前路径属于绝对路径还是相对路径,并分而治之。
三. alias 标签的解析过程
<alias/> 用于一个已定义的 bean 设置别名,虽然在 <bean/> 标签中可以通过 name 属性定义别名,但是存在即合理,<alias/> 总有它的应用场景。
protected void processAliasRegistration(Element ele) {
// <alias name="" alias=""/>
String name = ele.getAttribute(NAME_ATTRIBUTE); // 获取name字段
String alias = ele.getAttribute(ALIAS_ATTRIBUTE); // 获取alias字段
boolean valid = true;
if (!StringUtils.hasText(name)) {
// name不允许为空
this.getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
// alias不允许为空
this.getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
// 注册alias
this.getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex);
}
// alias注册完成后,通知相关监听器
this.getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
<alias/> 标签的解析过程复用了之前解析的 registerAlias(name, alias) 方法,用于建立别名与主名称之间的映射关系,同时避免出现环路。
四. beans 标签的解析过程
上面我们解析的几种标签都是位于 <beans/> 标签下面,本小节即将解析的 <beans/> 标签是嵌套在外围 <beans/> 标签中的,本质上没有什么区别,所以 Spring 的解析过程也是递归调用了之前的解析过程:
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理profile标签(其作用类比pom.xml中的profile)
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles =
StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 解析预处理,留给子类实现
this.preProcessXml(root);
// 解析并注册BeanDefinition
this.parseBeanDefinitions(root, this.delegate);
// 解析后处理,留给子类实现
this.postProcessXml(root);
this.delegate = parent;
}
上述方法就是解析 <beans/> 标签的源码,慢着,是不是看着有点眼熟,没错,本文最开始就是从这个方法的 parseBeanDefinitions(root, this.delegate) 方法开挖的,饶了一大圈,我们又回到了原点,突然想到了一部电影 《恐怖游轮》,不多说了,睡觉~
系列文章
- Spring源码解析:获取源码
- Spring源码解析:资源的描述与加载
- Spring源码解析:IoC容器的基本结构设计
- Spring源码解析:简单容器中Bean的加载过程初探
- Spring源码解析:默认标签的解析过程
- Spring源码解析:自定义标签的解析过程
- Spring源码解析:Bean实例的创建与初始化
- Spring源码解析:高级容器的扩展内幕
- Spring源码解析:循环依赖的探测与处理
鉴于作者水平有限,文中不免有错误之处,欢迎大家批评指正~
同步更新站点:www.zhenchao.org