返回介绍

3.1.1 解析 BeanDefinition

发布于 2025-04-22 22:09:08 字数 34310 浏览 0 评论 0 收藏

下面我们就针对各个操作做具体分析。首先我们从元素解析及信息提取开始,也就是 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入 BeanDefinition Delegate 类的 parseBeanDefinitionElement 方法。

BeanDefinitionDelegate.java

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {

  return parseBeanDefinitionElement(ele, null);

}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition

containingBean) {

 //解析 id 属性

   String id = ele.getAttribute(ID_ATTRIBUTE);

 //解析 name 属性

  String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

 //分割 name 属性

   List<String> aliases = new ArrayList<String>();

   if (StringUtils.hasLength(nameAttr)) {

    String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_

   VALUE_ATTRIBUTE_DELIMITERS);

   aliases.addAll(Arrays.asList(nameArr));

  }

   String beanName = id;

   if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {

    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) {

    checkNameUniqueness(beanName, aliases, ele);

  }

   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName,

  containingBean);

   if (beanDefinition != null) {

    if (!StringUtils.hasText(beanName)) {

     try {

     //如果不存在 beanName 那么根据 Spring 中提供的命名规则为当前 bean 生成对应

     的 beanName

      if (containingBean != null) {

       beanName = BeanDefinitionReaderUtils.generateBeanName(

       beanDefinition, this.readerContext.getRegistry(), true);

     }

     else {

      beanName = this.readerContext.generateBeanName(beanDefinition);

      String beanClassName = beanDefinition.getBeanClassName();

      if (beanClassName != null &&

       > beanClassName.length() &&

       beanName.startsWith(beanClassName) && beanName.length()

        (beanClassName)) {

        !this.readerContext.getRegistry(). IsBeanNameInUse

      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);

   return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);

 }

  return null;

}

以上便是对默认标签解析的全过程了。当然,对 Spring 的解析犹如洋葱剥皮一样,一层一层地进行,尽管现在只能看到对属性 id 以及 name 的解析,但是很庆幸,思路我们已经了解了。在开始对属性展开全面解析前,Spring 在外层又做了一个当前层的功能架构,在当前层完成的主要工作包括如下内容。

(1)提取元素中的 id 以及 name 属性。

(2)进一步解析其他所有属性并统一封装至 GenericBeanDefinition 类型的实例中。

(3)如果检测到 bean 没有指定 beanName,那么使用默认规则为此 Bean 生成 beanName。

(4)将获取到的信息封装到 BeanDefinitionHolder 的实例中。

我们进一步地查看步骤(2)中对标签其他属性的解析过程。

public AbstractBeanDefinition parseBeanDefinitionElement(

Element ele, String beanName, BeanDefinition containingBean) {

  this.parseState.push(new BeanEntry(beanName));

  String className = null;

 //解析 class 属性

  if (ele.hasAttribute(CLASS_ATTRIBUTE)) {

   className = ele.getAttribute(CLASS_ATTRIBUTE).trim();

 }

  try {

   String parent = null;

  //解析 parent 属性

   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {

    parent = ele.getAttribute(PARENT_ATTRIBUTE);

  }

  //创建用于承载属性的 AbstractBeanDefinition 类型的 GenericBeanDefinition

   AbstractBeanDefinition bd = createBeanDefinition(className, parent);

  //硬编码解析默认 bean 的各种属性

  parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

  //提取 description

  ELEMENT));

   bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_

  //解析元数据

  parseMetaElements(ele, bd);

  //解析 lookup-method 属性

  parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

  //解析 replaced-method 属性

  parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

  //解析构造函数参数

  parseConstructorArgElements(ele, bd);

  //解析 property 子元素

  parsePropertyElements(ele, bd);

  //解析 qualifier 子元素

  parseQualifierElements(ele, bd);

  bd.setResource(this.readerContext.getResource());

  bd.setSource(extractSource(ele));

   return bd;

 }

  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;

}

终于,bean 标签的所有属性,不论常用的还是不常用的我们都看到了,尽管有些复杂的属性还需要进一步的解析,不过丝毫不会影响我们兴奋的心情。接下来,我们继续一些复杂标签属性的解析。

1.创建用于属性承载的 BeanDefinition

BeanDefinition 是一个接口,在 Spring 中存在三种实现:RootBeanDefinition、ChildBean Definition 以及 GenericBeanDefinition。三种实现均继承了 AbstractBeanDefiniton ,其中 BeanDefinition 是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有 class、scope、lazy-init 等配置属性,BeanDefinition 则提供了相应的 beanClass、scope、lazyInit 属性,BeanDefinition 和<bean>中的属性是一一对应的。其中 RootBeanDefinition 是最常用的实现类,它对应一般性的<bean>元素标签,GenericBeanDefinition 是自 2.5 版本以后新加入的 bean 文件配置属性定义类,是一站式服务类。

在配置文件中可以定义父<bean>和子<bean>,父<bean>用 RootBeanDefinition 表示,而子<bean>用 ChildBeanDefiniton 表示,而没有父<bean>的<bean>就使用 RootBeanDefinition 表示。AbstractBeanDefinition 对两者共同的类信息进行抽象。

Spring 通过 BeanDefinition 将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些 BeanDefiniton 注册到 BeanDefinitonRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置信息的内存数据库,主要是以 map 的形式保存,后续操作直接从 BeanDefinition Registry 中读取配置信息。它们之间的关系如图 3-2 所示。

figure_0050_0029

图 3-2 BeanDefinition 及其实现类

由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建 GenericBeanDefinition 类型的实例。而代码 createBeanDefinition(className, parent) 的作用就是实现此功能。

protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)

throws ClassNotFoundException {

  return BeanDefinitionReaderUtils.createBeanDefinition(

  parentName, className, this.readerContext.getBeanClassLoader());

}

BeanDefinitionReaderUtils.java

public static AbstractBeanDefinition createBeanDefinition(

String parentName, String className, ClassLoader classLoader) throws

ClassNotFoundException {

  GenericBeanDefinition bd = new GenericBeanDefinition();

//parentName 可能为空

bd.setParentName(parentName);

  if (className != null) {

   if (classLoader != null) {

  //如果 classLoader 不为空,则使用以传入的 classLoader 同一虚拟机加载类对象,否则只是

  记录 className

    bd.setBeanClass(ClassUtils.forName(className, classLoader));

  }

   else {

   bd.setBeanClassName(className);

  }

 }

  return bd;

}

2.解析各种属性

当我们创建了 bean 信息的承载实例后,便可以进行 bean 信息的各种属性解析了,首先我们进入 parseBeanDefinitionAttributes 方法。parseBeanDefinitionAttributes 方法是对 element 所有元素属性进行解析:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,

BeanDefinition containingBean, AbstractBeanDefinition bd) {

 //解析 scope 属性

  if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {

   // Spring 2.x "scope" attribute

  bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));

   if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {

 //scope 与 singleton 两个属性只能指定其中之一,不可以同时出现,否则 Spring 将会报出异常

   error("Specify either 'scope' or 'singleton', not both", ele);

  }

 }

 //解析 singleton 属性

  else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {

   // Spring 1.x "singleton" attribute

   bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?

    BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);

 }

  else if (containingBean != null) {

  // Take default from containing bean in case of an inner bean definition.

 //在嵌入 beanDifinition 情况下且没有单独指定 scope 属性则使用父类默认的属性

  bd.setScope(containingBean.getScope());

 }

 //解析 abstract 属性

if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {

 bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));

}

//解析 lazy-init 属性

String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);

if (DEFAULT_VALUE.equals(lazyInit)) {

  lazyInit = this.defaults.getLazyInit();

}

//若没有设置或设置成其他字符都会被设置为 false

bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

//解析 autowire 属性

String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);

bd.setAutowireMode(getAutowireMode(autowire));

//解析 dependency-check 属性

String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);

bd.setDependencyCheck(getDependencyCheck(dependencyCheck));

//解析 depends-on 属性

if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {

  String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);

  bd.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);

   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)));

}

//解析 init-method 属性

if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {

  String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);

  if (!"".equals(initMethodName)) {

  bd.setInitMethodName(initMethodName);

 }

}

else {

  if (this.defaults.getInitMethod() != null) {

  bd.setInitMethodName(this.defaults.getInitMethod());

   bd.setEnforceInitMethod(false);

  }

 }

 //解析 destroy-method 属性

  if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {

   String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);

   if (!"".equals(destroyMethodName)) {

   bd.setDestroyMethodName(destroyMethodName);

  }

 }

  else {

   if (this.defaults.getDestroyMethod() != null) {

   bd.setDestroyMethodName(this.defaults.getDestroyMethod());

   bd.setEnforceDestroyMethod(false);

  }

 }

 //解析 factory-method 属性

  if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {

  bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));

 }

 //解析 factory-bean 属性

  if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {

  bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));

 }

  return bd;

}

我们可以清楚地看到 Spring 完成了对所有 bean 属性的解析,这些属性中有很多是我们经常使用的,同时我相信也一定会有或多或少的属性是读者不熟悉或者是没有使用过的,有兴趣的读者可以查阅相关资料进一步了解每个属性。

3.解析子元素 meta

在开始解析元数据的分析前,我们先回顾下元数据 meta 属性的使用。

<bean id="myTestBean" class="bean.MyTestBean">

  <meta key="testStr" value="aaaaaaaa"/>

</bean>

这段代码并不会体现在 MyTestBean 的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过 BeanDefinition 的 getAttribute(key) 方法进行获取。

对 meta 属性的解析代码如下:

public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {

 //获取当前节点的所有子元素

  NodeList nl = ele.getChildNodes();

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

  //提取 meta

   if (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(extractSource(metaElement));

   //记录信息

   attributeAccessor.addMetadataAttribute(attribute);

  }

 }

}

4.解析子元素 lookup-method

同样,子元素 lookup-method 似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注入。引用《Spring in Action》中的一句话:获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean,但实际要返回的 bean 是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。我们看看具体的应用。

(1)首先我们创建一个父类。

package test.lookup.bean;

public class User {

  public void showMe(){

   System.out.println("i am user");

 }

}

(2)创建其子类并覆盖 showMe 方法。

package test.lookup.bean;

public class Teacher extends User{

  public void showMe(){

   System.out.println("i am Teacher");

 }

}

(3)创建调用方法。

public abstract class GetBeanTest {

  public void showMe(){

  this.getBean().showMe();

 }

  public abstract User getBean();

}

(4)创建测试方法。

package test.lookup;

import org.Springframework.context.ApplicationContext;

import org.Springframework.context.support.ClassPathXmlApplicationContext;

import test.lookup.app.GetBeanTest;

public class Main {

  public static void main(String[] args) {

   ApplicationContext bf =

    new ClassPathXmlApplicationContext("test/lookup/lookupTest.xml");

   GetBeanTest test=(GetBeanTest) bf.getBean("getBeanTest");

  test.showMe();

 }

}

到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注入的读者们可能会有疑问:抽象方法还没有被实现,怎么可以直接调用呢?答案就在 Spring 为我们提供的获取器中,我们看看配置文件是怎么配置的。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.Springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www. Springframework.

 org/schema/beans/Spring-beans.xsd">

   <bean id="getBeanTest" class="test.lookup.app.GetBeanTest">

  <lookup-method name="getBean" bean="teacher"/>

 </bean>

<bean id="teacher" class="test.lookup.bean.Teacher"/>

</beans>

在配置文件中,我们看到了源码解析中提到的 lookup-method 子元素,这个配置完成的功能是动态地将 teacher 所代表的 bean 作为 getBean 的返回值,运行测试方法我们会看到控制台上的输出:

i am Teacher

当我们的业务变更或者在其他情况下,teacher 里面的业务逻辑已经不再符合我们的业务要求,需要进行替换怎么办呢?这是我们需要增加新的逻辑类:

package test.lookup.bean;

public class Student extends User {

  public void showMe(){

   System.out.println("i am student");

 }

}

同时修改配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.Springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.

 org/schema/beans/Spring-beans.xsd">

   <bean id="getBeanTest" class="test.lookup.app.GetBeanTest">

  <lookup-method name="getBean" bean="student"/>

 </bean>

<bean id="teacher" class="test.lookup.bean.Teacher"/>

<bean id="student" class="test.lookup.bean.Student"/>

</beans>

再次运行测试类,你会发现不一样的结果:

i am Student

至此,我们已经初步了解了 lookup-method 子元素所提供的大致功能,相信这时再次去看它的属性提取源码会觉得更有针对性。

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {

  NodeList nl = beanEle.getChildNodes();

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

   //仅当在 Spring 默认 bean 的子元素下且为 <lookup-method 时有效

  METHOD_ELEMENT)) {

   if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_

    Element ele = (Element) node;

   //获取要修饰的方法

    String methodName = ele.getAttribute(NAME_ATTRIBUTE);

   //获取配置返回的 bean

    String beanRef = ele.getAttribute(BEAN_ELEMENT);

    LookupOverride override = new LookupOverride(methodName, beanRef);

   override.setSource(extractSource(ele));

   overrides.addOverride(override);

  }

 }

}

上面的代码很眼熟,似乎与 parseMetaElements 的代码大同小异,最大的区别就是在 if 判断中的节点名称在这里被修改为 LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用 LookupOverride 类型的实体类来进行数据承载并记录在 AbstractBeanDefinition 中的 methodOverrides 属性中。

5.解析子元素 replaced-method

这个方法主要是对 bean 中 replaced-method 子元素的提取,在开始提取分析之前我们还是预先介绍下这个元素的用法。

方法替换:可以在运行时用新的方法替换现有的方法。与之前的 look-up 不同的是, replaced-method 不但可以动态地替换返回实体 bean,而且还能动态地更改原有方法的逻辑。我们来看看使用示例。

(1)在 changeMe 中完成某个业务逻辑。

public class TestChangeMethod {

  public void changeMe(){

  System.out.println("changeMe");

 }

}

(2)在运营一段时间后需要改变原有的业务逻辑。

public class TestMethodReplacer implements MethodReplacer{

@Override

public Object reimplement(Object obj, Method method, Object[] args)throws Throwable {

 System.out.println("我替换了原有的方法");

   return null;

 }

}

(3)使替换后的类生效。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.Springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.

org/schema/beans/Spring-beans.xsd">

<bean id="testChangeMethod" class="test.replacemethod.TestChangeMethod">

 <replaced-method name="changeMe" replacer="replacer"/>

</bean>

<bean id="replacer" class="test.replacemethod.TestMethodReplacer"/>

</beans>

(4)测试。

public static void main(String[] args) {

  ApplicationContext bf =

   new ClassPathXmlApplicationContext("test/replacemethod/replaceMethodTest.xml");

TestChangeMethod test=(TestChangeMethod) bf.getBean("testChangeMethod");

test.changeMe();

}

好了,运行测试类就可以看到预期的结果了,控制台成功打印出“我替换了原有的方法”,也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再次来看元素的提取过程:

public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {

  NodeList nl = beanEle.getChildNodes();

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

  //仅当在 Spring 默认 bean 的子元素下且为<replaced-method 时有效

  ELEMENT)) {

   if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_

    Element replacedMethodEle = (Element) node;

   //提取要替换的旧的 方法

    String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);

   //提取对应的新的替换方法

    String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);

    ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);

    List<Element> argTypeEles = DomUtils.getChildElementsByTagName

    (replacedMethodEle, ARG_TYPE_ELEMENT);

    for (Element argTypeEle : argTypeEles) {

    //记录参数

     String match = argTypeEle.getAttribute (ARG_TYPE_MATCH_ATTRIBUTE);

     match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue

    (argTypeEle));

     if (StringUtils.hasText(match)) {

     replaceOverride.addTypeIdentifier(match);

    }

   }

   replaceOverride.setSource(extractSource(replacedMethodEle));

   overrides.addOverride(replaceOverride);

  }

 }

}

我们可以看到无论是 look-up 还是 replaced-method 都是构造了一个 MethodOverride,并最终记录在了 AbstractBeanDefinition 中的 methodOverrides 属性中。而这个属性如何使用以完成它所提供的功能我们会在后续的章节进行详细地介绍。

6.解析子元素 constructor-arg

对构造函数的解析是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置都不陌生,举个简单的小例子:

... ...

<beans>

<!-- 默认的情况下是按照参数的顺序注入,当指定 index 索引后就可以改变注入参数的顺序 -->

<bean id="helloBean" class="com.HelloBean">

  <constructor-arg index="0">

  <value>郝佳</value>

 </constructor-arg>

  <constructor-arg index="1">

  <value>你好</value>

 </constructor-arg>

</bean>

... ...

</beans>

上面的配置是 Spring 构造函数配置中最基础的配置,实现的功能就是对 HelloBean 自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去。那么让我们来看看具体的 XML 解析过程。

对于 constructor-arg 子元素的解析,Spring 是通过 parseConstructorArgElements 函数来实现的,具体的代码如下:

public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {

  NodeList nl = beanEle.getChildNodes();

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

   if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {

   //解析 constructor-arg

   parseConstructorArgElement((Element) node, bd);

  }

 }

}

这个结构似乎我们可以想象得到,遍历所有子元素,也就是提取所有 constructor-arg,然后进行解析,但是具体的解析却被放置在了另个函数 parseConstructorArgElement 中,具体代码如下:

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {

 //提取 index 属性

  String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);

 //提取 type 属性

  String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);

 //提取 name 属性

  String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

  if (StringUtils.hasLength(indexAttr)) {

   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));

     //解析 ele 对应的属性元素

      Object value = parsePropertyValue(ele, bd, null);

      ConstructorArgumentValues.ValueHolder valueHolder = new

     ConstructorArgumentValues.ValueHolder(value);

      if (StringUtils.hasLength(typeAttr)) {

      valueHolder.setType(typeAttr);

     }

      if (StringUtils.hasLength(nameAttr)) {

      valueHolder.setName(nameAttr);

     }

     valueHolder.setSource(extractSource(ele));

     //不允许重复指定相同参数

      if (bd.getConstructorArgumentValues(). hasIndexedArgumentValue

      (index)) {

       error("Ambiguous constructor-arg entries for index " +

       index, ele);

      }else {

      (index, valueHolder);

      bd.getConstructorArgumentValues(). AddIndexedArgumentValue

     }

     }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());

    Object value = parsePropertyValue(ele, bd, null);

    ConstructorArgumentValues.ValueHolder valueHolder = new Constructor

   ArgumentValues.ValueHolder(value);

    if (StringUtils.hasLength(typeAttr)) {

    valueHolder.setType(typeAttr);

   }

    if (StringUtils.hasLength(nameAttr)) {

    valueHolder.setName(nameAttr);

   }

   valueHolder.setSource(extractSource(ele));

   bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);

  }

   finally {

   this.parseState.pop();

  }

 }

}

上面一段看似复杂的代码让很多人失去了耐心,但是,涉及的逻辑其实并不复杂,首先是提取 constructor-arg 上必要的属性(index、type、name)。

如果配置中指定了 index 属性,那么操作步骤如下。

(1)解析 constructor-arg 的子元素。

(2)使用 ConstructorArgumentValues.ValueHolder 类型来封装解析出来的元素。

(3)将 type、name 和 index 属性一并封装在 ConstructorArgumentValues.ValueHolder 类型中并添加至当前 BeanDefinition 的 constructorArgumentValues 的 indexedArgumentValues 属性中。

如果没有指定 index 属性,那么操作步骤如下。

(1)解析 constructor-arg 的子元素。

(2)使用 ConstructorArgumentValues.ValueHolder 类型来封装解析出来的元素。

(3)将 type、name 和 index 属性一并封装在 ConstructorArgumentValues.ValueHolder 类型中并添加至当前 BeanDefinition 的 constructorArgumentValues 的 genericArgumentValues 属性中。

可以看到,对于是否制定 index 属性来讲,Spring 的处理流程是不同的,关键在于属性信息被保存的位置。

那么了解了整个流程后,我们尝试着进一步了解解析构造函数配置中子元素的过程,进入 parsePropertyValue:

public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {

  String elementName = (propertyName != null) ?

   "<property> element for property '" + propertyName + "'" :

   "<constructor-arg> element";

 //一个属性只能对应一种类型:ref、value、list 等

  NodeList nl = ele.getChildNodes();

  Element subElement = null;

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

  //对应 description 或者 meta 不处理

   if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&

    !nodeNameEquals(node, META_ELEMENT)) {

   if (subElement != null) {

    error(elementName + " must not contain more than one sub-element", ele);

  }

   else {

    subElement = (Element) node;

  }

  }

 }

 //解析 constructor-arg 上的 ref 属性

  boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);

 //解析 constructor-arg 上的 value 属性

  boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);

  if ((hasRefAttribute && hasValueAttribute) ||

   ((hasRefAttribute || hasValueAttribute) && subElement != null)) {

 /*

  * 在 constructor-arg 上不存在:

  * 1、同时既有 ref 属性又有 value 属性

  * 2、存在 ref 属性或者 value 属性且又有子元素

  */

  error(elementName +

   " is only allowed to contain either 'ref' attribute OR 'value'

   attribute OR sub-element", ele);

 }

  if (hasRefAttribute) {

  //ref 属性的处理,使用 RuntimeBeanReference 封装对应的 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 封装

   TypedStringValue valueHolder = new TypedStringValue (ele.getAttribute

  (VALUE_ATTRIBUTE));

  valueHolder.setSource(extractSource(ele));

   return valueHolder;

  }else if (subElement != null) {

  //解析子元素

   return parsePropertySubElement(subElement, bd);

  }else {

  //既没有 ref 也没有 value 也没有子元素,Spring 蒙圈了

   error(elementName + " must specify a ref or value", ele);

   return null;

 }

}

从代码上来看,对构造函数中属性元素的解析,经历了以下几个过程。

(1)略过 description 或者 meta。

(2)提取 constructor-arg 上的 ref 和 value 属性,以便于根据规则验证正确性,其规则为在 constructor-arg 上不存在以下情况。

同时既有 ref 属性又有 value 属性。

存在 ref 属性或者 value 属性且又有子元素。

(3)ref 属性的处理。使用 RuntimeBeanReference 封装对应的 ref 名称,如:

<constructor-arg ref="a" >

(4)value 属性的处理。使用 TypedStringValue 封装,例如:

<constructor-arg value="a" >

(5)子元素的处理。例如:

<constructor-arg>

 <map>

   <entry key="key" value="value" />

 </map>

</constructor-arg>

而对于子元素的处理,例如这里提到的在构造函数中又嵌入了子元素 map 是怎么实现的呢?parsePropertySubElement 中实现了对各种子元素的分类处理。

public Object parsePropertySubElement(Element ele, BeanDefinition bd) {

  return parsePropertySubElement(ele, bd, null);

}

public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {

  if (!isDefaultNamespace(ele)) {

   return parseNestedCustomElement(ele, bd);

 }

  else if (nodeNameEquals(ele, BEAN_ELEMENT)) {

   BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);

   if (nestedBd != null) {

    nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);

  }

   return nestedBd;

 }

  else if (nodeNameEquals(ele, REF_ELEMENT)) {

   // A generic reference to any name of any bean.

   String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);

   boolean toParent = false;

   if (!StringUtils.hasLength(refName)) {

   //解析 local

    refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);

    if (!StringUtils.hasLength(refName)) {

    //解析 parent

     refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);

     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;

 }

 //对 idref 元素的解析

  else if (nodeNameEquals(ele, IDREF_ELEMENT)) {

   return parseIdRefElement(ele);

 }

 //对 value 子元素的解析

  else if (nodeNameEquals(ele, VALUE_ELEMENT)) {

   return parseValueElement(ele, defaultValueType);

 }

 //对 null 子元素的解析

  else if (nodeNameEquals(ele, NULL_ELEMENT)) {

   // It's a distinguished null value. Let's wrap it in a TypedStringValue

   // object in order to preserve the source location.

   TypedStringValue nullHolder = new TypedStringValue(null);

  nullHolder.setSource(extractSource(ele));

   return nullHolder;

 }

  else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {

  //解析 array 子元素

   return parseArrayElement(ele, bd);

 }

  else if (nodeNameEquals(ele, LIST_ELEMENT)) {

  //解析 list 子元素

   return parseListElement(ele, bd);

 }

  else if (nodeNameEquals(ele, SET_ELEMENT)) {

  //解析 set 子元素

   return parseSetElement(ele, bd);

 }

  else if (nodeNameEquals(ele, MAP_ELEMENT)) {

  //解析 map 子元素

   return parseMapElement(ele, bd);

 }

  else if (nodeNameEquals(ele, PROPS_ELEMENT)) {

  //解析 props 子元素

   return parsePropsElement(ele);

 }

  else {

   error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);

   return null;

 }

}

可以看到,在上面的函数中实现了所有可支持的子类的分类处理,到这里,我们已经大致理清构造函数的解析流程,至于再深入的解析读者有兴趣可以自己去探索。

7.解析子元素 property

parsePropertyElement 函数完成了对 property 属性的提取,property 使用方式如下:

<bean id="test" class="test.TestClass">

  <property name="testStr" value="aaa"/>

</bean>

或者

<bean id="a">

  <property name="p">

  <list>

   <value>aa</value>

   <value>bb</value>

  </list>

 </property>

</bean>

而具体的解析过程如下:

public void parsePropertyElements(Element beanEle, BeanDefinition bd) {

  NodeList nl = beanEle.getChildNodes();

  for (int i = 0; i < nl.getLength(); i++) {

   Node node = nl.item(i);

   if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {

   parsePropertyElement((Element) node, bd);

  }

 }

}

有了之前分析构造函数的经验,这个函数我们并不难理解,无非是提取所有 property 的子元素,然后调用 parsePropertyElement 处理,parsePropertyElement 代码如下:

public void parsePropertyElement(Element ele, BeanDefinition bd) {

 //获取配置元素中 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 (bd.getPropertyValues().contains(propertyName)) {

    error("Multiple 'property' definitions for property '" + propertyName + "'", ele);

   return;

  }

   Object val = parsePropertyValue(ele, bd, propertyName);

   PropertyValue pv = new PropertyValue(propertyName, val);

   parseMetaElements(ele, pv);

  pv.setSource(extractSource(ele));

  bd.getPropertyValues().addPropertyValue(pv);

 }

  finally {

  this.parseState.pop();

 }

}

可以看到上面函数与构造函数注入方式不同的是将返回值使用 PropertyValue 进行封装,并记录在了 BeanDefinition 中的 propertyValues 属性中。

8.解析子元素 qualifier

对于 qualifier 元素的获取,我们接触更多的是注解的形式,在使用 Spring 框架中进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时, Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。

Spring 允许我们通过 Qualifier 指定注入 Bean 的名称,这样歧义就消除了,而对于配置方式使用如:

<bean id="myTestBean" class="bean.MyTestBean">

  <qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/>

</bean>

其解析过程与之前大同小异,这里不再重复叙述。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。