什么是bean定义信息?

bean定义信息是bean在Spring中的描述,也就是BeanDefinition,里面存放bean元数据,比如Bean类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载等一些列信息有了BeanDefinition,Spring容器可以根据BeanDefinition获取到Class属性进行反射创建Bean;

 

bean定义信息加载会有读取配置类,扫描配置类下注入的Bean和指定的类路径,注册BeanDefinition,存储的BeanDefinition会以beanDefinitionMap的形式放在BeanFactory的实现类,让其根据创建BeanDefinition创建对象;

  • 读取配置类

  BeanDefinitionReader接口: 既可以使用BeanDefinitionRegistry构造,也可以通过loadBeanDefinitions把配置加载为多个BeanDefinition并注册到BeanDefinitionRegistry中;

  实现类如下:

  Spring BeanDefinition加载流程分析

     XmlBeanDefinitionReader从XML配置中读取BeanDefinition;

  PropertiesBeanDefinitionReader从Properties文件读取BeanDefinition;

  AnnotatedBeanDefinitionReader对带有@Configuration注解的BeanDefinition进行注册;

 

  • 扫描配置类,如@Configuration以外的注解(@PropertySource,@ComponentScan,@Import,@ImportResource,@Bean methods),接口的默认方法,superclass

  ClassPathBeanDefinitionScanner类: 可以扫描到@Component @Repository @Service @Controller 的BeanDefinition注册到容器中;

 

  • 注册BeanDefinition

  BeanDefinitionRegistry接口:具有增,查,删BeanDefinition的能力;一次只能注册一个BeanDefinition; 

  实现类SimpleBeanDefinitionRegistry,DefaultListableBeanFactory,GenericApplicationContext等 一般实现类里 都都有一个beanDefinitionMap来存储 BeanDefinition;

  Spring BeanDefinition加载流程分析

 

在bean定义信息加载过程中会涉及BeanFactoryPostProcessorBeanPostProcessor 这两个后置处理器;

 

前面说到BeanFactoryPostProcessorBeanPostProcessor不一样BeanFactoryPostProcessorBeanFactory的后置处理器,它为Spring提供一种的容器扩展机制,该机制类似一种钩子函数它允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改;关于BeanFactoryPostProcessor的注释如下;

Spring BeanDefinition加载流程分析

注:BeanFactory访问容器顶层的接口,它只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作; 

 

Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,BeanDefinition接口定义了获取/设置bean的定义信息的方法,bean的配置信息包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等而在容器中每一个对象都会有一个BeanDefinition的实例与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息RootBeanDefinition和GenericBeanDefinition为主要的实现类(ChildBeanDefinition类从Spring2.5开始,ChildBeanDefinition已经不再使用,取而代之的是GenericBeanDefinition);关于BeanDefinition 的注释如下;

Spring BeanDefinition加载流程分析

 

BeanDefinition继承树如下

  Spring BeanDefinition加载流程分析  

  • AbstractBeanDefinition类:抽象类统一实现了BeanDefinition定义的一部分操作,可以说是定义了BeanDefinition很多默认的属性;正是在AbstractBeanDefinition基础上,Spring衍生出了一系列BeanDefinition;
  • RootBeanDefinition类:表明它是一个可合并的BeanDefinition,在Spring beanFactory运行期间,可以返回一个特定的bean
  • ChildBeanDefinition类:从Spring2.5开始,ChildBeanDefinition已经不再使用,取而代之的是GenericBeanDefinition
  • GenericBeanDefinition类: 从Spring2.5开始,注册beandefintion首选的是GenericBeanDefinitionGenericBeanDefinition允许动态的设置父bean;GenericBeanDefinition可以作为RootBeanDefinition与ChildBeanDefinition的替代品;

  Spring BeanDefinition加载流程分析

  • AnnotatedBeanDefinition接口:表示注解类型BeanDefinition;有两个重要的属性,AnnotationMetadata,MethodMetadata分别表示BeanDefinition的注解元信息和方法元信息实现了此接口的BeanDefinition可以获取到注解元数据和方法元数据;
  • AnnotatedGenericBeanDefinition类:表示带注解修饰的注册组件的BeanDefinition类,如@Configuration注解注释的BeanDefinition类;
  • ScannedGenericBeanDefinition类:表示@Component、@Service、@Controller等注解注释的BeanDefinition类;

 

BeanDefinitionRegistryPostProcessorbean定义信息注册器的后置器它的执行时机为所有的bean定义信息(BeanDefinition)将要被加载到容器的时候,但此时Bean实例还没有被实例化;它与BeanFactoryPostProcessor区别在于,BeanFactoryPostProcessor执行是在所有的bean定义信息已经加载到容器的时候,因此BeanDefinitionRegistryPostProcessor的执行优先于BeanFactoryPostProcessor的执行,它们的相同点是执行时bean实例都没被实例化

如要对配置文件进行加解密,这个可以在bean定义信息已加载完成,但bean未实例化前进行处理,这个可使用BeanFactoryPostProcessor处理(如jasypt-spring-boot-starter就是类似这种处理的);

注:BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法;

 

org.springframework.context.support.AbstractApplicationContext#refresh用于容器创建和刷新,IOC容器的一切从这里开始

 

下面Debug以AnnotationConfigApplicationContext解析配置类作为分析BeanDefinition加载,而用于加载Xml类型的配置文件的ClassPathXmlApplicationContextBeanDefinition加载是不一样的;

ClassPathXmlApplicationContext是在AbstractApplicationContext#obtainFreshBeanFactory进行BeanDefinition的解析加载,最终会调用AbstractRefreshableApplicationContext#refreshBeanFactory加载BeanDefinition;

 

加载bean定义信息流程如下

  • 添加测试代码

   配置类

@ComponentScan(basePackages = {"org.example.spring.basic.beans"})
@Configuration
public class BeanFactoryConfig {
	@Bean
	public Person person() {
		return new Person();
	}
}

  

  在org.example.spring.basic.beans包下添加下面几个类;

@Controller
public class BeanController {

	private int id;
	private String name;
	private final static Logger logger = LoggerFactory.getLogger(BeanController.class);

	public BeanController() {
		logger.info("BeanController constructor without params");
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "BeanController{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
}

  

@Component
public class DemoBeanDefinitionRegistryPostProcessor
		implements BeanDefinitionRegistryPostProcessor {
	private final static Logger logger = LoggerFactory
			.getLogger(DemoBeanDefinitionRegistryPostProcessor.class);

	public DemoBeanDefinitionRegistryPostProcessor() {
		logger.info("DemoBeanDefinitionRegistryPostProcessor constructor without params");
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
			throws BeansException {
		logger.info("bean定义的数据量:" + registry.getBeanDefinitionCount());
		// 创建bean定义信息
		AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BeanController.class).getBeanDefinition();
		registry.registerBeanDefinition("MyBeanController", beanDefinition);

		registry.removeBeanDefinition("beanController");
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
			throws BeansException {
		logger.info("bean定义的数据量:" + beanFactory.getBeanDefinitionCount());

	}
}

  

@Component
public class DemoBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	private final static Logger logger = LoggerFactory
			.getLogger(DemoBeanFactoryPostProcessor.class);

	public DemoBeanFactoryPostProcessor() {
		logger.info("DemoBeanFactoryPostProcessor constructor without params");
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
			throws BeansException {

		// 所有bean的定义,已经加载到beanFactory, 但是bean实例还没创建
		int count = beanFactory.getBeanDefinitionCount();
		String[] beanDefName = beanFactory.getBeanDefinitionNames();
		logger.info("当前BeanFactory中有" + count + "个bean");
		logger.info("beanDefNames:{}", Arrays.asList(beanDefName));
	}
}

  

public class Person {
	private final static Logger logger = LoggerFactory.getLogger(Person.class);

	private String id;
	private String name;

	public Person() {
		logger.info("person constructor without params");
	}
}

  

  • AbstractApplicationContext#refresh 调用BeanFactory的后置处理器

  Spring BeanDefinition加载流程分析

 

  • AbstractApplicationContext#invokeBeanFactoryPostProcessors

  Spring BeanDefinition加载流程分析

   调用BeanFactory的后置处理器,这里会执行自定义未注册(没有显示调用register方法)的beanDefinition加载到IOC容器;

 

  • PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)

  Spring BeanDefinition加载流程分析

   首先是判断beanFactory是不是BeanDefinitionRegirstry的类型(具体可从继承树获得对应的关系),如果beanFactoryBeanDefinitionRegirstry类型的,将beanFactory强转成BeanDefinitonRegistry类型的,上面定义的两个list,分别用于存储BeanFactroyPostProcessorBeanDefinitonRegistryPostProcessor类型的,用于区分的;一开始的时候,beanFactoryProcessors的数量为0,因此它的遍历在这里并不执行;

 

  Spring BeanDefinition加载流程分析

   postProcessorNames中包含org.springframework.context.annotation.internalConfigurationAnnotationProcessor的定义信息,它是在org.springframework.context.annotation.AnnotationConfigUtils定义的;

  

  AnnotationConfigUtils#CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME

  Spring BeanDefinition加载流程分析

 

  AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)  

  Spring BeanDefinition加载流程分析

   在这里会先从BeanDefinitionRegistry类型的registry判断是否含有CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME,如果registrry没有则添加ConfigurationClassPostProcessor类型的bean定义信息到registry;为何BeanDefinitionRegistry能获取到AnnotationConfigApplicationContext的数据,这可以从BeanDefinitionRegistry的继承树和该方法的调用栈分析,如下;

 

   Spring BeanDefinition加载流程分析

  首先从继承树可以看到AnnotationConfigApplicationContext实现了BeanDefinitionRegistry的接口,AnnotationConfigApplicationContext属于BeanDefinitionRegistry的子类;

 

  之后观察调用栈如下:

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext()
 ->org.springframework.context.annotation.AnnotatedBeanDefinitionReader
  ->org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.core.env.Environment)
  ->org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
   ->org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
   ->org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

  可以观察到这个过程,调用的方法声明的入参是BeanDefinitionRegistry类型的,但是调用时候传入的入参是AnnotationConfigApplicationContext,这是里氏替换原则(LSP);

  关于LSP,在代码里,把父类都替换成它的子类,程序的行为没有发生变化,换句话说就是由于子类类型的可替代性才使得父类类型的模块在无需更改的情况下可以扩展,子类类型必须能够替换它的父类类型;

 

  回到上面invokeBeanFactoryPostProcessors的流程,之后调用PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors

   Spring BeanDefinition加载流程分析

  postProcessor的size为1,里面保存的元素为ConfigurationClassPostProcessor的实例;之后调用ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry因此可看出BeanDefinitionRegistryPostProcessor优先于BeanFactoryPostProcessor执行; 

 

  ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

  Spring BeanDefinition加载流程分析

  postProcessBeanDefinitionRegistryBeanDefinitionRegistryPostProcessor接口定义的方法,可对初始化后的BeanDefinitionRegistry进行修改;这里的registriesPostProcessed存储当前registry的hash值,之后调用processConfigBeanDefinitions方法;

 

  ConfigurationClassPostProcessor#processConfigBeanDefinitions

  Spring BeanDefinition加载流程分析

   candidateNames为没进行解析前,候选的bean的定义名称,包括Spring自带的和自定义添加的;上面红框部分是循环candidateNames,如果是自定义添加的主配置类的bean定义信息,并添加到ConfigCandidates;

  Spring BeanDefinition加载流程分析

 

  Spring BeanDefinition加载流程分析

   第一个框是配置类的排序;第二个框是bean名称的生成策略,bean包括@ComponentScan包扫描的和@Import导入的,componentScanBeanNameGenerator对应的是包扫描出来的bean的名称生成器,importBeanNameGenerator对应的是导入的bean的名称生成器;

 

  Spring BeanDefinition加载流程分析

  创建配置类解析器parser;

  candidates为将要被解析的配置类,将之前的configCandidates加入其中;

  alreadyParsed为已经被解析的配置了,由于下面执行的do while(!candidates.isEmpty()),自定义配置类必定会被解析,alreadyParsed创建configCandidates.size()的容量大小,可以节省空间;

 

  之后调用parser.parse(Candidates)

  ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)

  Spring BeanDefinition加载流程分析

   红框部分为解析注解形式的bean定义信息,而这里传入的configCandidates为自定义的配置类;调用parse方法最终会调用ConfigurationClassParser#processConfigurationClass;

 

  ConfigurationClassParser#processConfigurationClass

  Spring BeanDefinition加载流程分析

     首先判断是否跳过解析,用于Condition条件判断;

 

  Spring BeanDefinition加载流程分析

   asSourceClass方法用于处理配置类,由于配置类可能存在于父类(若父类的全类名是以java开头的,则除外),所有需要将configClass变成sourceClass去解析,然后返回sourceClass的父类,如果此时父类为空,则不会进行while循环解析,如果父类为空,则会循环解析父类;

   doProcessConfigurationClass方法这里会递归处理配置类及其父类的层次结构;

 

  ConfigurationClassParser#doProcessConfigurationClass

  这个方法主要是依次解析@Component注解,@ProPertySource注解,@ComponentScan注解,@Import注解,@ImportResource注解(用于导入配置文件),解析@Bean方法,解析接口默认方法(处理接口的默认方法实现,从jdk8开始,接口的方法可以有默认实现,因此如果接口的方法添加了@Bean注解,该方法也需要被解析解析父类(如果被解析的配置类继承了某个类,那么配置类的父类也会被解析,这个可根据注释看出来);

  

  @Configuration继承了@Component注解,当内部类也是一个配置类,配置类上有@Configuration注解,需要递归解析内部类,因此会先解析内部类;

  Spring BeanDefinition加载流程分析

 

   如果一个配置类内部类也是一个配置类,一般不会有这种写法,如下;

@Configuration
public class Config {
    @Configuration
    public class InnerConfig {
    }
}

 

  下面这里主要分析一下@ComponentScan注解的解析处理;

  Spring BeanDefinition加载流程分析

 

    • 第一个红框是将@ComponentScan注解的属性封装到Set<AnnotationAttributes>类型的componentScans对象;如下;

    Spring BeanDefinition加载流程分析

 

    • 第二个红框是解析@ComponetScan的处理

    调用org.springframework.context.annotation.ComponentScanAnnotationParser#parse

    Spring BeanDefinition加载流程分析

      该方法前面的是创建的类路径扫描器scanner,将传入的componentScan的值一一赋值给scanner;调用scanner.doScan方法扫描指定的路径;

    调用org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

    Spring BeanDefinition加载流程分析

    遍历包扫描的路径的集合,findCandidateComponents方法根据包扫描的路径获取对应的候选组件,registerBeanDefinition方法将候选组件封装的BeandDefinitionHolder对象添加到beanDefinitions,并返回;

    Spring BeanDefinition加载流程分析

    componentsIndex默认为null,如果要componentsIndex不为null需要导入如下依赖,并将工程重新构建;因此默认调用scanCandidateComponents方法;

    注:spring-context-indexer这个依赖是Spring5.x以后新增的;

<!-- 注解@Indexed不能孤立存在,需要添加spring-context-indexer依赖 -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-indexer</artifactId>
	<version>5.2.4.RELEASE</version>
</dependency>

    之后调用ClassPathScanningCandidateComponentProvider#scanCandidateComponents

    ClassPathScanningCandidateComponentProvider#scanCandidateComponents 

    Spring BeanDefinition加载流程分析

    packageSearchPath为拼接需要扫描的包下面的类路径;

    Spring BeanDefinition加载流程分析

    resources为packageSearchPath路径下的绝对路径的class文件;

     Spring BeanDefinition加载流程分析

    isCandidateComponent是用于判断是否候选组件,如果是则加入到BeanDefinition的集合candidates中;Person是由@Bean注入的,Person的定义信息在另外的地方添加;

    Spring BeanDefinition加载流程分析

 

    •  第三个红框是包扫描路径下组件递归依次解析@ProPertySource注解,@ComponentScan注解,@Import注解,@ImportResource注解,解析@Bean方法,解析接口默认方法,解析父类;如解析一个Controller的@ComponentScan注解,但一般不会这样用;

     测试代码中的Person类是@Bean注入在的,它的定义信息解析是在@Configuration注解解析ConfigurationClassPostProcessor#processConfigBeanDefinition那里;loadBeanDefinitions方法里加载不是包扫描(即@ComponentScan扫描的)的bean的定义信息,如@Bean,@Import等;

    Spring BeanDefinition加载流程分析

 

     调用loadBeanDefinitions方法;遍历获取到BeanFactoryConfig类型的configClass;

    Spring BeanDefinition加载流程分析

 

    ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass

     Spring BeanDefinition加载流程分析

 

     而@Bean注入的beanName为methodName,可从下面ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod看出;

    Spring BeanDefinition加载流程分析

 

     当loadBeanDefinitionsForBeanMethodf方法执行完,beanDefinitionMap出现Person的定义信息;在实例化bean时,加载的beanDefinitionMap会被用于合并beanDefintion;

    Spring BeanDefinition加载流程分析

 

  执行结果如下:

  Spring BeanDefinition加载流程分析

 

   bean定义信息是先往容器加载,除了BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor类型的bean加载bean定义信息过程中调用getBean方法实例化了,其余的bean是在定义信息加载后实例化;

   从上面可以看出,ConfigurationClassPostProcessor会处理所有BeanDefinition中的符合注解条件的BeanDefinition(@Configuration,@Component,@ComponentScan,@Import,@ImportResource,@Bean注解自定义方法),这个过程使用ConfigurationClassParser解析成ConfigurationClass类型的结果集,之后使用ConfigurationClassBeanDefinitionReader对解析出的结果集进行加载;

 

  流程图如下:

  Spring BeanDefinition加载流程分析

 

相关文章:

  • 2021-06-17
  • 2022-12-23
  • 2019-02-23
  • 2019-09-02
  • 2020-05-18
  • 2022-01-21
  • 2022-12-23
  • 2021-10-07
猜你喜欢
  • 2019-10-03
  • 2019-12-17
  • 2022-12-23
  • 2022-12-23
  • 2021-12-01
  • 2021-12-04
相关资源
相似解决方案