本文同享自华为云社区《Spring高手之路11——BeanDefinition解密:构建和办理Spring Beans的柱石》,作者: 砖业洋__ 。

BeanDefinition是Spring中一个十分重要的概念,它包含了Spring容器用于创立、装备Bean所需的一切信息。了解BeanDefinition能够帮助咱们深化把握Spring的内部作业机制。

1. 探索BeanDefinition

首要,让咱们来对 BeanDefinition 有一个全体的知道。

1.1 官方文档对BeanDefinition的解读

对于了解Spring结构的概念和组件,Spring的官方文档是一个十分重要的资源。关于BeanDefinition,官方文档粗心如下:

BeanDefinition包含了大量的装备信息,这些信息能够辅导Spring怎么创立Bean,包含Bean的结构函数参数,特点值,初始化办法,静态工厂办法称号等等。此外,子BeanDefinition还能够从父BeanDefinition中承继装备信息,一起也能够覆盖或增加新的装备信息。这种规划形式有效减少了冗余的装备信息,使装备更为简练。

接下来,让咱们经过一个详细的比如来更好地了解BeanDefinition。

考虑一个简略的Java类,Person:

public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters and setters
}

咱们能够用XML装备或许Java装备的办法来界说一个Person类型的Bean,一起这个Bean的装备信息会被封装在BeanDefinition中。

在XML装备中,一个Person Bean的界说或许如下:

<bean id="person" class="com.example.Person">
<constructor-arg name="name" value="John"/>
<constructor-arg name="age" value="25"/>
</bean>

在这儿,BeanDefinition的信息包含了class特点(全限制类名)以及结构函数参数的称号和值。

在Java装备中,咱们能够这样界说一个Person Bean:

@Configuration
public class AppConfig {
@Bean
public Person person() {
return new Person("John", 25);
}
}

在这个比如中,BeanDefinition的信息包含class特点(全限制类名)以及结构函数参数。咱们能够经过BeanDefinition的getBeanClassName()办法获取到这个全限制类名。

1.2 BeanDefinition要害办法剖析

BeanDefinition接口界说了Bean的一切元信息,首要包含以下办法:

  • get/setBeanClassName() – 获取/设置Bean的类名
  • get/setScope() – 获取/设置Bean的效果域
  • isSingleton() / isPrototype() – 判别是否单例/原型效果域
  • get/setInitMethodName() – 获取/设置初始化办法名
  • get/setDestroyMethodName() – 获取/设置毁掉办法名
  • get/setLazyInit() – 获取/设置是否推迟初始化
  • get/setDependsOn() – 获取/设置依靠的Bean
  • get/setPropertyValues() – 获取/设置特点值
  • get/setAutowireCandidate() – 获取/设置是否能够主动安装
  • get/setPrimary() – 获取/设置是否首选的主动安装Bean

由于BeanDefinition源码篇幅较长,这儿就不悉数贴上来,大家能够自行检查。BeanDefinition还完成了AttributeAccessor接口,能够经过该接口增加自界说元数据,后面小节会举例AttributeAccessor的运用。

从上面能够看到,BeanDefinition 是 Spring 结构中用来描绘 Bean 的元数据目标,这个元数据包含了关于 Bean 的一些基本信息,包含以下几个方面:

  • Bean 的类信息: 这是 Bean 的全限制类名,即这个 Bean 实例化后的详细类型。
  • Bean 的特点信息: 包含了如 Bean 的效果域(是单例仍是原型)、是否为首要的 Bean(primary)、描绘信息等。
  • Bean 的行为特性: 例如 Bean 是否支持推迟加载,是否能够作为主动安装的候选者,以及 Bean 的初始化和毁掉办法等。
  • Bean 与其他 Bean 的联系: 比如说这个 Bean 所依靠的其他 Bean,以及这个 Bean 是否有父 Bean。
  • Bean 的装备信息: 这包含了 Bean 的结构器参数,以及特点值等。

1.3 BeanDefinition部分办法的实践运用

接下来用一个详细的代码示例来阐明BeanDefinition接口中各个办法的运用,并结合实践的代码示例阐明这些办法的实践意义。下面,我会针对BeanDefinition的几个重要方面供给代码示例。

悉数代码如下:

首要,这是咱们的Java装备类以及Person类的界说:

package com.example.demo.configuration;
import com.example.demo.bean.Person;
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
@Scope("singleton")
@Lazy
@Primary
@Description("A bean for person")
public Person person() {
return new Person("John", 25);
}
}
package com.example.demo.bean;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters and setters
public void init() {
System.out.println("Initializing Person bean");
}
public void cleanup() {
System.out.println("Cleaning up Person bean");
}
}

下面是怎么经过BeanDefinition获取到各个特点:

package com.example.demo;
import com.example.demo.configuration.AppConfig;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
String personBeanName = "person";
BeanDefinition personBeanDefinition = context.getBeanFactory().getBeanDefinition(personBeanName);
// 获取Bean的类信息
System.out.println("Bean Class Name: " + context.getBean(personBeanName).getClass().getName());
// 获取Bean的特点
System.out.println("Scope: " + personBeanDefinition.getScope());
System.out.println("Is primary: " + personBeanDefinition.isPrimary());
System.out.println("Description: " + personBeanDefinition.getDescription());
// 获取Bean的行为特征
System.out.println("Is lazy init: " + personBeanDefinition.isLazyInit());
System.out.println("Init method: " + personBeanDefinition.getInitMethodName());
System.out.println("Destroy method: " + personBeanDefinition.getDestroyMethodName());
// 获取Bean的联系
System.out.println("Parent bean name: " + personBeanDefinition.getParentName());
System.out.println("Depends on: " + Arrays.toString(personBeanDefinition.getDependsOn()));
// 获取Bean的装备特点
System.out.println("Constructor argument values: " + personBeanDefinition.getConstructorArgumentValues());
System.out.println("Property values: " + personBeanDefinition.getPropertyValues());
}
}

运转成果:

BeanDefinition解密:构建和管理Spring Beans的基石

这个比如包含了BeanDefinition的大部分办法,展示了它们的效果。请注意,在这个比如中,一些办法如getDependsOn()、getParentName()、getConstructorArgumentValues()、getPropertyValues()的回来成果或许不会显示出任何实质内容,由于咱们的person Bean并没有设置这些值。假如在实践运用中设置了这些值,那么这些办法将回来相应的成果。

1.4 BeanDefinition深层信息结构梳理

在 Spring 中,BeanDefinition 包含了以下首要信息:

  • Class:这是全限制类名,Spring 运用这个信息经过反射创立 Bean 实例。例如,com.example.demo.bean.Book,当 Spring 需求创立 Book bean 的实例时,它将依据这个类名经过反射创立 Book 类的实例。
  • Name:这是 Bean 的称号。在运用程序中,咱们一般运用这个称号来获取 Bean 的实例。例如,咱们或许有一个称号为 “bookService” 的 Bean,咱们能够经过 context.getBean(“bookService”) 来获取这个 Bean 的实例。
  • Scope:这界说了 Bean 的效果域,例如 singleton 或 prototype。假如 scope 是 singleton,那么 Spring 容器将只创立一个 Bean 实例并在每次恳求时回来这个实例。假如 scope 是 prototype,那么每次恳求 Bean 时,Spring 容器都将创立一个新的 Bean 实例。
  • Constructor arguments:这是用于实例化 Bean 的结构函数参数。例如,假如咱们有一个 Book 类,它的结构函数需求一个 String 类型的参数 title,那么咱们能够在 BeanDefinition 中设置 constructor arguments 来供给这个参数。
  • Properties:这些是需求注入到 Bean 的特点值。例如,咱们或许有一个 Book 类,它有一个 title 特点,咱们能够在 BeanDefinition 中设置 properties 来供给这个特点的值。这些值也能够经过 标签或 @Value 注解在装备文件或类中注入。
  • Autowiring Mode:这是主动安装的形式。假如设置为 byType,那么 Spring 容器将主动安装 Bean 的特点,它将查找容器中与特点类型相匹配的 Bean 并注入。假如设置为 byName,那么容器将查找容器中称号与特点名相匹配的 Bean 并注入。还有一个选项是 constructor,它指的是经过 Bean 结构函数的参数类型来主动安装依靠。
  • Lazy Initialization:假如设置为 true,Bean 将在初次恳求时创立,而不是在运用发动时。这能够提高运用的发动速度,但或许会在初次恳求 Bean 时引入一些推迟。
  • Initialization Method and Destroy Method:这些是 Bean 的初始化和毁掉办法。例如,咱们或许有一个 BookService 类,它有一个名为 init 的初始化办法和一个名为 cleanup 的毁掉办法,咱们能够在 BeanDefinition 中设置这两个办法,那么 Spring 容器将在创立 Bean 后调用 init 办法,而在毁掉 Bean 之前调用 cleanup 办法。
  • Dependency beans:这些是 Bean 的依靠联系。例如,咱们或许有一个 BookService Bean,它依靠于一个 BookRepository Bean,那么咱们能够在 BookService 的 BeanDefinition 中设置 dependency beans 为 “bookRepository”,那么在创立 BookService Bean 之前,Spring 容器将首要创立 BookRepository Bean。

以上便是 BeanDefinition 中首要包含的信息,这些信息将会告知 Spring 容器怎么创立和装备 Bean。不同的 BeanDefinition 完成或许会有更多的装备信息。例如,RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition 等都是 BeanDefinition 接口的详细完成类,它们或许包含更多的装备选项。

2. BeanDefinition结构系统解析

让咱们首要清晰BeanDefinition的人物。BeanDefinition是Spring中的中心组件,它界说了bean的装备信息,包含类名、效果域、结构器参数、特点值等。下面咱们来看看BeanDefinition在Spring中的规划是怎么的。

经过IDEA咱们能够得到如下的承继联系图:

BeanDefinition解密:构建和管理Spring Beans的基石

虽然有许多接口、抽象类和扩展,咱们只需求重视其间的要害部分。

2.1 BeanDefinition的类型及其运用

在Spring中,一个bean的装备信息便是由BeanDefinition目标来保存的。依据bean装备的不同来历和办法,BeanDefinition又被分为很多种类型,咱们选取其间几种解说一下

  • RootBeanDefinition:当咱们在XML装备文件中界说一个bean时,Spring会为这个bean创立一个RootBeanDefinition目标,这个目标包含了一切用于创立bean的信息,如bean的类名、特点值等。例如:

这段XML装备中界说了一个名为”exampleBean”的bean,它的类是”com.example.ExampleBean”,而且有一个名为”stringProperty”的特点值是”stringValue”。当Spring读取这段装备时,会创立一个RootBeanDefinition目标来保存这个bean的一切装备信息。

总结:在XML文件中界说一个bean时,Spring就会创立一个RootBeanDefinition实例,这个实例会保存一切的装备信息,比如类名、特点值等。

  • ChildBeanDefinition:当咱们需求让一个bean承继另一个bean的装备时,能够运用ChildBeanDefinition。例如:

这段XML装备中,”childBean”承继了”parentBean”的一切装备,一起还增加了一个新的特点”anotherStringProperty”。当Spring读取这段装备时,会首要为”parentBean”创立一个RootBeanDefinition目标,然后为”childBean”创立一个ChildBeanDefinition目标,这个目标会引证”parentBean”的BeanDefinition。

总结:假如有一个bean,而且想创立一个新的bean,这个新的bean需求承继原有bean的一切装备,但又要增加或修改一些装备信息,Spring就会创立一个ChildBeanDefinition实例。

  • GenericBeanDefinition:这是一种通用的BeanDefinition,能够依据需求转化为RootBeanDefinition或许ChildBeanDefinition。例如,在一个装备类中运用@Bean注解界说了一个bean:

    @Configuration

    public class AppConfig {

    @Bean

    public MyComponent myComponent() {

    return new MyComponent();

    }

    }

在这段代码中,咱们界说了一个名为”myComponent”的bean,它的类是”MyComponent”。当Spring解析这个装备类时,会为myComponent()办法创立一个GenericBeanDefinition目标。这个GenericBeanDefinition目标会保存办法的姓名(这也是bean的姓名)、回来类型,以及任何需求的结构函数参数或特点。在这个比如中,咱们没有界说任何参数或特点,所以GenericBeanDefinition目标只包含了基本的信息。这个GenericBeanDefinition目标之后能够被Spring容器用于生成bean的实例。

总结:在Java装备类中运用@Bean注解界说一个bean时,Spring就会创立一个GenericBeanDefinition实例。

  • AnnotatedBeanDefinition:当咱们在代码中运用注解(如@Component, @Service, @Repository等)来界说bean时,Spring会创立一个AnnotatedBeanDefinition接口的实例。例如:

    @Component(“myComponent”)

    public class MyComponent {

    // some fields and methods

    }

在这段代码中,咱们界说了一个名为”myComponent”的bean,它的类是”MyComponent”,而且这个类上有一个@Component注解。当Spring解析这个类时,会创立一个AnnotatedBeanDefinition目标。这个AnnotatedBeanDefinition目标会保存类名(这也是bean的姓名)、类的类型,以及类上的一切注解信息。在这个比如中,AnnotatedBeanDefinition实例会包含@Component注解及其一切元数据。这个AnnotatedBeanDefinition实例之后能够被Spring容器用于生成bean的实例,一起Spring还能够运用存储在AnnotatedBeanDefinition中的注解信息来进行进一步的处理,如AOP代理、事务办理等。

总结:在类上运用注解(如@Component, @Service, @Repository等)来界说一个bean时,Spring会创立一个完成了AnnotatedBeanDefinition接口的实例,如AnnotatedGenericBeanDefinition或ScannedGenericBeanDefinition。这个实例会保存类名、类的类型,以及类上的一切注解信息。

GenericBeanDefinition和AnnotatedBeanDefinition的首要区别在于,AnnotatedBeanDefinition保存了类上的注解信息,而GenericBeanDefinition没有。这就使得Spring能够在运转时读取和处理这些注解,供给更丰富的功能。

在大多数情况下,咱们并不需求关怀Spring为bean创立的是哪一种BeanDefinition。Spring会主动办理这些BeanDefinition,并依据它们的类型以及它们所包含的信息来创立和装备bean。

2.2 生成BeanDefinition的原理剖析

这个 BeanDefinition 目标是在 Spring 发动过程中由各种 BeanDefinitionReader 完成类读取装备并生成的。

在 Spring 中首要有三种办法来创立 BeanDefinition:

  • XML 装备办法

首要,咱们在 XML 文件中界说了一个 bean:

<bean id="bookService" class="com.example.demo.service.BookService">
<property name="bookRepository" ref="bookRepository"/>
</bean>
<bean id="bookRepository" class="com.example.demo.repository.BookRepository"/>

在这种情况下,当 Spring 发动的时分,XmlBeanDefinitionReader 会读取这个 XML 文件,解析其间的 元素,并为每一个 元素创立一个 BeanDefinition 目标。

简略描绘为:由XmlBeanDefinitionReader读取XML文件,解析元素并生成BeanDefinition。

  • 注解装备办法

咱们在类上运用 @Component, @Service, @Repository 等注解来界说 bean,例如:

@Repository
public class BookRepository {
// ... repository methods
}
@Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
// ... service methods
}

在这种情况下,当 Spring 发动的时分,ClassPathBeanDefinitionScanner 会扫描指定的包途径,找到一切带有特定注解的类,并为这些类创立 BeanDefinition 目标。这种办法下生成的 BeanDefinition 一般是 ScannedGenericBeanDefinition 类型。

简略描绘为:由ClassPathBeanDefinitionScanner扫描指定包途径下的带注解的类,并生成BeanDefinition。

  • Java 装备办法

咱们运用 @Configuration 和 @Bean 注解来界说装备类和 bean,例如:

@Configuration
public class AppConfig {
@Bean
public BookRepository bookRepository() {
return new BookRepository();
}
@Bean
public BookService bookService(BookRepository bookRepository) {
return new BookService(bookRepository);
}
}

在这种情况下,当 Spring 发动的时分,ConfigurationClassPostProcessor 就会处理这些装备类,并交给 ConfigurationClassParser 来解析。对于装备类中每一个标记了 @Bean 的办法,都会创立一个 BeanDefinition 目标。这种办法下生成的 BeanDefinition 一般是 ConfigurationClassBeanDefinition 类型。

简略描绘为:由ConfigurationClassPostProcessor处理标记了@Configuration的类,解析其间的@Bean办法并生成BeanDefinition。

总的来说,不论咱们选择 XML 装备、注解装备仍是 Java 装备办法,Spring 发动时都会解析这些装备,并生成对应的 BeanDefinition 目标,以此来辅导 Spring 容器怎么创立和办理 Bean 实例。

这些内容或许比较抽象和复杂,但对于初学者来说,只需求了解:BeanDefinition 是 Spring 用来存储 Bean 装备信息的目标,它是在 Spring 发动过程中由 BeanDefinitionReader 读取装备生成的,详细的生成办法取决于运用的装备办法(XML、注解或许 Java 装备),至于其间详细的完成原理,以后再深化了解。

2.3 AttributeAccessor实战:特点操作利器

AttributeAccessor是Spring结构中的一个重要接口,它供给了一种灵敏的办法来附加额定的元数据到Spring的中心组件。在Spring中,包含BeanDefinition在内的许多重要类都完成了AttributeAccessor接口,这样就能够动态地增加和获取这些组件的额定特点。这样做的一个显著好处是,开发人员能够在不改动原有类界说的情况下,灵敏地办理这些组件的额定信息。

让咱们来看一个比如,悉数代码如下:

先创立一个Book目标

class Book {
private String title;
private String author;
public Book() {}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
// getter 和 setter 省掉...
}

主程序:

package com.example.demo;
import com.example.demo.bean.Book;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
public class DemoApplication {
public static void main(String[] args) {
// 创立一个BeanDefinition, BeanDefinition是AttributeAccessor的子接口
BeanDefinition bd = new RootBeanDefinition(Book.class);
// 设置特点
bd.setAttribute("bookAttr", "a value");
// 检查和获取特点
if(bd.hasAttribute("bookAttr")) {
System.out.println("bookAttr: " + bd.getAttribute("bookAttr"));
// 移除特点
bd.removeAttribute("bookAttr");
System.out.println("bookAttr: " + bd.getAttribute("bookAttr"));
}
}
}

在这个比如中,咱们创立了一个RootBeanDefinition实例来描绘怎么创立一个Book类的实例。RootBeanDefinition是BeanDefinition的完成,而BeanDefinition完成了AttributeAccessor接口,因而RootBeanDefinition也就承继了AttributeAccessor的办法。

有人或许会疑问,Book并没有bookAttr这个成员变量,这是怎么赋值的?

在Spring结构中,AttributeAccessor接口界说的办法是为了附加、获取和移除与某个目标(例如RootBeanDefinition)相关联的元数据,而不是操作目标(例如Book)自身的字段。

所以,在RootBeanDefinition实例上调用setAttribute(“bookAttr”, “a value”)办法时,其实并不是在Book实例上设置一个名为bookAttr的字段。而是在RootBeanDefinition实例上附加了一个元数据,元数据的键是”bookAttr”,值是”a value”。

后续运用getAttribute(“bookAttr”)办法时,它将回来之前设置的元数据值”a value”,而不是测验访问Book类的bookAttr字段(实践上Book类并没有bookAttr字段)。

简略来说,这些元数据是附加在RootBeanDefinition目标上的,而不是附加在由RootBeanDefinition目标描绘的Book实例上的。

运转成果:

BeanDefinition解密:构建和管理Spring Beans的基石

总结:

BeanDefinition是完成了AttributeAccessor接口的一个重要的类,BeanDefinition 目标是 Spring 结构用来存储 bean 装备信息的数据结构。当咱们在装备类中运用 @Bean、@Scope、@Lazy 等注解界说一个 bean 时,Spring 会为这个 bean 创立一个 BeanDefinition 目标,并将这些注解的元数据附加到这个 BeanDefinition 目标上。

当 Spring 容器在后续需求创立 bean实例时,它会检查这个 BeanDefinition 目标,按照其间的元数据(如 scope、lazy 初始化、初始化和毁掉办法等)来创立和办理 bean实例。这些元数据并不会直接附加到 bean实例上,而是存储在 BeanDefinition 目标中,由 Spring 容器来办理和运用。

所以,当咱们在 main 办法中从 ApplicationContext 获取 BeanDefinition 并打印其特点时,咱们实践上是在检查 Spring 结构用来办理 bean 的内部数据结构,而不是直接检查 bean 实例自身的状况。

这种办法的好处是,它将这些额定的元数据与bean实例自身分离,这样就能够在不修改bean类的情况下灵敏地改动这些元数据,而且AttributeAccessor能够在同一个JVM进程中的不同线程间同享数据。这也是为什么咱们能够经过修改装备文件或注解来改动bean的规模、是否是懒加载等,而不需求修改bean的类界说。

3. BeanDefinition回顾及总结

在咱们深化探讨Spring结构的过程中,咱们已经了解了BeanDefinition是Spring中十分要害的一个概念。BeanDefinition的首要职责是作为一个数据目标,存储了关于怎么创立、初始化、装备一个详细的Bean实例的详细信息。

特别是,BeanDefinition中包含以下首要信息:

  • 彻底限制的类名,以便Spring容器经过反射创立Bean实例。
  • Bean的称号和别名,用于在运用中引证和查找Bean。
  • Bean的效果域,如单例或原型,决定了Spring怎么办理Bean实例的生命周期。
  • 结构函数参数和特点值,用于实例化Bean和依靠注入。
  • 主动安装形式,指示Spring怎么主动注入依靠。
  • 初始化和毁掉办法,让Spring知道在Bean生命周期的特定时间怎么履行自界说逻辑。
  • Bean的依靠联系,告知Spring在创立当时Bean之前需求先创立哪些Bean。

不管咱们运用哪种装备办法(XML、注解或Java装备),Spring在发动时都会解析这些装备,然后生成相应的BeanDefinition目标。这些BeanDefinition目标就像是Spring容器内部的配方,告知Spring容器怎么创立和装备各个Bean。

点击重视,第一时间了解华为云新鲜技能~