Spring Core Container 源码分析七:注册 Bean Definitions

前言

本来觉得,Spring 经过解析 bean 的配置,生成并注册 bean defintions 的过程不太复杂,比较简单,不用单独开辟一篇博文来说述;可是当在分析前面两个章节有关 @Autowired、@Component、@Service 注解的注入机制的时候,发现,若是没有对有关 bean defintions 的解析和注册机制完全弄明白,则很难弄清楚 annotation 在 Spring 容器中的底层运行机制;因此,本篇博文做者将试图去弄清楚 Spring 容器内部是如何去解析 bean 配置并生成和注册 bean definitions 的相关主流程;java

备注,本文是做者的原创做品,转载请注明出处。node

bean definition 是什么?

➥ bean definitions 是什么?spring

其实很简单,就是 Java 中的 POJO,用来描述 bean 配置中的 element 元素的,好比,咱们有以下的一个简单的配置编程

beans.xml缓存

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
   
   <context:component-scan base-package="org.shangyang" />  
   
   <bean name="jane" class="org.shangyang.spring.container.Person">
       <property name="name" value="Jane Doe"/>
   </bean>
   
</beans>

能够看到,上面有三个 elementapp

  1. _<beans/>_, root element
  2. _<context:component/>_, component-scan element
  3. _<bean/>_, bean element

在配置文件 beans.xml 被 Spring 解析的过程当中,每个 element 将会被解析为一个 bean definition 对象缓存在 Spring 容器中;ide

➥ 须要被描述为 bean definitions 的配置对象主要分为以下几大类,源码分析

  1. xml-based,解析 xml beans 的状况;
  2. 使用 @Autowired、@Required 注解注入 beans 的解析状况;
    须要特殊处理并解析的元素 <context:annotation-config/>
  3. 使用 @Component、@Service、@Repository,@Beans 注解注入 beans 的解析状况;
    须要特殊处理并解析的元素 <context:annotation-scan/>

➥ 最开始个人确是这么认识 bean definitions 的,可是当我分析完有关 bean definitions 的相关逻辑和源码之后,对其认识有了升华,参考写在最后post

源码分析

最好的分析源码的方式,就是经过高屋建瓴,逐个击破的方式;首先经过流程图得到它的蓝图(顶层设计图),而后再根据蓝图上的点逐个击破;最后才能达到融会贯通,成竹在胸的境界;因此,这里做者用这样的方式带你深刻剖析 Spring 容器里面的核心点,以及相关主流程究竟是如何运做的。测试

测试用例

为了一次性把上述源码分析所描述有的状况阐述清楚,咱们继续使用 Spring Core Container 源码分析六:@Service 中使用的测试用例;惟一作的修改是,再使用一个特殊的 element xmlns:p 来配置 john,这样能够进一步去调试自定义 Spring 配置标签是如何实现的;

beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
   
   <context:component-scan base-package="org.shangyang" />  
   
    <bean name="john"
          class="org.shangyang.spring.container.Person"
          p:name="John Doe"
          p:spouse-ref="jane"/>
    
    <bean name="jane" class="org.shangyang.spring.container.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
   
   <bean name="niba" class="org.shangyang.spring.container.Dog">
      <property name="name" value="Niba" />
   </bean>
       
</beans>

流程分析

整个流程是从解析 bean definitions 流程开始的,对应的入口是主流程的 _step 1.1.1.2 obtainFreshBeanFactory_;

入口流程

  1. 首选初始化获得 BeanFactory 实例 DefaultListableBeanFactory,用来注册解析配置后生成的 bean definitions;
  2. 而后经过 XmlBeanDefinitionReader 解析 Spring XML 配置文件
    根据用户指定的 XML 文件路径 location,进行解析而且获得 Resource[] 对象,具体参考 step 1.1.3.3.1.1 getResource(location) 步骤;这里,对其如何经过 location 获得 Resource[] 对象作进一步分析,看源码,

    PathMatchingResourcePatternResolver.java

    public Resource[] getResources(String locationPattern) throws IOException {
       Assert.notNull(locationPattern, "Location pattern must not be null");
       if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
          // a class path resource (multiple resources for same name possible)
          if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
             // a class path resource pattern
             return findPathMatchingResources(locationPattern);
          }
          else {
             // all class path resources with the given name
             return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
          }
       }
       else {
          // Only look for a pattern after a prefix here
          // (to not get fooled by a pattern symbol in a strange prefix).
          int prefixEnd = locationPattern.indexOf(":") + 1;
          if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
             // a file pattern
             return findPathMatchingResources(locationPattern);
          }
          else {
             // a single resource with the given name
             return new Resource[] {getResourceLoader().getResource(locationPattern)};
          }
       }
    }

    这里的解析过程主要分为两种状况进行解析,一种是前缀是 classpath: 的状况,一种是普通的状况,正如咱们当前所使用的测试用例的状况,既是 new ClassPathXmlApplicationContext("beans.xml") 的状况,这里不打算在这里继续深挖;

  3. 以测试用例 beans.xml 为例,从 #2 解析获得的 Resource 实例 resource 对应的是 beans.xml 的配置信息,从 step 1.1.3.3.1.2 loadBeanDefinitions 开始将会对 resource 既是 beans.xml 中的元素依次进行解析;首先生成对应 beans.xml 的 org.w3c.Document 对象实例 document_,见 _step 1.1.3.3.1.2.2.1_,其次获得解析 _document 对象的 BeanDefinitionDocumentReader 实例 documentReader_,将当前的 Resource 对象封装为 XmlReaderContext 实例 _xmlReaderContext_,最后经过 _documentReader 开始正式解析 document 对象获得 bean definitions 并将其注册到当前的 beanFactory 实例中,该步骤见 step 1.1.3.3.1.2.2.2.3

当完成上述三个步骤之后,将进入 register bean definitions process 流程

register bean definitions process

➥ 首先,重要的两件事情是,

  • document 对象中得到了 Root 实例 root_,见 _step 1.2
    看一个 root 元素,长什么样的

    <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:p="http://www.springframework.org/schema/p"
         xmlns:context="http://www.springframework.org/schema/context"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd">
     </beans>

    就是一个 xml 配置文件中的最顶层元素 <beans/>

  • 而后初始化获得 documentReader 实例的解析对象既 this.delegate<BeanDefinitionParserDelegate>_,后面针对 _element 元素的解析将会使用到它;

➥ 后续,当前面的工做准备好了之后,来看看是如何解析 element 的?

首先,判断 root 元素的 namespace 对应的是否是 default namespace,若不是,将进入 _step 1.3.3.3: parse custom element_;这里咱们关注常规流程,既是当 root 元素的 namespace 是 default namespace 的流程;

遍历 root 元素下的全部 element,

  1. 若 element 的 namespace 是 default namespace,将进入 parse default element 流程
    好比当前 element 是普通的 <bean/>
  2. 若 element 的 namespace 不是 default namespace,将进入 parse custom element 流程
    好比当前 element 是 <context:annotation-config/> 或者是 <context:component-scan/>

parse default element process

能够看到,该流程中包含四个子流程,依次处理不一样的 element 元素的状况,其它三种都是比较特殊的状况,咱们这里,主要关注“解析 <bean/>" 元素的流程

解析 bean element 流程

这里,为了可以尽可能的展现出解析 <bean/> 元素的流程中的逻辑,我将使用一个比较特殊的 <bean/> 来梳理此部分的流程;

<bean name="john"
          class="com.example.Person"
          p:name="John Doe"
          p:spouse-ref="jane"/>

<bean/> 元素使用了 namespace xmlns:p="http://www.springframework.org/schema/p"

➥ 首先,经过 BeanDefintionParserDelegate 对象解析该 element,获得一个 BeanDefinitionHolder 对象 bdHolder 实例;该解析过程当中会依次去解析 bean id, bean name, 以及相关的 scope, init, autowired model 等等属性;见 step 1.1

➥ 其次,对 bean definition 进行相关的修饰操做,见 step 1.2

常规步骤

  1. 遍历当前 element 中的全部 attributes,依次获得 atttribute node
  2. 取得 node 所对应的 namespace URI,并判断该 namespace 是不是 custom namespace,若是是 custom namespace,那么正式进入对该 attribute node 的修饰过程,以下所述;

attribute node 的修饰过程

假设,咱们当前的 attribute node 为 _p:spouse-ref="jane"_,看看该属性是如何被解析的,

  1. 首先,经过 node namespace 获得对应的 NamespaceHandler 实例 handler
    经过 xmlns:p="http://www.springframework.org/schema/p" 获得的 NamespaceHandler 为 SimplePropertyNamespaceHandler 对象;
  2. 其次,调用 SimplePropertyNamespaceHandler 对象对当前的元素进行解析;
    能够看到,前面的解析并无什么特殊的,从元素 p:spouse-ref="jane" 中解析获得 propery name: _spouse-ref_,property value: _jane_;可是后续解析,比较特殊,须要处理 REF_SUFFIX 的状况了,也就是当 property name 的后缀为 -ref 的状况,表示该 attribute 是一个 ref-bean 属性,其属性值引用的是其它的 bean 实例,因此呢,这里将其 property value 封装为了一个 RuntimeBeanReference 对象实例,表示未来在解析该 property value 为 Java Object 的时候,须要去初始化其引用的 bean 实例 _jane_,而后注入到当前的 property value 中;
  3. 最后,将解析后获得的 bean definition 封装在 bean definition holder 对象中进行返回;

➥ 最后,注册 bean definition;

step 1.3.2 register.registerBeanDefinition(beanName, beanDefinition)_,_register 就是当前的 bean factory 实例,经过将 bean namebean definition 以键值对的方式在当前的 bean factory 中进行注册;这样,咱们就能够经过 bean 的名字,获得其对应的 bean definition 对象了;

➥ 写在该小节最后,

咱们也能够自定义某个 element 或者 element attribute,而且定义与之相关的 namespace 和 namespace handler,这样,就可使得 Spring 容器解析自定义的元素;相似于 dubbo 配置中所使用的 <dubbo /> 自定义元素那样;

parse custom element process

此步骤对应 register bean definitions process 步骤中的 step 1.3.3.2

该小节我将试图使用一个经常使用的 custom element: <context:component-scan/> 来梳理整个流程;

  1. 首先获得与 <context:component-scan /> 元素相关的 namespace uri: http://www.springframework.or...,见 _step 1.1
  2. 经过 #1 获得的 namespace uri 解析获得相应的 NamespaceHandler,这里获得的是 ContextNamespaceHandler_;见 _step 1.2
    step 1.2.1 getHandlerMappings() 返回了全部内置的 namespace uri 与 namespace handler 所一一对应的键值对;
  3. 使用 #2 返回的 NamespaceHandler 既 ContextNamespaceHandler 进行 parse 操做,见 _step 1.3_,参考子流程 parse element by ContextNamespaceHandler,注意,之因此这里单独使用一个子流程来介绍,是由于使用 ContextNamespaceHandler 来解析只是其中的一种解析状况,未来考虑分析更多的子流程状况;
parse element by ContextNamespaceHandler

继续 parse custom element process 章节中所使用到的例子,<context:component-scan/> 来分析该流程,

➥ 在开始分析以前,看看 component-scan 元素长什么样,

注意,_component-scan_ element 自己包含 annotation-config attribute;

➥ 流程分析

首先,根据 element name: component-scan 找到对应的 BeanDefinitionParser,在 ContextNamespaceHandler 初始化的时候,便初始化设置好 8 对内置的 element nameparsers 的键值对;这里,根据名字 component-scan 找到对应的 parser ComponentScanBeanDefinitionParser 对象;

其次,使用 ComponentScanBeanDefinitionParser 对象开始解析工做,

  1. 首先,解析 <context:component-scan base-package="org.shangyang"/> 获得 basePcakges String[] 对象;
  2. 其次,初始化获得 ClassPathBeanDefinitiionScanner 对象实例 scanner_,而后调用 _scanner.doScan 方法进入 do scan 流程,该流程中将会遍历 base package 中所包含的全部 .class 文件,解析之,并生成相应的 bean definitions;另外在这个流程中,还要注意的是,最后会将 bean definitions 在当前的 bean factory 对象中进行注册;
  3. 最后,这一步是从 step 1.2.4 开始,主要处理的逻辑为,当 element 含有 annotation-config 属性的时候,将会注册一系列的 post-processors-bean-definitions
do scan 流程

这里主要介绍上一个小节中 #2 步骤中所提到的 do scan 流程步骤,对应 parse element by ContextNamespaceHandler 流程图中的 _step 1.2.3 scanner.doScan_;

➥ 先来看看 step 1.2.3.1 findCandidateComponent(basePackage)

ClassPathScanningCandidateComponentProvider.java (已删除大量不相干代码)

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      
      //1. 从当前用户自定的 classpath 子路径中,经过 regex 查询到全部的所匹配的 resources;要特别注意的是,
      //   这里为何不直接经过 Class Loader 去获取 classes 来进行判断? 由于这样的话就至关因而加载了 Class Type,而 Class Type 的加载过程是经过 Spring 容器严格控制的,是不容许随随便便加载的
      //   因此,取而代之,使用一个 File Resource 去读取相关的字节码,从字节码中去解析........
      Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); 
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();
      
      //2. 依次遍历用户定义的 bean Class 对象
      for (Resource resource : resources) {
         if (traceEnabled) {
            logger.trace("Scanning " + resource);
         }
         
         if (resource.isReadable()) {
            try {
               // 将从字节码中获取到的相关 annotation(@Service) 以及 FileSystemResource 对象保存在 metadataReader 当中; 
               MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); 
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     candidates.add(sbd);
                  }
                  ...
               }
               ...
            }
            ...
         }
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}
  1. 代码第 10 行

    Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

    这一步经过递归搜索 base package 目录下的全部 .class 文件,并将其字节码封装成 Resource[] 对象;上面的注释解释得很是清楚了,这里封装的是 .class 文件的字节码,而非 class type;除了注解中所描述的,这里再引伸说明下,这里为何不直接加载其 Class Type 还有一个缘由就是当 Spring 在加载 Class Type 的时候,颇有可能在该 Class Type 上配置了 AOP,经过 ASM 字节码技术去修改原有的字节码之后,再加入 Class Loader 中;因此,之类不能直接去解析 Class Type,而只能经过字节码的方式去解析;

    这一步一样告诫咱们,在使用 Spring 容器来开发应用的时候,开发者不要随随便便的自行加载 Class Type 到容器中,由于有可能在加载 Class Type 以前须要经过 Spring 容器的 ASM AOP 进行字节码的修改之后再加载;

  2. 代码第 23 行

    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

    解析当前的 .class 字节码,解析出对应的 annotation,好比 @Service,并将其协同 FileSystemResource 对象一同保存到 metadataReader 对象中;

  3. 代码第 24 行

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
       for (TypeFilter tf : this.excludeFilters) {
          if (tf.match(metadataReader, this.metadataReaderFactory)) {
             return false;
          }
       }
       for (TypeFilter tf : this.includeFilters) { // includedFilters 包含三类 annotation,1. @Component 2. @ManagedBean 3. @Named
          if (tf.match(metadataReader, this.metadataReaderFactory)) {
             return isConditionMatch(metadataReader);
          }
       }
       return false;
    }

    既是从当前的 metadataReader 中去判断是否存在 1. @Component 2. @ManagedBean 3. @Named 三种注解中的一种,若是是,则进入下面的流程

  4. 代码 25 - 29 行,将符合 #3 标准的 annotation 封装为 ScannedGenericBeanDefinition annotation-bean-definition,并加入 candidates 返回

    if (isCandidateComponent(metadataReader)) {
       ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
       sbd.setResource(resource);
       sbd.setSource(resource);
       if (isCandidateComponent(sbd)) {
          candidates.add(sbd);
       }
       ...
    }

➥ 依次处理并注册返回的 candidates

该步骤从流程图 parse element by ContextNamespaceHandler 中的 step 1.2.3.2 开始,主要作了以下几件事情,

  1. 设置 candiate (既 annotation bean definition) 的 scope
  2. 经过 AnnotationBeanNameGenerator 生成 bean name,由于经过 @Component、@Service 注解的方式注入的 bean 每每没有配置 bean name,因此每每须要经过程序的方式自行生成相应的 bean name,看看内部的源码,如何生成 bean name 的,

    /**
*/

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {

if (definition instanceof AnnotatedBeanDefinition) {
     String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); // 处理诸如 @Service("dogService") 的状况
     if (StringUtils.hasText(beanName)) {
        // Explicit bean name found.
        return beanName;
     }
  }
  // Fallback: generate a unique default bean name. 里面的实现逻辑就是经过将 Class Name 的首字母大写编程小写,而后返回;
  return buildDefaultBeanName(definition, registry);

}

一般状况下,是将类名的首字母进行小写并返回;对应 _step 1.2.2.3.3_
3. 设置 annotation bean definition 的默认值,参考 _step 1.2.4_
4. 设置 scoped proxy 到当前的 annotation bean definition
5. 最后,将 annotation bean definition 注册到当前的 bean factory

###### 注册 post-processor-bean-definitions

该步骤从流程图 [parse element by ContextNamespaceHandler](#parse-element-by-ContextNamespaceHandler) 的 _step 1.2.4.2 registerAnnotationConfigProcessors_ 开始,将会依次注册由以下 post-processor class 对象所对应的 post-processor-bean-definitions,

+ ConfigurationClassPostProcessor.class
+ AutowiredAnnotationBeanPostProcessor.class
+ RequiredAnnotationBeanPostProcessor.class
+ CommonAnnotationBeanPostProcessor.class
+ 经过 PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME 发射获得的 class
+ EventListenerMethodProcessor.class
+ DefaultEventListenerFactory.class

注意,这里都是经过 Class 对象注册的,并不是注册的实例化对象,下面,咱们来简单分析一下注册相关的源码,以注册 _AutowiredAnnotationBeanPostProcessor_ post-processor-bean-definition 为例子,

_AnnotationConfigUtils#registerAnnotationConfigProcessors_

if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
// 将 AutowiredAnnotationBeanPostProcessor.class 封装为 bean definition
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}

上面的步骤将 AutowiredAnnotationBeanPostProcessor.class 封装为 bean definition;

_AnnotationConfigUtils.registerPostProcessor_

private static BeanDefinitionHolder registerPostProcessor(

BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {

definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition); // 注册 bean definition
return new BeanDefinitionHolder(definition, beanName);
}

这一步将 _AutowiredAnnotationBeanPostProcessor_ 所对应的 bean definition 注入了当前的 bean factory 当中;

_AutowiredAnnotationBeanPostProcessor_ 提供了 @Autowired 注解注入机制的实现,详情参考 [AutowiredAnnotationBeanPostProcessor](/2017/04/05/spring-core-container-sourcecode-analysis-annotation-autowired/#AutowiredAnnotationBeanPostProcessor) 章节;
## 写在最后

经过上述的分析,能够清晰的看到,bean definition 的做用是什么,就是经过 bean definition 中的描述去限定经过 Class Type 实例化获得 instance 的业务规则,咱们看看由 [do scan 流程](#do-scan-流程) 所生成的 annotation-bean-definition<ScannedGenericBeanDefinition> 对象,

{% asset_img debug-scanned-generic-bean-definition.png %}

能够看到,当咱们在后续要根据该 annotation-bean-definition 获得一个 DogService 实例的时候,所要遵循的业务规则,以下所示,

Generic bean: class [org.shangyang.spring.container.DogService];
scope=;
abstract=false;
lazyInit=false;
autowireMode=0;
dependencyCheck=0;
autowireCandidate=true;
primary=false;
factoryBeanName=null;
factoryMethodName=null;
initMethodName=null;
destroyMethodName=null;
defined in file [/Users/mac/workspace/spring/framework/sourcecode-analysis/spring-core-container/spring-sourcecode-test/target/classes/org/shangyang/spring/container/DogService.class]

不过,要注意,这里所获得的 ScannedGenericBeanDefinition 实例,一样没有真正去加载 _org.shangyang.spring.container.DogService_ Class Type 到容器中,而只是将 class name `字符串`赋值给了 ScannedGenericBeanDefinition.beanClass,言外之意,未来在加载 Class Type 到容器中的时候,或许与实例化 instance 同样也要根据 bean definitions 中的规则来限定其加载行为,目前我所可以想到的与其相关的就是 ASM 字节码技术,能够在 bean definition 中定义 ASM 字节码修改规则,来控制相关 Class Type 的加载行为;

# References
相关文章
相关标签/搜索