本文同享自华为云社区《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的大部分办法,展示了它们的效果。请注意,在这个比如中,一些办法如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咱们能够得到如下的承继联系图:
虽然有许多接口、抽象类和扩展,咱们只需求重视其间的要害部分。
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是完成了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。
点击重视,第一时间了解华为云新鲜技能~