Spring3.2 中 Bean 定义之基于 Annotation 和 Java Code 配置方式的源码解析

来源:互联网 发布:spss 23 mac 许可证 编辑:程序博客网 时间:2024/06/05 23:21

基于 Annotation 配置 Bean Definition 的源码解读

本系列文章第一部分分析了 Spring 解析 XML 配置文件中 <bean /> 元素的源码,这是 Spring 最原始的一种配置方式,同时也使 XML 中的节点具有命名空间特性。参考 Spring 相关文档,如果有如下的配置方式: <context:component-scan base-package="com.colorcc.spring.sample"/> 则可知:其一,该元素采用了"http://www.springframework.org/schema/context" 命名空间的配置方式。其二,针对 "com.colorcc.spring.sample" 包里的每个对象,Spring 可能采用基于 Annotation 方式配置和解析对应的对象。说可能是因为还需要在相关 Java 代码中使用如 @Component 及其子 Annotation 注解后才可以。

基于 Annotation 的 Bean 解析入口

如上文的图 2 所示,其右下角最后一个 Loop 顺序图即为 Annotation 配置解析 Bean 的入口,因此其详细步骤可参考上文的图 2 的分析。 XML Context 命名空间 bean 元素解析由上文的清单 6 可知,如果采用"http://www.springframework.org/schema/context" 命名空间,则执行"delegate.parseCustomElement(ele)" 方法进行 Bean 元素解析,其顺序图如图 1 和图 2 所示。

图 1. 基于 Annotation 解析 Bean 的顺序图 – Part1(查看大图)
图 1. 基于 Annotation 解析 Bean 的顺序图 – Part1
图 2. 基于 Annotation 解析 Bean 的顺序图 – Part2(查看大图)
图 2. 基于 Annotation 解析 Bean 的顺序图 – Part2
  1. 如果开发人员在 XML 文件中配置类似 <context:component-scan .../> 元素时,其中 context 标识了对应的命名空间,而 component-scan 则具有占位符的功能,通过该占位符,Spring 框架可以定义对应的解析对象(如 component-scan 对应于 ComponentScanBeanDefinitionParser)进行具体的业务逻辑处理。
  2. 从 图 1分析可知,Spring 框架遇到非默认命名空间定义的节点时,会根据该节点解析对应的命名空间对应的 URI。如果是第一次解析该命名空间,则会根据图 1 描述找到对应的 schema 进行语法检查。同时也会找到处理该命名空间对应的 Handler。如 context 对应的 handler 为 org.springframework.context.config.ContextNamespaceHandler,这是一个字符串,然后通过反射机制创建该字符串对应的 Java 对象。并缓存该对象供以后继续使用。
  3. 步骤 2 得到的 handler 对象即为每个命名空间处理的 handler,也就是完成对 <context .../> 的解析。当 handler 创建后,Spring 框架会调用其 init() 方法对其后面跟随的占位符(如 component-scan 等)创建对应的工具类对象,通常以 Parser 结尾。同时 Spring 框架也会缓存该工具类供以后继续使用。一个例子如清单 1 所示。
清单 1. 通过 Handler 定义后缀处理对象
 public class ContextNamespaceHandler extends NamespaceHandlerSupport {  public void init() {  registerBeanDefinitionParser("property-placeholder",  new PropertyPlaceholderBeanDefinitionParser());  registerBeanDefinitionParser("property-override",  new PropertyOverrideBeanDefinitionParser());  registerBeanDefinitionParser("annotation-config",  new AnnotationConfigBeanDefinitionParser());  //  component-scan 占位符对应 ComponentScanBeanDefinitionParser 对象 registerBeanDefinitionParser("component-scan",  new ComponentScanBeanDefinitionParser());  registerBeanDefinitionParser("load-time-weaver",  new LoadTimeWeaverBeanDefinitionParser());  registerBeanDefinitionParser("spring-configured",  new SpringConfiguredBeanDefinitionParser());  registerBeanDefinitionParser("mbean-export",  new MBeanExportBeanDefinitionParser());  registerBeanDefinitionParser("mbean-server",  new MBeanServerBeanDefinitionParser());  }  }
  1. 通过前述步骤,Spring 得到命名空间对应的 handler 对象集合。这样就可以通过要解析的节点命名空间对应的占位符寻找其 parser 对象,如未找到,则报错返回。根据清单 1 可知,对于 component-scan 占位符,其对应的 parser 对象为 ComponentScanBeanDefinitionParser 实例化后的对象(简称 csbdParser)。同时,delegate 会根据读入的 XML 相关信息,实例化一个 ParserContext 对象用于存储 xml, delegate 等上下文信息。这样,NamespaceHandler 会根据传入的节点元素和 ParserContext 信息,找到解析 XML 配置节点的解析对象 csbdParser,调用其 parse(element, parserContect) 方法进行解析 , 如图 1 的 (1) 处所示。
  2. parse 方法的处理可以简单归结为三个步骤:一、创建 Scanner 工具。 二、使用工具扫描 Java 对象,解析符合规则的 Bean 到对应 Bean Definition。三、对得到的 Bean Definitons进行处理。
  3. csbdParser 根据 ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); 创建一个 Scanner 工具,具体步骤如下:
    • 6.1 根据图 1 的(2)处所示,csbdParser 会 new 出一个 ClassPathBeanDefinitionScanner 对象 scanner,其传入参数为 BeanDefinitionRegistry 对象和 boolean 类型参数 useDefaultFilters,前者即为 BeanFactory 对象,前文已经分析过,用于保存 Spring 解析出来的 Bean Definition对象的容器。后者提供给开发人员一个钩子功能,由开发人员决定是否使用 Spring 框架提供的默认参数寻找满足条件的 bean 并解析,默认情况为 true。清单 2 列出了 Spring 框架定义的默认条件的 filters。从中可知通过 @Component、@javax.annotation.ManagedBean 和 @javax.inject.Named 以及标记了这些 Annotation 的新 Annotation 注解过的 Java 对象即为 Spring 框架通过 Annotation 配置的默认规则。
      清单 2. Annotation 解析 Bean 定义的默认规则
       // 定义默认情况下 filter 规则,凡是被 @Component、@ManagedBean、@Named 及被这些 Annotation  // 注解过的 Annotaion 注解的 Java 文件会被 Spring 框架使用 Annotation 方式处理其 Bean 定义 @SuppressWarnings("unchecked")  protected void registerDefaultFilters() {  // 将 @Component 加入到 filter 中 this.includeFilters.add(new AnnotationTypeFilter(Component.class));  ClassLoader cl =  ClassPathScanningCandidateComponentProvider.class.getClassLoader();  try {  // 将 @javax.annotation.ManagedBean 加入到 filter 中 this.includeFilters.add(new AnnotationTypeFilter(  ((Class<? extends Annotation>)    cl.loadClass("javax.annotation.ManagedBean")), false));  logger.info("JSR-250 'javax.annotation.ManagedBean' found  and supported for component scanning");  }  catch (ClassNotFoundException ex) { //JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.  }  try {  // 将 @javax.inject.Named 加入到 filter 中 this.includeFilters.add(new AnnotationTypeFilter(  ((Class<? extends Annotation>)  cl.loadClass("javax.inject.Named")), false));  logger.info("JSR-330 'javax.inject.Named' annotation found and  supported for component scanning");  }  catch (ClassNotFoundException ex) {  //JSR-330 API not available - simply skip.  }  }
    • 6.2 由类图可知,scanner 继承了 ClassPathScanningCandidateComponentProvider 对象。该对象提供了一些公共属性的设置,如默认的扫描文件 Pattern 为"**/*.class",Pattern Resolver 为 PathMatchingResourcePatternResolverd 等。这里需要特别注意的两个重要的过滤器 includeFilters 和 excludeFilters,Spring 框架在扫描给定 pattern 的对象时,会根据 excludeFilters 过滤掉满足条件的对象,然后才根据 includeFilters 过滤需要解析的对象。因此定义 Scanner 时可以根据这两个过滤器实现开发人员自定义 Scanner 的过滤条件。 Scanner 自己也定义了一些工具类,如 BeanDefinitionDefaults 用于设置由 Annotation 方式解析 Bean 对象得到的Bean Definition的默认属性值;BeanNameGenerator 用于设置默认 beanName 属性等。
    • 6.3 创建了 Scanner 后,Spring 框架会根据一些业务逻辑设置一些属性供以后使用,如图 1 的 (3) 处。
  1. 步骤 6 创建了一个工具类 scanner, 同时 Spring 框架也会得到开发人员配置的 base-package 属性值。这样 csbdParser 会执行 Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); 来扫描 basePackages 里满足条件的 bean 定义,并生成对应的Bean Definition,如图 2 所示。具体分析如下:
    • 7.1 Scanner 首先会定义一个 Set<BeanDefinitionHolder> 容器,用于存放解析每个 bean 对应的 BeanDefinitionHolder 对象。
    • 7.2 传入的 basePackages 如果以",;"形式分隔,则分割 basePackages 字符串为 String 数组,并循环每个元素,执行后面操作。
    • 7.3 如图 2 的 (1) 所示,该循环首先通过 findCandidateComponents 方法根据一定的规则构造 Spring 框架需要加载的 class 文件 pattern,如 basePackage 为 com.colorcc.spring.sample 构造的需要加载的 class 文件 pattern 为 :classpath*:com/colorcc/spring/sample/**/*.class。这样 Spring 框架会将满足条件的 class 文件加载并转换为 Resource 类型对象,返回给 scanner。
    • 7.4 针对得到的每个 Resource 对象,通过 ASM 工具分析其 Annotation 元素,构造 MetadataReader 对象,并将 annotation 信息封装到 AnnotationMetadata 类型的 annotationMetadata 属性中。
    • 7.5 针对上步得到的 MetadataReader 对象,根据 annotationMetadata 属性,如上文 6.2 的描述,如满足 excludeFilters 过滤器条件,则直接返回,不再解析。反之,如满足 includeFilters 过滤器条件(默认情况即为被 6.1 描述的 annotation 标记过),则继续后续步骤。
    • 7.6 使用经过上步过滤后得到的 MetadataReader 对象作为构造函数的参数,创建 ScannedGenericBeanDefinition 对象 sbd。根据类图(如上文图 5 所示),其直接继承 GenericBeanDefinition 对象,因此其具有上一章分析的所有 GenericBeanDefinition 对象的属性值。同时 sdb 义了一个 AnnotationMetadata 属性,保存了 MetadataReader 对象中关于 Annotation 元数据的一些信息。并且 sbd 的 beanClass 属性也是通过 Annotation 元数据得到并设置的。
    • 7.7 得到的 sbd 返回给 scanner,通过 scanner 设置其一些属性如 resource 和 source 等。同时判断该 sbd 的属性若为非接口,非抽象类且子类方法可重写,则加入到一个 Set 集合 candidates 中。反之循环下一个 resource 继续进行 7.4-7.7 的业务处理。
    • 7.8 针对所有的开发人员定义的 package,进行 7.3-7.7 的处理,得到所有满足 Annotation 解析的 bean 元素的结合 candidates。
    • 7.9 循环 7.8 得到的每个 candidate,如果其是 AbstractBeanDefinition 类型,则根据 6.2 定义的 BeanDefinitionDefaults 对象设置 candidate 的一些默认属性,如图 2 的 (3) 处所示。

      如果 candidate 是 AnnotatedBeanDefinition 类型,则得到其 Annotation 元数据,并根据该元数据中的 annotation 设置 candidate 的一些属性,如被 @Primary 标识,则设置 candidate 的 primary 属性为 true,其他 annotation 还包括 @Lazy、@DependsOn 和 @Role 等。

    • 7.10 查询 registory 的 beanDefinitionMap 属性容器,如果其不包含 candidate 的 beanName 元素,则通过 candidate 及其 beanName 构造一个 BeanDefinitionHolder 对象,将该对象加入到 7.1 创建都容器中。
    • 7.11 最后一步就是将得到的 beanDefinitionHolder 解析并注册到 registry 中。该步骤与上文图 4 黄色背景的的顺序图片段(sd Registry Bean Definition)完全一致。具体分析可参考前文。
  2. csbdParser 最后一步需要完成的操作是将得到的每个 BeanDefinitionHolder 通过 registerComponents(parserContext.getReaderContext(), beanDefinitions, element); 做一些收尾的处理,如图 1 的右下角部分所示。具体分析如下:
    • 8.1 框架根据 BeanDefinitionHolder 的 BeanDefinition 属性构造一个 BeanComponentDefinition 对象,该对象继承 BeanDefinitionHolder,定义了两个私有属性 innerBeanDefinitions 和 beanReferences ,用于存放每个 bean 元素解析时所引用的嵌套 bean 和引用 bean 对象。
    • 8.2 根据 XML 配置的 element 信息,定义一个容器 CompositeComponentDefinition 用于存放每个 BeanComponentDefinition 对象。
    • 8.3 默认情况下,将 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor 等通用 processor 也解析成对应的 BeanDefinitionHolder 并加入到 8.2 创建的 CompositeComponentDefinition 容器中。
    • 8.4 类似前文,Spring 框架发送一个 ReaderEventListener 事件。默认情况下,该事件为 EmptyReaderEventListener 对象的 componentRegistered 事件,这是一个空事件,没有具体的业务逻辑。

基于 Java Code 配置 Bean Definition 的源码解读

前文分析了基于 XML 和 Annotation 配置 Beam Definition的源码实现。从 Spring3.0 开始,增加了一种新的途经来配置Bean Definition,这就是通过 Java Code 配置 Bean Definition。与前两种配置方式不同点在于:

前两种配置方式为预定义方式,即开发人员通过 XML 文件或者 Annotation 预定义配置 bean 的各种属性后,启动 Spring 容器,Spring 容器会首先解析这些配置属性,生成对应都Bean Definition,装入到 DefaultListableBeanFactory 对象的属性容器中去。与此同时,Spring 框架也会定义一些内部使用的 Bean 定义,如 bean 名为”org.springframework.context.annotation.internalConfigurationAnnotationProcessor”的 ConfigurationClassPostProcessor 定义。

而后者此刻不会做任何 Bean Definition 的定义解析动作,Spring 框架会根据前两种配置,过滤出 BeanDefinitionRegistryPostProcessor 类型的 Bean 定义,并通过 Spring 框架生成其对应的 Bean 对象(如 ConfigurationClassPostProcessor 实例)。结合 Spring 上下文源码可知这个对象是一个 processor 类型工具类,Spring 容器会在实例化开发人员所定义的 Bean 前先调用该 processor 的 postProcessBeanDefinitionRegistry(...) 方法。此处实现基于 Java Code 配置 Bean Definition的处理。

由上文图 5 可知,Spring 框架定义了一系列Bean Definition对象,通过 XML 配置方式定义的 Bean 属性,经 Spring 框架解析后会封装成 GenericBeanDefinition 对象,然后注册到 DefaultListableBeanFactory 对象的属性容器中去。通过 Annotation 配置方式定义的 Bean 属性经 Spring 框架解析后会封装成 ScannedGenericBeanDefinition 对象(即 GenericBeanDefinition 对象增加了 AnnotationMetadata 属性),然后注册到 DefaultListableBeanFactory 对象的属性容器中去。RootBeanDefinition 类类似 GenericBeanDefinition,都是 AbstractBeanDefinition 的实现类,其额外定义了一些属性如 externallyManagedConfigMembers,externallyManagedInitMethods 以及 constructorArgumentLock 等,供 Spring 框架将Bean Definition实例化为 Java 对象时使用。通过 Java Code 配置的 Bean Definition经 Spring 框架解析后会封装成 ConfigurationClassBeanDefinition 对象(RootBeanDefinition 对象增加了 AnnotationMetadata 属性), 然后注册到 DefaultListableBeanFactory 对象的属性容器中去。

图 3. 基于 Java Code 解析 Bean 的顺序图(查看大图)
图 3. 基于 Java Code 解析 Bean 的顺序图

图 3 展示了基于 Java Code 解析 Bean 的顺序图。简单分析如下:

  1. 根据前文分析,该顺序图的入口为 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry reigistry) 方法。
  2. 步骤 1 首先注册一个工具类 ImportAwareBeanPostProcessor 的 Bean 定义到 reigsitry 中去。然后处理 resistry 已有的Bean Definition。
  3. 至图 3 的步骤 (1) 处,Spring 框架顺序扫描每个Bean Definition,如果被扫描的 Bean Definition 被 @Commponent 及其子注解或者 @ConfigureClass 注解过,则加入到类型为 Set<BeanDefinitionHolder> 的 configCandidates 容器中。如果容器为空,则表示不存在通过 Java Code 配置的 Bean 定义,直接返回。否则,执行步骤 4。
  4. Spring 框架根据 registry 及自定义的一系列属性如 environment、resourceLoader 等,创建 Bean 解析的工具类 ConfigurationClassParser 对象 parser,然后使用该工具类解析 configCandidates 容器中每个Bean Definition,如图 3 的 (2) 处所示。该处扫描Bean Definition,将所有被 @Bean 注解过的 method 方法及所属 class 信息封装成 BeanMethod 对象,并装入 class 的属性容器中。最终将处理过的 class 对象装入 parser 容器中。
  5. Spring 框架根据 registry 及子定义的一系列属性创建工具类 ConfigurationClassBeanDefinitionReader 对象 reader, 该工具类负责解析 parser 容器中每个 class 的 bean 信息,如图 3 的 (3) 处所示。

    步骤 4 的 parser 和步骤 5 的 reader 区别在于:parser 负责解析被特定 annotation 注解过的 class 对象的被注解 @Bean 注解过的 method 封装为 BeanMethod 对象。而 reader 负责将每个 BeanMethod 对象解析为 Bean Definition。

  6. 有了前述知识,图 3 的 (3) 处解析将非常简单。首先通过 parser 找到步骤 4 解析的每个 class 对象,然后以该对象为参数,调用 reader 的 loadBeanDefinitions(class) 方法,如图 3 的 (4) 处。
  7. 步骤 6 的方法会循环其每个被 @Bean 注解过的方法对象 BeanMethod,针对每个 BeanMethod,首先会以 class 为参数,创建一个 ConfigurationClassBeanDefinition 对象 ccbd(这是一个带有 AnnotationMeta 元数据的 RootBeanDefinition)code type="inline">,设置一些自身属性。然后将方法名设置为 ccbd 的一个 factory name,再根据 BeanMethod 的 annotation 信息如 autowired、initMethod 以及 destroyMethod 等设置 ccbd 的相关属性值。
  8. 类似前述步骤,将得到的 ccbd 注册到 rigistry 的属性容器中去。该步骤与上文图 4 黄色背景的的顺序图片段(sd Registry Bean Definition)完全一致。具体分析可参考前文。

小结

本文详细分析了 Spring 框架解析开发人员定义的 Bean 到 Spring 容器的 Bean Definition对象的处理过程。

基于 XML 配置方式是最原始也是使用最普遍的一种方法,其优点在于将配置集中在一起(XML 配置文件中),且与 Java 代码分离,方便管理。对于配置文件的改变,不需要重新编译源代码,极大的提高了开发效率。其缺点在于对大型基于 Spring 配置的项目,冗余的 XML 配置较多,增加了开发的工作量和维护成本。

基于 Annotation 配置方式是随着 JDK 对 Annotation 支持新引入的一种配置方法,其优点在于可以减少开发人员的工作量,保证代码的整洁性。而其缺点在于与 Java 代码结合,同时配置信息散入到每个 Java 文件中,对新加入项目的开发人员,需要一定的学习成本,另外,基于 Annotation 不能实现所有基于 XML 配置的元数据信息。

基于 Java Code 的配置方式,其执行原理不同于前两种。它是在 Spring 框架已经解析了基于 XML 和 Annotation 配置后,通过加入 BeanDefinitionRegistryPostProcessor 类型的 processor 来处理配置信息,让开发人员通过 Java 编程方式定义一个 Java 对象。其优点在于可以将配置信息集中在一定数量的 Java 对象中,同时通过 Java 编程方式,比基于 Annotation 方式具有更高的灵活性。并且该配置方式给开发人员提供了一种非常好的范例来增加用户自定义的解析工具类。其主要缺点在于与 Java 代码结合紧密,配置信息的改变需要重新编译 Java 代码,另外这是一种新引入的解析方式,需要一定的学习成本。

原创粉丝点击