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#doGetBeanAbstractAutowireCapableBeanFactory#createBeanAbstractAutowireCapableBeanFactory#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. 三级缓存是如何解决循环依赖的?
学习中,后续更新。
