1. 优享JAVA首页
  2. Java
  3. Spring
  4. Spring源码分析

默认标签解析 二

1.2、AbstractBeanDefinition属性

至此我们完成了对XML文档到GenericBeanDefinition的转换,也就是说到这里,XML中所有的配置都可以在GenericBeanDefinition的实例类中找到对应的配置。
GenericBeanDefinition只有一个子实现类,而大部分的通用属性都保存在了AbstractBeanDefinition中,那么我们再次通过AbstractBeanDefinition的属性来回顾一下我们都解析了哪些对应的配置

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
    implements BeanDefinition, Cloneable {
//此处省略静态变量以及final常亮
  /**
   * bean的作用范围,对应bean属性scope
   */
  private String scope = SCOPE_DEFAULT;
 
  /**
   * 是否是抽象,对应bean属性的abstract
   */
  private boolean abstractFlag = false;
 
  /**
   * 是否是延迟加载,对应bean属性的lazy-init
   */
  private boolean lazyInit = false;
 
  /**
   * 自动注入模式,对应bean属性的autowire
   */
  private int autowireMode = AUTOWIRE_NO;
 
  /**
   * 依赖检查,Spring 3.0后弃用了这个属性
   */
  private int dependencyCheck = DEPENDENCY_CHECK_NONE;
 
  /**
   * 用来表示一个bean的实例化依靠另一个bean先实例化,对应bean属性depend-on
   */
  private String[] dependsOn;
 
  /**
   * autowire-candidate属性设置为false,这样容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,但是该bean本身还是可以使用自动装配来注入其他bean的
   * 对应bean属性autowire-candidate
   */
  private boolean autowireCandidate = true;
 
  /**
   * 自动装配时当出现多个bean候选者时,将作为首选者,对应bean属性primary
   */
  private boolean primary = false;
 
  /**
   * 用于记录Qualifier,对应子元素qualifier
   */
  private final Map<String, AutowireCandidateQualifier> qualifiers =
     new LinkedHashMap<String, AutowireCandidateQualifier>(0);
 
  /**
   * 运行访问非公开的构造器和方法,程序设置
   */
  private boolean nonPublicAccessAllowed = true;
 
  /**
   * 是否以一种宽松的模式解析构造函数,默认为true
   * 如果为false,则在如下情况下
   * interface ITest{}
   * class ITestImpl implements ITest{};
   * class Main{
   *   Main(ITest i){}
   *   Main(ITestImpl i){}  
   * }
   * 抛出异常,因为Spring无法准确定位哪个构造函数
   * 程序设置
   */
  private boolean lenientConstructorResolution = true;
 
  /**
   * 对应bean属性factory-bean,用法:
   * <bean id="instanceFactoryBean" class="example.chapter3.InstanceFactoryBean"/>
   * <bean id="currentTime" factory-bean="instanceFactoryBean" factory-method="createTime">
   */
  private String factoryBeanName;
 
  /**
   * 对应bean属性factory-method
   */
  private String factoryMethodName;
 
  /**
   * 记录构造函数注入属性,对应bean属性constructor-arg
   */
  private ConstructorArgumentValues constructorArgumentValues;
 
  /**
   * 普通属性集合
   */
  private MutablePropertyValues propertyValues;
 
  /**
   * 方法重写的持有者,记录lookup-method、replaced-method
   */
  private MethodOverrides methodOverrides = new MethodOverrides();
 
  /**
   * 初始化方法,对应bean属性init-method
   */
  private String initMethodName;
 
  /**
   * 销毁方法,对应bean属性destory-method
   */
  private String destroyMethodName;
 
  /**
   * 是否执行init-method,程序设置
   */
  private boolean enforceInitMethod = true;
 
  /**
   * 是否执行init-method,程序设置
   */
  private boolean enforceDestroyMethod = true;
 
  /**
   * 是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true,程序设置
   */
  private boolean synthetic = false;
 
  /**
   * 定义这个bean的应用,APPLICATION:用户,INFRASTRUCTURE:完全内部使用,与用户无关,SUPPORT:某些复杂配置的一部分,程序设置
   */
  private int role = BeanDefinition.ROLE_APPLICATION;
 
  /**
   * bean的描述信息
   */
  private String description;
 
  /**
   * 这个bean定义的资源
   */
  private Resource resource;
//此处省略set/get方法
}

1.3、解析默认标签中的自定义标签元素

到此我们已经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,已经忘了从哪个函数开始了,再次回顾一下默认标签解析函数的起始函数:

  protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
     bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
     try {
       // Register the final decorated instance.
       BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
     }
     catch (BeanDefinitionStoreException ex) {
       getReaderContext().error("Failed to register bean definition with name '" +
          bdHolder.getBeanName() + "'", ele, ex);
     }
     // Send registration event.
     getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
  }

前面已经用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)这句代码,下面我们要进行bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder)代码的分析,首先大致了解一下这句代码的作用,其实可以从语义上分析:如果需要的话就对beanDefinition进行装饰,那这句代码到底是什么功能呢?这句代码适用这样的场景:

<bean id="test" class="test.MyTest">
  <mybean:user username="aaa">
</bean>

Spring中的bean使用的是默认标签配置,但是其中的子元素却使用了自定义的配置时,这句代码便会起作用了,对bean的解析分为默认类型的解析和自定义类型的解析,这个自定义类型并不是以Bean的形式出现的,之前讲过的两种类型的不同处理只是针对Bean的,这里我们看到的,这个自定义类型其实是属性,下面我们继续分析代码:

  public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
  }

这里将第三个参数设置为null,那么第三参数是做什么用的?什么情况下不为空呢?其实第三个参数是父类bean,当对某个嵌套配置进行分析时,这里需要传递父类beanDefinition。分析源码得知这里传递的参数其实是为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的是顶层配置,所以传null,将第三参数设置为空后进一步跟踪函数:

  public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
     Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {
 
    BeanDefinitionHolder finalDefinition = definitionHolder;
    NamedNodeMap attributes = ele.getAttributes();
    //遍历所有的属性,看看是否有适合用于修饰的属性
    for (int i = 0; i < attributes.getLength(); i++) {
     Node node = attributes.item(i);
     finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }
    NodeList children = ele.getChildNodes();
    //遍历所有子节点,看看是否有适合用于修饰的子元素
    for (int i = 0; i < children.getLength(); i++) {
     Node node = children.item(i);
     if (node.getNodeType() == Node.ELEMENT_NODE) {
       finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
     }
    }
    return finalDefinition;
  }

上面的代码,函数分别对元素的所有属性以及子节点进行了decorateIfRequired函数的调用,继续看代码:

  public BeanDefinitionHolder decorateIfRequired(
     Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
    //获取自定义标签的命名空间
    String namespaceUri = getNamespaceURI(node);
    //对于非默认标签进行修饰
    if (!isDefaultNamespace(namespaceUri)) {
     //根据命名空间找到对应的处理器
     NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
     //进行修饰
     if (handler != null) {
       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 {
       // A custom namespace, not to be handled by Spring - maybe "xml:...".
       if (logger.isDebugEnabled()) {
         logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
       }
     }
    }
    return originalDef;
  }
 
  public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
  }
 
  public boolean isDefaultNamespace(String namespaceUri) {
    return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
  }

程序走到这里,条理已经非常清楚了,首先获取元素或属性的命名空间以此来判断该元素或属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler并进行进一步解析。在自定义标签章节会重点讲解,这里先略过。
总结一下,decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfRequired中我们可以看到对于程序默认的标签的处理其实是直接略过的,因为默认标签到这里已经被处理完了。这里只对自定义标签或者说bean的自定义属性感兴趣,在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进行进一步解析。

1.4、注册解析的BeanDefinition

对于配置文件,解析也完成了,装饰也完成了。对于得到的BeanDefinition已经可以满足后续的使用要求了,唯一还剩下的工作就是注册了,也就是processBeanDefinition函数中的BeanDefinitioneaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())代码的解析了。

  public static void registerBeanDefinition(
     BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
     throws BeanDefinitionStoreException {
    //使用beanName作为唯一标识注册
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    //注册所有的别名
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
     for (String alias : aliases) {
       registry.registerAlias(beanName, alias);
     }
    }
  }

从上面可以看出,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于beanDefinition的注册分成了两部分:通过beanName的注册以及通过别名的注册

1.4.1、通过beanName注册BeanDefinition

对于BeanDefinition的注册,或许很多人认为的方式就是讲beanDefinition直接放入map中就好了,使用beanName最为key。确实Spring就是这么做的,只不过除此之外,它还做了点别的事情。

  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 {
       /*
        * 注册前的最后一次校验,这里的校验不同于之前的XML文件校验,主要是对于AbstractBeanDefinition属性中的methodOverrides校验         * 。校验methodOverrides是否与工厂方法并存或者methodOverrieds对应的方法根本不存在
        */ 
       ((AbstractBeanDefinition) beanDefinition).validate();
     }
     catch (BeanDefinitionValidationException ex) {
       throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
          "Validation of bean definition failed", ex);
     }
    }
    // 因为beanDefinitionMap是全局变量,这里定会存在并发访问的情况
    synchronized (this.beanDefinitionMap) {
     BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
     if (oldBeanDefinition != null) {
       //如果对应的BeanName已经注册且在配置中配置了bean不允许被覆盖则抛出异常
       if (!this.allowBeanDefinitionOverriding) {
         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()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         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 (this.logger.isInfoEnabled()) {
          this.logger.info("Overriding bean definition for bean '" + beanName +
              "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
         }
       }
     }
     else {
       //记录beanName
       this.beanDefinitionNames.add(beanName);
       this.frozenBeanDefinitionNames = null;
     }
     //注册beanDefinition
     this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    //重置所有beanName对应的缓存
    resetBeanDefinition(beanName);
  }

上面的代码中我们看到,在对于bean的注册处理方式上主要进行了以下几个步骤:
1、对AbstractBeanDefinition的校验,在解析XML文件的时候我们提过校验,但是此校验非彼校验,之前的校验是针对于XML格式的校验,而此时的校验是针对于AbstractBeanDefinition的methodOverrides属性的。
2、对于beanName已经注册的情况的处理,如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖
3、加入map缓存
4、清除解析之前留下的对应beanName的缓存。

1.4.2、通过别名注册BeanDefinition

理解了bean的原理后,理解注册别名的原理就容易多了

  public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    //如果beanName与alias相同的话不记录alias,并删除对应的alias
    if (alias.equals(name)) {
     this.aliasMap.remove(alias);
    }
    else {
     //如果alias不允许被覆盖则抛出异常
     if (!allowAliasOverriding()) {
       String registeredName = this.aliasMap.get(alias);
       if (registeredName != null && !registeredName.equals(name)) {
         throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
            name + "': It is already registered for name '" + registeredName + "'.");
       }
     }
     //当A->B存在时,若再次出现A->C->B时候则会抛出异常
     checkForAliasCircle(name, alias);
     this.aliasMap.put(alias, name);
    }
  }

由以上代码中可以得知注册alias的步骤如下
1、alias与beanName相同情况处理。若alias与beanName名称相同则不需要处理并删除掉原有alias
2、alias覆盖处理,若aliasName已经使用并已经执行另一beanName则需要用户的设置进行处理
3、alias循环检查。当A->B存在时,若再次出现A->C->B时候则会抛出异常
4、注册alias

1.5、通知监听器解析及注册完成

通告代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此工作,这里的实现只为扩展,当程序开发人员需要对注册BeanDefinition时间进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前在Spring中并没有对此事件做任何逻辑处理。

2、alias标签的解析

上面介绍完了默认标签中对bean的处理,那么我们之前提到过,对配置文件的解析包括对import标签、alias标签、bean标签、beans标签的处理,现在已经完成了最重要的功能,其他的解析步骤也都是围绕bean的解析过程而进行的,下面我们来看看对alias的解析
在对bean进行定义时,除了使用id属性来指定名称之外,为了提供多个名称,可以是用alias标签来指定。而所有指定的这些标签都指向同一个bean,在一些情况下提供别名非常有用,比如为了让应用的每一个组件都能够更容易地对公共组件进行引用。
在定义bean时就指定所有别名并不总是恰当的。有时候我们希望在当期那位置为那些在别处定义的bean指定别名,在XML文件中,可以用单独的元素来完成bean别名的定义,如下面的配置文件中定义了一个JavaBean:

  <bean id="testBean" class="com.test"/>

给这个JavaBean增加别名,以方便不同的对象来调用,就可以直接使用bean标签中的name属性:

<bean id="testBean" class="com.test" name="testBean,testBean2"/>

也可以使用另外一种声明方式:

  <bean id="testBean" class="com.test"/>
  <alias name="testBean" alias="testBean,testBean2"/>

一个具体的例子,组件A在XML配置文件中定义了一个名为componentA的dataSource类型的bean,组件B想要在其XML文件中以componentB命名来引用此bean,而且在主程序MyApp的XML配置文件中,希望以myApp来引用此bean,可以通过配置文件中添加下列alias元素来实现

  <alias name="componentA" alias="componentB" />
  <alias name="componentA" alias="myApp" />

这样每个组件及主程序就可以通过唯一名称来引用同一个数据源而互不干扰了。
下面我们来分析下对alias标签的解析过程:

  protected void processAliasRegistration(Element ele) {
    //获取beanName
    String name = ele.getAttribute(NAME_ATTRIBUTE);
    //获取alias
    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
    boolean valid = true;
    if (!StringUtils.hasText(name)) {
     getReaderContext().error("Name must not be empty", ele);
     valid = false;
    }
    if (!StringUtils.hasText(alias)) {
     getReaderContext().error("Alias must not be empty", ele);
     valid = false;
    }
    if (valid) {
     try {
       //注册alias
       getReaderContext().getRegistry().registerAlias(name, alias);
     }
     catch (Exception ex) {
       getReaderContext().error("Failed to register alias '" + alias +
          "' for bean with name '" + name + "'", ele, ex);
     }
     //别名注册后通知监听器做相应处理
     getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
    }
  }

可以看到,跟之前bean中的alias解析大同小异,都是将别名与beanName组成一对注册至registry中。这里就不再赘述。

3、import标签的解析

对于Spring配置文件的编写,经历过大项目的人,都有种恐惧的心理,太多的配置文件,不过分模块是大多数人能想到的方法。
使用import是一个不错的选择,例如我们可以构造这样的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">
  <import resource="customerContext.xml"/>
  <import resource="systemContext.xml"/>
</beans>

applicationContext.xml文件中使用import的方式导入有模块配置文件,后面如果有新模块的加入可以简单的修改下这个文件,简化了配置后期维护的复杂度,并使配置模块化,易于管理,下面我们看看Spring是如何解析import配置文件的

  protected void importBeanDefinitionResource(Element ele) {
    //获取resource属性
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    //如果不存在resource属性则不做任何逻辑处理
    if (!StringUtils.hasText(location)) {
     getReaderContext().error("Resource location must not be empty", ele);
     return;
    }
 
    // 解析系统属性,格式如:"${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
 
    Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
 
    // 判断location是绝对URI还是相对URI
    boolean absoluteLocation = false;
    try {
     absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    }
    catch (URISyntaxException ex) {
     // cannot convert to an URI, considering the location relative
     // unless it is the well-known Spring prefix "classpath*:"
    }
 
    // Absolute or relative?
    // 如果是绝对URI则直接根据地址加载对应的配置文件
    if (absoluteLocation) {
     try {
       int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
       if (logger.isDebugEnabled()) {
         logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
       }
     }
     catch (BeanDefinitionStoreException ex) {
       getReaderContext().error(
          "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
     }
    }
    else {
     // 如果是相对地址,则根据相对地址计算出绝对地址
     try {
       int importCount;
       //Resource存在多个子实现类,如VfsResource、FileSystemResource等
       //而每个resource的createRelative方式实现都不一样,所有这里先使用子类的方式尝试解析
       Resource relativeResource = getReaderContext().getResource().createRelative(location);
       if (relativeResource.exists()) {
         importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
         actualResources.add(relativeResource);
       }
       else {
         //如果解析不成功,则使用默认的解析器ResourcePatternResolver进行解析
         String baseLocation = getReaderContext().getResource().getURL().toString();
         importCount = getReaderContext().getReader().loadBeanDefinitions(
            StringUtils.applyRelativePath(baseLocation, location), actualResources);
       }
       if (logger.isDebugEnabled()) {
         logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
       }
     }
     catch (IOException ex) {
       getReaderContext().error("Failed to resolve current resource location", ele, ex);
     }
     catch (BeanDefinitionStoreException ex) {
       getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
          ele, ex);
     }
    }
    //解析后进行监听器激活处理
    Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
  }

我们总结一下大致流程,在解析import标签时,Spring进行解析的步骤大致如下:
1、获取resource属性所表示的路径
2、解析路径中的系统属性,格式如”${user.dir}”
3、判断location是相对路径还是绝对路径
4、如果是绝对路径则递归调用bean的解析过程,进行另一次解析
5、如果是相对路径则计算出绝对路径进行解析
6、通知监听器解析完成

4、嵌入式beans标签的解析

对于嵌入式的beans标签,非常类似于import标签所提供的功能,使用如下:

<?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="aa" class="test.aa"/>
<beans></beans>
</beans>

对于嵌入式的beans标签来讲,与单独的配置文件并没有太大的差别,无非是递归调用beans的解析过程。相信小伙伴们根据之前的介绍内容已经有能力理解其中的奥秘了

原创文章,作者:Craig,如若转载,请注明出处:https://www.goodlymoon.com/archives/948.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

QR code