SpringFramework源码解析——Bean在Spring中是如何被创建的?
1. 背景
我们使用Spring是为了让Spring帮我们管理Bean(也就是依赖)。如果只是简单地使用Spring创建的Bean,便无需了解Spring内部发生了什么;但如果想要得到更定制化的Bean,便需要对Spring创建Bean的过程进行扩展,也就需要了解Bean在Spring中是如何被创建的。
Bean,Bean定义,Bean实例有什么区别?
- Bean是可重复使用的组件,也是对依赖的描述;
- Bean定义则用来记录Bean的信息;
- Bean实例是通过Bean定义创建的实例,也就是实际使用的依赖。
获取一个Bean和创建一个Bean有什么区别?
- 对于单例Bean,在首次获取Bean的时候会创建Bean,后续获取Bean会从缓存中获取;
- 对于原型Bean,在每次获取Bean的时候都会创建一个新的Bean。
2. 如果让你设计,你会如何设计?
简单地说,创建Bean无非就是根据Bean定义创建一个Bean实例。下面让我们看看创建一个Bean需要哪些步骤:
- 实例化Bean:根据Bean定义获取到
class
类型,并通过反射的方式创建一个Bean实例; - 设置属性值:根据Bean定义获取到属性值,并通过反射的方式为Bean实例设置属性值;
- 执行初始化方法:根据Bean定义获取到初始化方法,并通过反射的方式执行初始化方法。
3. Spring是如何设计的?
同我们上面的设计类似,不过Spring为了满足更多更复杂的场景,其提供了更多的扩展点,如为依赖注入,AOP,Aware
接口等提供支持。Bean的创建由BeanFactory#getBean
方法触发,会依次执行以下方法:
AbstractBeanFactory#doGetBean
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
3.1. AbstractBeanFactory#doGetBean 详解
3.1.1. 转换传入的名称
DefaultListableBeanFactory
维护了一个Bean名称与BeanDefinition
的映射(也就是beanDefinitionMap
),我们通过Bean名称可以去该映射中获取到BeanDefinition
,用于后续的创建Bean的操作。那为什么还需要转换传入的名称呢?其实传入的名称不一定是映射中的Bean名称,主要有以下两种场景:
- 当获取
FactoryBean
类型的Bean时。假如有一个Bean名称为factoryBean
的FactoryBean
:
- 当传入的名称为
factoryBean
时,会返回FactoryBean
工厂创建的Bean; - 当传入的名称为
&factoryBean
时,会返回FactoryBean
工厂。
- 当传入的名称是别名时,需要根据别名找到实际的Bean名称。
对应源码所在位置:
AbstractBeanFactory#transformedBeanName
。
3.1.2. 从单例缓存中获取Bean
单例Bean首次获取并创建后会被添加到单例缓存中去,后续再次获取该Bean时,会直接从缓存中获取。这也是为什么单例Bean每次通过getBean
获取时都会返回同一个对象的原因。
对应源码所在位置:
DefaultSingletonBeanRegistry#getSingleton
。
3.1.3. 从父BeanFactory中获取Bean
如果当前BeanFactory
中不包含Bean名称对应的BeanDefinition
,且当前BeanFactory
有父BeanFactory
,则委派给父BeanFactory
进行Bean的获取。
3.1.4. 合并BeanDefinition
如果Bean名称对应的BeanDefinition
配置了父BeanDefinition
,则需要进行BeanDefinition
的合并。由子BeanDefinition
的信息覆盖父BeanDefinition
中的信息。这也是XML配置中的bean
标签的parent
属性的原理。
对应源码所在位置:
AbstractBeanFactory#getMergedLocalBeanDefinition
。
3.1.5. 检查依赖项
如果Bean名称对应的BeanDefinition
配置了dependsOn
依赖项,则在创建当前Bean之前,需要确保该Bean所依赖的Bean都已经被初始化。这也是XML配置中bean
标签的depends-on
属性和注解配置中@DependsOn
的原理。
3.1.6. 根据作用域来创建Bean
创建单例Bean
如果Bean名称对应的BeanDefinition
是单例的,则调用DefaultSingletonBeanRegistry#getSingleton
包装过的AbstractAutowireCapableBeanFactory#createBean
创建Bean。Bean在被创建后会加入到单例缓存中,这也是为什么单例Bean每次获取时都会返回相同对象的原因。创建原型Bean
如果Bean名称对应的BeanDefinition
是原型的,则调用AbstractAutowireCapableBeanFactory#createBean
创建Bean。相比于单例Bean,Bean在创建后并不会加入到单例缓存中,这也是为什么原型Bean每次获取时都会返回不同对象的原因。创建自定义作用域Bean
如果Bean名称对应的BeanDefinition
是自定义的,则调用自定义作用域Scope#get
包装过的AbstractAutowireCapableBeanFactory#createBean
创建Bean。这也是session
和request
等自定义作用域工作的原理。
对应源码所在位置:
DefaultSingletonBeanRegistry#getSingleton
。
3.1.7. 从Bean实例中获取对象
如果传入的名称不是以&
开头,并且Bean的实例是FactoryBean
时,则需要调用FactoryBean#getObject
方法得到工厂创建的Bean。这也是为什么使用Bean名称获取FactoryBean
类型的Bean时,会返回FactoryBean
工厂创建的Bean的原因。
对应源码所在位置:
AbstractBeanFactory#getObjectForBeanInstance
。
3.1.8. 适配Bean实例的类型
如果创建的Bean不是所需的Class
类型,则需要进行类型转换,然后再返回。
对应源码所在位置:
AbstractBeanFactory#adaptBeanInstance
。
3.2. AbstractAutowireCapableBeanFactory#createBean 详解
3.2.1. 解析Bean的Class
如果Bean名称对应的BeanDefinition
中的Class
还未被解析,则需要将BeanDefinition
中的beanClassName
解析为具体的Class
。这也是XML配置中bean
标签的class
属性的原理。
对应源码所在位置:
AbstractBeanFactory#resolveBeanClass
。
3.2.2. 准备方法重写
如果Bean名称对应的BeanDefinition
配置了方法重写,则需要校验待重写的方法是否存在,并且标记是否为重载。主要用于XML配置中lookup-method
和replace-method
标签及注解配置中的@Lookup
的前置校验。
lookup-method
:用于重写指定Bean的某个方法,使其返回指定的Bean,也是方法注入的实现。replace-method
:用于重写指定Bean的某个方法,使其委派给指定MethodReplacer
的reimplement
方法。
对应源码所在位置:
AbstractBeanDefinition#prepareMethodOverrides
。
3.2.3. 实例化前解析Bean
依次执行InstantiationAwareBeanPostProcessor
后置处理器的postProcessBeforeInstantiation
方法,如果返回的Bean不为空,则依次执行BeanPostProcessor
后置处理器的postProcessAfterInitialization
方法,如果返回的Bean不为空,则跳过后续的创建Bean实例的步骤直接返回该Bean实例。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
。
3.2.4. 创建Bean实例
调用AbstractAutowireCapableBeanFactory#doCreateBean
方法创建Bean实例,详见下文。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#doCreateBean
。
3.3. AbstractAutowireCapableBeanFactory#doCreateBean 详解
3.3.1. 实例化Bean
实例化Bean是通过反射的方式创建Bean的实例。有以下几个步骤:
- 如果Bean名称对应的
BeanDefinition
配置了instanceSupplier
属性,则使用其get
方法实例化Bean; - 如果Bean名称对应的
BeanDefinition
配置了factoryMethodName
属性,则使用工厂方法实例化Bean。这也是XML配置中bean
标签的factory-method
和factory-bean
属性的原理; - 如果依次执行
SmartInstantiationAwareBeanPostProcessor
后置处理器的determineCandidateConstructors
方法后有返回候选构造器或Bean名称对应的BeanDefinition
中配置了构造器自动装配模式,或BeanDefinition
中配置了构造器参数,或显式传入了参数,则使用构造器注入的方式实例化Bean; - 如果Bean名称对应的
BeanDefinition
配置了首选构造器,则使用构造器注入的方式实例化Bean; - 如果以上条件均不满足,则使用无参构造器实例化Bean。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#createBeanInstance
。
3.3.2. 合并BeanDefinition后置处理
依次执行MergedBeanDefinitionPostProcessor
后置处理器的postProcessMergedBeanDefinition
方法,对已被合并的BeanDefinition
进行后置处理。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
。
3.3.3. 设置Bean的属性
设置Bean的属性是通过反射的方式将BeanDefinition
中配置的属性值设置到Bean的实例中去。有以下几个步骤:
- 依次执行
InstantiationAwareBeanPostProcessor
后置处理器的postProcessAfterInstantiation
方法,对Bean进行实例化后置处理,如果返回false
则跳过后续步骤; - 如果Bean名称对应的
BeanDefinition
配置了自动装配模式,则通过autowireByName
或autowireByType
进行自动装配,将装配好的属性设置到BeanDefinition
中去; - 依次执行
InstantiationAwareBeanPostProcessor
后置处理器的postProcessProperties
方法,对属性进行后置处理,如果返回null
则跳过后续步骤; - 如果Bean名称对应的
BeanDefinition
配置了dependencyCheck
依赖检查,则需要对BeanDefinition
中的属性值进行检查; - 如果Bean名称对应的
BeanDefinition
配置了PropertyValues
属性值,则需要进行属性值的应用,即通过反射的方式将属性名对应的属性值设置到Bean的实例对应的属性上。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#populateBean
3.3.4. 初始化Bean
初始化Bean是执行Bean实例的初始化方法。有以下几个步骤:
- 如果Bean是
Aware
接口的实例,则调用Aware
对应的set
方法。这也是BeanNameAware
,BeanClassLoaderAware
和BeanFactoryAware
的原理。 - 依次执行
BeanPostProcessor
后置处理器的postProcessBeforeInitialization
方法,对Bean进行初始化前后置处理; - 如果Bean是
InitializingBean
接口的实例,则调用Bean的afterPropertiesSet
方法进行初始化; - 如果Bean名称对应的
BeanDefinition
配置了initMethodNames
初始化方法名称,通过反射依次调用。这也是XML配置中bean
标签的init-method
属性的原理; - 依次执行
BeanPostProcessor
后置处理器的postProcessAfterInitialization
方法,对Bean进行初始化后后置处理; - 返回初始化后的Bean。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#initializeBean
。
3.3.5. 注册Bean的销毁回调
注册Bean的销毁回调是为了后续销毁Bean时执行Bean实例的销毁方法。
对应源码所在位置:
AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary
。
3.3.6. 销毁Bean
销毁Bean是执行Bean实例的销毁方法。有以下几个步骤:
- 依次执行
DestructionAwareBeanPostProcessor
后置处理器的postProcessBeforeDestruction
方法,对Bean进行销毁前前后置处理; - 如果Bean是
DisposableBean
接口的实例,则调用Bean的destroy
方法进行销毁; - 如果Bean名称对应的
BeanDefinition
配置了destroyMethodNames
销毁方法名称,通过反射依次调用。这也是XML配置中bean
标签的destroy-method
属性的原理。
对应源码所在位置:
DisposableBeanAdapter#destroy
。
3.4. Bean的创建的流程图
回顾上面三个步骤,Bean的创建的流程图如下:
4. 实战
4.1. 如何自定义一个线程级Bean的作用域?
首先什么是作用域?作用域就是一个Bean生效的范围,在这个范围内根据相同的Bean名称多次获取Bean都会返回同一个Bean。
- 如
singleton
作用域的Bean在IoC容器中生效。 - 如
prototype
作用域的Bean在每次获取Bean中生效。 - 如
request
作用域的Bean在每次请求中生效。 - 如
session
作用域的Bean在每次会话中生效。 - 如
thread
作用域的Bean在每个线程中生效。
为了自定义一个线程级Bean的作用域,需要我们提供一个线程私有的Bean的缓存用来缓存当前作用域已经被创建的Bean,不难想到可以使用ThreadLocal
,如下代码。
@Slf4j
public class ThreadScopeTest {
/**
* 一个简单的Java Bean
*/
private static class TestBean {
}
/**
* 自定义线程级作用域
*/
public static class ThreadScope implements Scope {
/**
* 线程级缓存,每个线程都有一个私有的缓存
*/
private static final ThreadLocal<Map<String, Object>> beanMapLocal = ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 获取当前线程的缓存
final Map<String, Object> beanMap = beanMapLocal.get();
// 从缓存中获取Bean
Object bean = beanMap.get(name);
if (bean == null) {
// 缓存中未获取到Bean,使用工厂创建Bean
bean = objectFactory.getObject();
// 将创建的Bean加入到缓存中
beanMap.put(name, bean);
}
return bean;
}
@Override
public Object remove(String name) {
final Map<String, Object> beanMap = beanMapLocal.get();
return beanMap.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册自定义线程级别作用域
beanFactory.registerScope("thread", new ThreadScope());
// 注册一个作用域为线程级别的 BeanDefinition
beanFactory.registerBeanDefinition("testBean", BeanDefinitionBuilder
.genericBeanDefinition(TestBean.class)
.setScope("thread")
.getBeanDefinition());
executeInNewThread(() -> {
log.info("{} get bean fist: {}", Thread.currentThread().getName(), beanFactory.getBean(TestBean.class));
log.info("{} get bean second: {}", Thread.currentThread().getName(), beanFactory.getBean(TestBean.class));
// 销毁作用域缓存里的Bean
beanFactory.destroyScopedBean("testBean");
});
executeInNewThread(() -> {
log.info("{} get bean fist: {}", Thread.currentThread().getName(), beanFactory.getBean(TestBean.class));
log.info("{} get bean second: {}", Thread.currentThread().getName(), beanFactory.getBean(TestBean.class));
// 销毁作用域缓存里的Bean
beanFactory.destroyScopedBean("testBean");
});
}
private static void executeInNewThread(Runnable runnable) {
new Thread(runnable).start();
}
}
运行结果如下图。线程级作用域的Bean是在每个线程中生效,体现在行为上是:
- 相同的线程之间的Bean是共享的,也意味着在根据相同的Bean名称在相同的线程执行
getBean
方法,会返回相同的Bean; - 不同的线程之间的Bean不是共享的,也意味着根据相同的Bean名称在不同的线程执行
getBean
方法,会返回不同的Bean。
Thread-1 get bean fist: com.remeio.upsnippet.spring.beanfactory.ThreadScopeTest$TestBean@5e98cb5f
Thread-0 get bean fist: com.remeio.upsnippet.spring.beanfactory.ThreadScopeTest$TestBean@49e16fa6
Thread-1 get bean second: com.remeio.upsnippet.spring.beanfactory.ThreadScopeTest$TestBean@5e98cb5f
Thread-0 get bean second: com.remeio.upsnippet.spring.beanfactory.ThreadScopeTest$TestBean@49e16fa6
4.2. 如何解决原型Bean依赖注入单例Bean导致原型失效的问题?
当使用@Autowired
将一个原型Bean依赖注入到一个单例Bean中时,该单例Bean每次获取该原型Bean都会得到同一个Bean,这和我们使用原型Bean的初衷相违背;原因是依赖注入仅会发生一次,后续都将使用被依赖注入的Bean。如何解决这个问题呢?核心思路在于每次获取原型Bean的时候都需要触发getBean
方法,使其触发作用域创建Bean的方法,有以下几种方式:
- 使用依赖查找
BeanFactory#getBean
代替依赖注入@Autowired
; - 使用方法覆盖
@Lookup
去代理获取原型Bean的方法。
@Slf4j
public class LookupTest {
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public static class SingletonTestBean {
@Autowired
private PrototypeTestBean prototypeTestBean;
@Autowired
private BeanFactory beanFactory;
public PrototypeTestBean getPrototypeTestBeanByAutowired() {
return prototypeTestBean;
}
@Lookup("prototypeTestBean")
public PrototypeTestBean getPrototypeTestBeanByLookup() {
return null;
}
public PrototypeTestBean getPrototypeTestBeanByGetBean() {
return beanFactory.getBean(PrototypeTestBean.class);
}
}
@Component("prototypeTestBean")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public static class PrototypeTestBean {
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SingletonTestBean.class, PrototypeTestBean.class);
context.refresh();
SingletonTestBean singletonTestBean = context.getBean(SingletonTestBean.class);
printInfo("@Autowired", singletonTestBean::getPrototypeTestBeanByAutowired);
printInfo("BeanFactory#getBean", singletonTestBean::getPrototypeTestBeanByGetBean);
printInfo("@Lookup", singletonTestBean::getPrototypeTestBeanByLookup);
}
private static void printInfo(String message, Supplier<PrototypeTestBean> supplier) {
PrototypeTestBean first = supplier.get();
PrototypeTestBean second = supplier.get();
log.info("Get prototype test bean by {}: {} == {}? {}",
message,
first,
second,
first == second);
}
}
运行结果如下图。可以看到使用依赖查找BeanFactory#getBean
和方法覆盖@Lookup
的方式每次获取原型Bean都会返回不同的Bean。
Get prototype test bean by @Autowired: com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@6973bf95 == com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@6973bf95? true
Get prototype test bean by BeanFactory#getBean: com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@229d10bd == com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@47542153? false
Get prototype test bean by @Lookup: com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@7a4ccb53 == com.remeio.upsnippet.spring.beanfactory.LookupTest$PrototypeTestBean@309e345f? false
4.3. 三级缓存是如何解决循环依赖的?
学习中,后续更新。