• 七种 BeanDefinition,各显其能!


    聚沙成塔!不知不觉 Spring 源码已经连续更了两个月啦,视频也录制了不少了,对 Spring 源码分析感兴趣的小伙伴戳这里哦Spring源码应该怎么学?

    今天我们继续来看 Spring 源码中一个非常重要的概念:BeanDefinition。

    1.BeanDefinition

    在 Spring 容器中,我们广泛使用的是一个一个的 Bean,BeanDefinition 从名字上就可以看出是关于 Bean 的定义。

    事实上就是这样,我们在 XML 文件中配置的 Bean 的各种属性,亦或者用注解定义出来的 Bean 的各种属性,在真正生成 Bean 直接,我们需要先对这些设置的属性进行解析,解析的结果需要有一个对象来承载,很明显,这个对象就是 BeanDefinition。

    无论是通过 XML 中定义的 Bean 属性还是通过 Java 代码定义的 Bean 属性,都会先加载到 BeanDefinition 上,然后通过 BeanDefinition 来生成一个 Bean,从这个角度来说,BeanDefinition 和 Bean 的关系有点类似于类和对象的关系,BeanDefinition 是模板,Bean 是模板具体化之后的产物。

    要理解 BeanDefinition,我们从 BeanDefinition 的继承关系开始看起。

    BeanDefinition 是一个接口,继承自 BeanMetadataElement 和 AttributeAccessor 接口。

    • BeanMetadataElement:该接口只有一个方法 getSource,该方法返回 Bean 的来源。
    • AttributeAccessor:该接口主要规范了问任意对象元数据的方法。

    我们来看下 AttributeAccessor:

    public interface AttributeAccessor {
    	void setAttribute(String name, @Nullable Object value);
    	@Nullable
    	Object getAttribute(String name);
    	@Nullable
    	Object removeAttribute(String name);
    	boolean hasAttribute(String name);
    	String[] attributeNames();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里定义了元数据的访问接口,具体的实现则是 AttributeAccessorSupport,这些数据采用 LinkedHashMap 进行存储。

    这是 BeanDefinition 所继承的两个接口。接下来我们来看下 BeanDefinition 接口:

    public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    	int ROLE_APPLICATION = 0;
    	int ROLE_SUPPORT = 1;
    	int ROLE_INFRASTRUCTURE = 2;
    	void setParentName(@Nullable String parentName);
    	@Nullable
    	String getParentName();
    	void setBeanClassName(@Nullable String beanClassName);
    	@Nullable
    	String getBeanClassName();
    	void setScope(@Nullable String scope);
    	@Nullable
    	String getScope();
    	void setLazyInit(boolean lazyInit);
    	boolean isLazyInit();
    	void setDependsOn(@Nullable String... dependsOn);
    	@Nullable
    	String[] getDependsOn();
    	void setAutowireCandidate(boolean autowireCandidate);
    	boolean isAutowireCandidate();
    	void setPrimary(boolean primary);
    	boolean isPrimary();
    	void setFactoryBeanName(@Nullable String factoryBeanName);
    	@Nullable
    	String getFactoryBeanName();
    	void setFactoryMethodName(@Nullable String factoryMethodName);
    	@Nullable
    	String getFactoryMethodName();
    	ConstructorArgumentValues getConstructorArgumentValues();
    	default boolean hasConstructorArgumentValues() {
    		return !getConstructorArgumentValues().isEmpty();
    	}
    	MutablePropertyValues getPropertyValues();
    	default boolean hasPropertyValues() {
    		return !getPropertyValues().isEmpty();
    	}
    	void setInitMethodName(@Nullable String initMethodName);
    	@Nullable
    	String getInitMethodName();
    	void setDestroyMethodName(@Nullable String destroyMethodName);
    	@Nullable
    	String getDestroyMethodName();
    	void setRole(int role);
    	int getRole();
    	void setDescription(@Nullable String description);
    	@Nullable
    	String getDescription();
    	ResolvableType getResolvableType();
    	boolean isSingleton();
    	boolean isPrototype();
    	boolean isAbstract();
    	@Nullable
    	String getResourceDescription();
    	@Nullable
    	BeanDefinition getOriginatingBeanDefinition();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    BeanDefinition 中的方法虽然多,但是结合我们平时在 XML/Java 中的配置,这些方法其实都很好理解:

    1. 首先一开始定义了两个变量用来描述 Bean 是不是单例的,后面的 setScope/getScope 方法可以用来修改/获取 scope 属性。
    2. ROLE_xxx 用来描述一个 Bean 的角色,ROLE_APPLICATION 表示这个 Bean 是用户自己定义的 Bean;ROLE_SUPPORT 表示这个 Bean 是某些复杂配置的支撑部分;ROLE_INFRASTRUCTURE 表示这是一个 Spring 内部的 Bean,通过 setRole/getRole 可以修改。
    3. setParentName/getParentName 用来配置 parent 的名称,这块可能有的小伙伴使用较少,这个对应着 XML 中的 配置,在之前的视频中松哥已经和大家讲过了 Spring 中 parent 的使用了。
    4. setBeanClassName/getBeanClassName 这个就是配置 Bean 的 Class 全路径,对应 XML 中的 配置。
    5. setLazyInit/isLazyInit 配置/获取 Bean 是否懒加载,这个对应了 XML 中的 配置。
    6. setDependsOn/getDependsOn 配置/获取 Bean 的依赖对象,这个对应了 XML 中的 配置。
    7. setAutowireCandidate/isAutowireCandidate 配置/获取 Bean 是否是自动装配,对应了 XML 中的 配置。
    8. setPrimary/isPrimary 配置/获取当前 Bean 是否为首选的 Bean,对应了 XML 中的 配置。
    9. setFactoryBeanName/getFactoryBeanName 配置/获取 FactoryBean 的名字,对应了 XML 中的 配置,factory-bean 松哥在之前的视频中讲过,小伙伴们可以参考这里:Spring源码应该怎么学?
    10. setFactoryMethodName/getFactoryMethodName 和上一条成对出现的,对应了 XML 中的 配置,不再赘述。
    11. getConstructorArgumentValues 返回该 Bean 构造方法的参数值。
    12. hasConstructorArgumentValues 判断上一条是否是空对象。
    13. getPropertyValues 这个是获取普通属性的集合。
    14. hasPropertyValues 判断上一条是否为空对象。
    15. setInitMethodName/setDestroyMethodName 配置 Bean 的初始化方法、销毁方法。
    16. setDescription/getDescription 配置/返回 Bean 的描述。
    17. isSingleton Bean 是否为单例。
    18. isPrototype Bean 是否为原型。
    19. isAbstract Bean 是否抽象。
    20. getResourceDescription 返回定义 Bean 的资源描述。
    21. getOriginatingBeanDefinition 如果当前 BeanDefinition 是一个代理对象,那么该方法可以用来返回原始的 BeanDefinition 。

    这个就是 BeanDefinition 的定义以及它里边方法的含义。

    2.BeanDefinition 实现类

    上面只是 BeanDefinition 接口的定义,BeanDefinition 还拥有诸多实现类,我们也来大致了解下。

    先来看一张继承关系图:

    这么多实现类看着有点眼花缭乱,不过搞清楚了每一个接口和类的作用,再看就很容易了。

    2.1 AbstractBeanDefinition

    AbstractBeanDefinition 是一个抽象类,它根据 BeanDefinition 中定义的接口提供了相应的属性,并实现了 BeanDefinition 中定义的一部分方法。BeanDefinition 中原本只是定义了一系列的 get/set 方法,并没有提供对应的属性,在 AbstractBeanDefinition 中将所有的属性定义出来了。

    后面其他的实现类也基本上都是在 AbstractBeanDefinition 的基础上完成的。

    2.2 RootBeanDefinition

    这是一个比较常用的实现类,对应了一般的元素标签。

    2.3 ChildBeanDefinition

    可以让子 BeanDefinition 定义拥有从父 BeanDefinition 那里继承配置的能力,如果子 Bean 从父 Bean 获取配置,可以参考松哥之前的这篇文章:Spring BeanDefinition 也分父子?

    2.4 GenericBeanDefinition

    GenericBeanDefinition 是从 Spring2.5 以后新加入的 BeanDefinition 实现类。GenericBeanDefinition 可以动态设置父 Bean,同时兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能,因此,自从有了 GenericBeanDefinition 之后,RootBeanDefinition 和 ChildBeanDefinition 现在相对就用的少了。

    2.5 AnnotatedBeanDefinition

    这个表示注解类型 BeanDefinition,用于表示通过注解配置的 Bean 定义。通过 AnnotatedBeanDefinition,我们可以获取到被注解的 Bean 的相关信息,包括注解类型、属性值、方法等。这个接口提供了一种方便的方式来处理通过注解方式配置的 Bean,并且可以在运行时动态地获取和操作这些注解信息,当然,这是一个接口,它有三个实现类,分别是 AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition 以及 ConfigurationClassBeanDefinition。

    2.6 AnnotatedGenericBeanDefinition

    作为系统配置类的类会解析为 AnnotatedGenericBeanDefinition。

    2.7 ScannedGenericBeanDefinition

    这个是通过包扫描自动注册的 Bean,就会被解析为 ScannedGenericBeanDefinition。

    2.8 ConfigurationClassBeanDefinition

    这是一个私有的内部类。我们通过 @Bean 注解定义的 Bean,最终会被解析为 ConfigurationClassBeanDefinition。

    2.9 ClassDerivedBeanDefinition

    ClassDerivedBeanDefinition 的作用是扩展并描述一个类派生的Bean的元数据。它是 AnnotatedBeanDefinition 接口的一个实现类,在 Spring 框架中用于表示通过类派生方式配置的Bean定义。

    2.10 CreateFromClassBeanDefinition

    这个是按照类型创建 Bean 的时候会用到。

    差不多就这么多了,大部分我们日常开发中其实都用不上,接下来松哥通过几个具体的案例来和小伙伴们演示这些 BeanDefinition 的具体用法。后面的文章,我们再来分析这些 BeanDefinition 在 Spring 源码中是如何应用的。

    3.实践

    接下来我通过几个具体的案例来和小伙伴们演示各种不同的 BeanDefinition 的用法,今天我主要和小伙伴们演示我们纯手动使用 BeanDefinition,然后分析一下我们平时的配置本质上使用的 BeanDefinition 是哪一个,今天我们先不去源码分析,单纯的就看看效果。

    3.1 GenericBeanDefinition

    先来看 GenericBeanDefinition,这个功能相对比较全,兼具 RootBeanDefinition 和 ChildBeanDefinition 的能力。

    先来看一个简单用法:

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setBeanClass(User.class);
    MutablePropertyValues pValues = new MutablePropertyValues();
    pValues.add("username", "javaboy");
    bd.setPropertyValues(pValues);
    beanFactory.registerBeanDefinition("user", bd);
    User user = beanFactory.getBean("user", User.class);
    System.out.println("user = " + user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    小伙伴们看到,我们这里向 Spring 容器注册了一个 GenericBeanDefinition 类型的 BeanDefinition,GenericBeanDefinition 中包含了具体的 class 以及 Bean 的各个属性值。

    如果我们在 Bean 定义的时候,想要使用继承特性,也可以使用 GenericBeanDefinition:

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    GenericBeanDefinition parentBD = new GenericBeanDefinition();
    GenericBeanDefinition childBD = new GenericBeanDefinition();
    parentBD.setBeanClass(Animal.class);
    MutablePropertyValues pValues = new MutablePropertyValues();
    pValues.add("name", "小黄");
    parentBD.setPropertyValues(pValues);
    childBD.setBeanClass(Dog.class);
    childBD.setParentName("animal");
    beanFactory.registerBeanDefinition("animal", parentBD);
    beanFactory.registerBeanDefinition("dog", childBD);
    Dog dog = beanFactory.getBean("dog", Dog.class);
    System.out.println("dog = " + dog);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这次我直接定义了两个 GenericBeanDefinition,一个作为 parent,另外一个作为 child,为 child 设置 parentName,则 child 可以继承 parent 中的属性。上面的案例中,最终打印出来 dog 的 name 属性就是 小黄,这块小伙伴们可以参考松哥之前的文章:Spring BeanDefinition 也分父子?

    我们平时通过 XML 文件定义的 Bean,最终解析后就是 GenericBeanDefinition。

    例如下面这样:

    
    <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 class="org.javaboy.demo.User" id="user">bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    加载 XML 文件,创建容器:

    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition bd = ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
        System.out.println(beanDefinitionName + ">>>" + bd.getClass());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终打印结果如下:

    这个也好理解,毕竟我们在 XML 中配置的时候,可能存在 parent,也可能不存在,用 GenericBeanDefinition 就能够应对各种情况。

    3.2 RootBeanDefinition/ChildBeanDefinition

    这两个常规的功能其实都有,但是 RootBeanDefinition 一般可以用来做 parent,不能用作 child,即给 RootBeanDefinition 不能配置 parentName 属性。强行设置会抛出如下异常:

    @Override
    public void setParentName(@Nullable String parentName) {
    	if (parentName != null) {
    		throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ChildBeanDefinition 则既可以做 parent 也可以做 child,但是 ChildBeanDefinition 在使用的使用必须指定 parent,即使 ChildBeanDefinition 作为 parent,也必须指定 parent,所以 ChildBeanDefinition 在使用的过程中有一点点局限性,因此目前被 GenericBeanDefinition 代替了。

    来看一个简单的案例:

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    RootBeanDefinition parentBD = new RootBeanDefinition();
    parentBD.setBeanClass(Animal.class);
    MutablePropertyValues pValues = new MutablePropertyValues();
    pValues.add("name", "小黄");
    parentBD.setPropertyValues(pValues);
    ChildBeanDefinition childBD = new ChildBeanDefinition("animal");
    childBD.setBeanClass(Dog.class);
    beanFactory.registerBeanDefinition("animal", parentBD);
    beanFactory.registerBeanDefinition("dog", childBD);
    Dog dog = beanFactory.getBean("dog", Dog.class);
    System.out.println("dog = " + dog);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.3 AnnotatedGenericBeanDefinition

    对于使用 @Configuration 注解标记的类,最终解析出来的 BeanDefinition 就是 AnnotatedGenericBeanDefinition。例如我有一个配置类如下:

    @Configuration
    public class JavaConfig {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    加载配置类并启动容器:

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
        System.out.println(beanDefinitionName + " >>> " + beanDefinition.getClass());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终打印结果如下:

    3.4 ScannedGenericBeanDefinition

    这个是那些通过包扫描注册到 Spring 容器中的 Bean,在一开始定义出来的 BeanDefinition 就是 ScannedGenericBeanDefinition。

    例如我有如下 Bean:

    @Service
    public class UserService {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    然后配置包扫描:

    @Configuration
    @ComponentScan
    public class JavaConfig {
    }
    
    • 1
    • 2
    • 3
    • 4

    启动容器:

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = ctx.getBeanFactory().getBeanDefinition(beanDefinitionName);
        System.out.println(beanDefinitionName + " >>> " + beanDefinition.getClass());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终打印结果如下:

    3.5 ConfigurationClassBeanDefinition

    当我们通过 @Bean 注解去定义 Bean 的时候,那么被 @Bean 注解标记的类就会被解析为 ConfigurationClassBeanDefinition。

    例如下面这个例子:

    @Configuration
    public class JavaConfig {
        @Bean
        User user() {
            return new User();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看解析后的 BeanDefinition 如下:

    3.6 CreateFromClassBeanDefinition

    这个其实用的少,但是咱么既然讲到 Spring,松哥也说两句。

    这个是当我们想要创建一个对象,我们希望这个对象能够自动走一遍 Spring 中的各种后置处理器的时候,那么可以调用 createBean 方法,该方法内部使用了 CreateFromClassBeanDefinition。

    例如如下代码:

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
    ConfigurableListableBeanFactory beanFactory = ctx.getBeanFactory();
    User user = beanFactory.createBean(User.class);
    System.out.println("user = " + user);
    
    • 1
    • 2
    • 3
    • 4

    使用这种方式去创建一个 Bean,这个 Bean 会走一遍 Spring 中 Bean 的后置处理器,其中,createBean 方法的内部就使用了 CreateFromClassBeanDefinition。

    3.7 ClassDerivedBeanDefinition

    ClassDerivedBeanDefinition 和 CreateFromClassBeanDefinition 其实比较像,差别在于二者处理构造方法的方式不同。

    而且 ClassDerivedBeanDefinition 是一个相当冷门的 BeanDefinition,在 GenericApplicationContext 的实现类中,可以使用 GenericXmlApplicationContext、StaticApplicationContext 或者 GenericGroovyApplicationContext,只有这三个类中 registerBean 方法用到了 ClassDerivedBeanDefinition,我们常见的 AnnotationConfigApplicationContext 由于方法重写的缘故并未使用 ClassDerivedBeanDefinition。

    StaticApplicationContext ctx = new StaticApplicationContext();
    ctx.registerBean(User.class,()->{
        User user = new User();
        user.setUsername("javaboy");
        return user;
    });
    User user = ctx.getBean(User.class);
    System.out.println("user = " + user);
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition bd = ctx.getBeanDefinition(beanDefinitionName);
        System.out.println(beanDefinitionName + ">>>" + bd.getClass());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们可以调用 registerBean 方法向 Spring 容器中注入一个 Bean,该方法第二个参数是一个 Bean 的生产者,如果不指定生产者,那么这个方法最终就是通过第一个参数反射创建 Bean,registerBean 方法的内部就是使用了 ClassDerivedBeanDefinition。

    好啦,BeanDefinition 一共就是这七种,接下来我会通过几篇文章和大家重点介绍 GenericBeanDefinition、AnnotatedGenericBeanDefinition、ScannedGenericBeanDefinition 以及 ConfigurationClassBeanDefinition 这四种最为常见的 BeanDefinition。

  • 相关阅读:
    【Transformers】第 7 章 :问答
    【华为OD机试高分必刷题目】决战(Java&Python&C++动态规划DP实现)
    关于聚合函数的课后作业
    Android kotlin 实现一键清理内存功能
    IEEE独立出版 | 第七届计算机科学与智能控制国际会议(ISCSIC 2023)
    数字藏品实物化金谷诺亚艺博馆布局 携手翰德轩转变多维创新
    基于Java+SpringBoot+Vue宠物咖啡馆平台设计和实现
    利用京东云Web应用防火墙实现Web入侵防护
    《Span-Based Joint Entity and Relation Extraction with Transformer Pre-Training》阅读笔记
    桌面宠物 ① 通过python制作属于自己的桌面宠物
  • 原文地址:https://blog.csdn.net/u012702547/article/details/132752668