3

七种 BeanDefinition,各显其能!

 9 months ago
source link: http://www.javaboy.org/2023/0906/spring-7-beandefinition.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

聚沙成塔!不知不觉 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.png

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();
}

这里定义了元数据的访问接口,具体的实现则是 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();
}

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 中的 <bean parent=""> 配置,在之前的视频中松哥已经和大家讲过了 Spring 中 parent 的使用了。
  4. setBeanClassName/getBeanClassName 这个就是配置 Bean 的 Class 全路径,对应 XML 中的 <bean class=""> 配置。
  5. setLazyInit/isLazyInit 配置/获取 Bean 是否懒加载,这个对应了 XML 中的 <bean lazy-init=""> 配置。
  6. setDependsOn/getDependsOn 配置/获取 Bean 的依赖对象,这个对应了 XML 中的 <bean depends-on=""> 配置。
  7. setAutowireCandidate/isAutowireCandidate 配置/获取 Bean 是否是自动装配,对应了 XML 中的 <bean autowire-candidate=""> 配置。
  8. setPrimary/isPrimary 配置/获取当前 Bean 是否为首选的 Bean,对应了 XML 中的 <bean primary=""> 配置。
  9. setFactoryBeanName/getFactoryBeanName 配置/获取 FactoryBean 的名字,对应了 XML 中的 <bean factory-bean=""> 配置,factory-bean 松哥在之前的视频中讲过,小伙伴们可以参考这里:Spring源码应该怎么学?
  10. setFactoryMethodName/getFactoryMethodName 和上一条成对出现的,对应了 XML 中的 <bean factory-method=""> 配置,不再赘述。
  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 还拥有诸多实现类,我们也来大致了解下。

先来看一张继承关系图:

BeanDefinition202309051544.png

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

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 源码中是如何应用的。

接下来我通过几个具体的案例来和小伙伴们演示各种不同的 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);

小伙伴们看到,我们这里向 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);

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

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

例如下面这样:

<?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 class="org.javaboy.demo.User" id="user"></bean>
</beans>

加载 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());
}

最终打印结果如下:

这个也好理解,毕竟我们在 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");
}
}

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);

3.3 AnnotatedGenericBeanDefinition

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

@Configuration
public class JavaConfig {

}

加载配置类并启动容器:

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());
}

最终打印结果如下:

3.4 ScannedGenericBeanDefinition

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

例如我有如下 Bean:

@Service
public class UserService {

}

然后配置包扫描:

@Configuration
@ComponentScan
public class JavaConfig {
}

启动容器:

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());
}

最终打印结果如下:

3.5 ConfigurationClassBeanDefinition

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

例如下面这个例子:

@Configuration
public class JavaConfig {
@Bean
User user() {
return new User();
}
}

查看解析后的 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);

使用这种方式去创建一个 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());
}

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK