SpringFramework源码解析——Bean在Spring中是如何表示的?
Spring相当于一个画笔,你可以通过调节画笔的各项参数(Bean的配置项),如笔的粗细,颜色等,来帮我们画出多姿多彩的画(Bean)。那让我们一起来看看这个画笔有哪些参数?
1. 背景
为什么要搞清楚Bean在Spring中是如何表示的,主要有以下两个方面的原因:
- 对于使用者来说,在使用XML或注解配置Bean时,需要知道有哪些配置项,才能进行配置;
- 对于开发者来说,在Spring处理Bean的过程中,会依赖Bean在Spring中的表示,是阅读源码的前置知识。
2. 如果让你设计,你会如何设计?
我们使用Spring是为了让Spring帮我们创建和管理依赖(也就是Bean)。那创建一个依赖需要哪些配置项呢?不难想到以下这些配置项:
- 类的名称
- 属性的值,如果有的话
- 构造器参数的值,如果有的话
如果说我们还需要Spring帮我们做一些额外动作,如在Bean创建时或销毁时分别执行某个特定方法,我们可以添加以下这些配置项:
- 初始化方法
- 销毁方法
如果说我们并不仅限于使用构造器或属性的方式去创建一个依赖,而是通过工厂的方式去创建依赖,我们可以添加以下这些配置项:
- factory-bean,用于指定工厂
- factory-method,用于指定工厂中创建Bean的方法
- …
不难发现,对于开发者来说,当我们需要实现更多的功能,就需要更多的配置项,对Bean的表示也就越复杂;而对于使用者来说,也就有了更多可配置的地方。
Spring为了更好更全面地满足用户的需求,添加了许多不常用的功能,这样的好处是复杂度转移给了Spring的开发者,而使用者只需要关注自己的业务,然后根据业务做相应的配置即可,这也是一个成熟的企业级框架的设计思想。
为什么会有越来越多的配置项?拿手机的设置为例,起初可能只能简简单单的设置下壁纸,蓝牙等,随着需求的增加,现在的手机可以设置WIFI,静音模式等,本质上是为用户提供更定制化的功能。试想如果你的手机设置不支持设置WIFI,那是多么悲惨的一件事。
3. Spring是如何设计的?
3.1. 什么是BeanDefinition?
同我们上面的设计类似,Spring在我们设计的基础上,还维护了如是否延迟初始化、作用域、所需依赖等配置项。Spring使用BeanDefinition
来表示Bean,类图如下:
从继承关系上来看,BeanDefinition
继承了以下接口:
BeanMetadataElement
接口,用于返回Bean的元数据来源于哪里,也就是这个Bean的配置信息最初是在哪里设置的,比如是在XML中,还是在配置类中。AttributeAccessor
接口,用于记录一些内部的属性以供后续Spring处理BeanDefinition
的过程中使用。
3.2. BeanDefinition有哪些分类?
为了满足不同的场景,Spring提供了不同类型的BeanDefinition
,如下:
AbstractBeanDefinition
,是所有BeanDefinition
实现类的父类。- 当Spring容器内部处理Bean定义时,使用
RootBeanDefinition
。 - 当使用XML注册Bean定义时,使用
GenericBeanDefinition
。 - 当使用组件扫描注册Bean定义时,使用
ScannedGenericBeanDefinition
。 - 当使用编程式注册Bean定义时,使用
AnnotatedGenericBeanDefinition
。 - 当使用配置类的
@Bean
注册Bean定义时,使用ConfigurationClassBeanDefinition
。
3.3. 为什么要拆分出这么多的BeanDefinition?
从需求上来看,起初Spring只能通过XML配置文件注册Bean定义,使用的是RootBeanDefinition
的继承体系;后来为了能够通过注解注册Bean定义,在Spring2.5引入了GenericBeanDefinition
的继承体系。
从设计上来看,BeanDefinition
的不同的子类适用于不同的场景,如当使用注解的方式注册Bean定义时,需要携带所标注注解的信息(也就是AnnotationMetadata
),自然也就需要引入新的实现类。
汽车,公交车,货车虽然都是车,但有着不同的适用场景。想象一下一个货车拉了一满车的人,也能拉,就是不太合适。
4. BeanDefinition源码剖析
4.1. 如何表示构造器参数?
从设计上来看,构造器参数无非就参数的下标,及参数的值两部分。
我们可以通过维护一个Map
来维护多个构造器参数,键就代表参数的下标,值就代表参数的值;另外还需要额外添加一个List
来维护泛型参数。
ConstructorArgumentValues#ConstructorArgumentValues(ConstructorArgumentValues)
使用了原型模式,提供了同类型构造器以深拷贝对象。
4.2. 如何表示属性值?
从设计上来看,属性值无非就属性的名称,及属性的值两部分。
我们可以通过PropertyValue
类来代表一个属性值,其包含了属性的名称,属性的值,转换后的值,以及是否已被转换等字段。
为了方便维护多个属性值,我们可以通过MutablePropertyValues
来持有一个PropertyValue
的列表。为了方便对属性值的遍历,我们可以实现了PropertyValues
接口,本质也是一个PropertyValue
的迭代器。
MutablePropertyValues
使用了迭代器模式,方便对多个属性值进行遍历;并维护了多个属性值为其提供统一的访问操作,也是一种组合模式。
4.3. 如何实现通用的BeanDefinition?
BeanDefinition
的通用抽象实现是AbstractBeanDefinition
,也是所有BeanDefinition
实现类的父类,维护了Bean定义相关的配置信息,也提供了对其进行设置和获取的方法。
4.4. 如何实现基于注解的通用BeanDefinition
基于注解的通用BeanDefinition
是AnnotatedGenericBeanDefinition
,其维护了标注在该类或方法上的注解信息。类图如下:
AnnotatedTypeMetadata
代表了标注注解的元数据ClassMetadata
代表了类的元数据,如类的名称等MethodMetadata
代表了单个方法的元数据,如方法的名称等,其继承了AnnotatedTypeMetadata
接口AnnotationMetadata
是一个组合接口,代表了标注注解的元数据,类的元数据和多个方法的元数据。
我们可以通过
BeanDefinitionBuilder
来构建各种类型的BeanDefinition
,如GenericBeanDefinition
,RootBeanDefinition
,和ChildBeanDefinition
。BeanDefinitionBuilder
使用了Builder模式
5. 实战
5.1. 如何通过BeanDefinitionBuilder编程式创建并注册一个BeanDefinition?
我们使用BeanDefinitionBuilder
编程式创建一个TestBean
的BeanDefinition
,并注册到BeanFactory
中去,如下代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@Slf4j
public class BeanDefinitionBuilderTest {
public static void main(String[] args) {
testBeanDefinitionBuilder();
}
public static void testBeanDefinitionBuilder() {
// 使用 Builder 模式编程式声明一个 BeanDefinition
final BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(TestBean.class)
.addPropertyValue("name", "foo")
.addPropertyValue("age", 1)
.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON)
.setInitMethodName("initialize")
.getBeanDefinition();
final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册 BeanDefinition
beanFactory.registerBeanDefinition("testBean", beanDefinition);
// 获取 Bean
final Object bean = beanFactory.getBean("testBean");
printBeanInfo("BeanDefinitionBuilder", beanDefinition, bean);
}
private static void printBeanInfo(String from, BeanDefinition beanDefinition, Object bean) {
log.info("Loaded from: {}, bean definition type: {}, bean definition: {}, bean: {}", from, beanDefinition.getClass(), beanDefinition, bean);
}
}
运行结果如下,其实通过BeanDefinitionBuilder
编程式创建并注册一个BeanDefinition
在日常开发中并不常用,BeanDefinition
的创建和注册工作通常由Spring帮我们完成,我们只需要关注Bean是如何配置的即可。
TestBean(name=foo, age=1) had initialized
Loaded from: BeanDefinitionBuilder, bean definition type: class org.springframework.beans.factory.support.GenericBeanDefinition, bean definition: Generic bean: class [com.remeio.upsnippet.spring.beandefinition.TestBean]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=initialize; destroyMethodName=null, bean: TestBean(name=foo, age=1)
5.2. 使用不同注册BeanDefinition的方式会构建哪种类型的BeanDefinition?
我们编写一些测试方法,分别以XML的方式,编程式注册的方式,组件扫描的方式,配置类的@Bean的方式来注册BeanDefinition
,如下代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@Slf4j
public class BeanDefinitionTypeTest {
public static void main(String[] args) {
testGenericBeanDefinition();
testAnnotatedGenericBeanDefinition();
testScannedGenericBeanDefinition();
testConfigurationClassBeanDefinition();
}
public static void testGenericBeanDefinition() {
// 使用XML的方式
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(BeanDefinitionTypeTest.class.getPackage().getName().replace(".", "/") + "/beans.xml");
context.refresh();
final BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("testBean");
final TestBean bean = context.getBean(TestBean.class);
printBeanInfo("XML", beanDefinition, bean);
}
public static void testAnnotatedGenericBeanDefinition() {
// 使用编程式注册的方式
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestComponent.class);
context.refresh();
final BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("testComponent");
final TestComponent bean = context.getBean(TestComponent.class);
printBeanInfo("Programming", beanDefinition, bean);
}
public static void testScannedGenericBeanDefinition() {
// 使用组件扫描的方式
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan(BeanDefinitionTypeTest.class.getPackage().getName());
context.refresh();
final BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("testComponent");
final TestComponent bean = context.getBean(TestComponent.class);
printBeanInfo("Scan", beanDefinition, bean);
}
public static void testConfigurationClassBeanDefinition() {
// 使用配置类的@Bean的方式
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestConfiguration.class);
context.refresh();
final BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("testBean");
final TestBean bean = context.getBean(TestBean.class);
printBeanInfo("@Bean", beanDefinition, bean);
}
private static void printBeanInfo(String from, BeanDefinition beanDefinition, Object bean) {
log.info("Loaded from: {}, bean definition type: {}, bean definition: {}, bean: {}", from, beanDefinition.getClass(), beanDefinition, bean);
}
}
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
@Slf4j
@Data
public class TestBean {
private String name;
private int age;
public void initialize() {
log.info(this + " had initialized");
}
}
<?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 id="testBean" class="com.remeio.upsnippet.spring.beandefinition.TestBean">
<property name="name" value="foo"/>
<property name="age" value="1"/>
</bean>
</beans>
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TestComponent {
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfiguration {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
运行结果如下,不难看出当使用不同的方式注册BeanDefinition
时,其最终使用的BeanDefinition
的实现类也不同。
Loaded from: XML, bean definition type: class org.springframework.beans.factory.support.GenericBeanDefinition, bean definition: Generic bean: class [com.remeio.upsnippet.spring.beandefinition.TestBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [com/remeio/upsnippet/spring/beandefinition/beans.xml], bean: TestBean(name=foo, age=1)
Loaded from: Programming, bean definition type: class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition, bean definition: Generic bean: class [com.remeio.upsnippet.spring.beandefinition.TestComponent]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, bean: com.remeio.upsnippet.spring.beandefinition.TestComponent@78b729e6
Loaded from: Scan, bean definition type: class org.springframework.context.annotation.ScannedGenericBeanDefinition, bean definition: Generic bean: class [com.remeio.upsnippet.spring.beandefinition.TestComponent]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\project\remeio\upsnippet\upsnippet-spring\target\classes\com\remeio\upsnippet\spring\beandefinition\TestComponent.class], bean: com.remeio.upsnippet.spring.beandefinition.TestComponent@5b8dfcc1
Loaded from: @Bean, bean definition type: class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition, bean definition: Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=testConfiguration; factoryMethodName=testBean; initMethodName=null; destroyMethodName=(inferred); defined in com.remeio.upsnippet.spring.beandefinition.TestConfiguration, bean: TestBean(name=null, age=0)