🔍Spring Bean创建机制揭秘:6个让你"原来如此"的源码瞬间
在技术学习的道路上,理论知识的掌握固然重要,但真正让人豁然开朗的,往往是在源码调试过程中亲眼见证那些曾经只存在于概念中的原理。今天,让我们跟随小李和小王的对话,一起走进Spring IoC容器的源码世界,体验那些令人兴奋的"原来如此"时刻。
本文基于Spring 5.2.25.RELEASE版本进行源码分析
引言:从理论到实践的跨越
小李:小王,我最近在研究Spring IoC容器创建Bean的过程时,发现了一个有趣的现象。以前别人告诉我的很多理论,比如"Spring IoC容器是什么"、“Spring Bean为什么默认是单例的”、"Spring Bean的名字为什么默认是类名首字母小写"等等,这些概念在Spring源码中都能找到对应的实现。那种感觉就像是在看一部悬疑电影,当所有的线索都串联起来时,整个故事变得清晰明了。
小王:哈哈,你说得很对!这就是源码阅读的魅力所在。很多开发者停留在"知其然"的层面,而通过源码调试,我们能够达到"知其所以然"的境界。Spring框架作为Java生态中最成功的框架之一,其源码设计精妙,处处体现着设计模式和编程思想。今天我们就一起来探索Spring IoC容器创建Bean过程中的那些"豁然开朗"瞬间。
第一个豁然开朗:IoC容器的本质
小李:首先我想问的是,Spring IoC容器到底是什么?在源码中它是如何体现的?
小王:这是一个很好的问题!让我们从源码的角度来理解IoC容器的本质。
在Spring 5.2.25.RELEASE源码中,IoC容器的核心接口是BeanFactory
,它的实现类DefaultListableBeanFactory
就是整个IoC容器的心脏。让我们看看关键代码:
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
// ... 其他方法
}
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
// 存储Bean定义的Map
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// 存储单例Bean的Map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
}
小李:哇!原来IoC容器本质上就是一个Map!这个发现让我豁然开朗。
小王:没错!IoC容器的核心就是两个Map:
beanDefinitionMap
:存储Bean的定义信息(BeanDefinition)singletonObjects
:存储已经创建好的单例Bean实例
当你调用getBean()
方法时,Spring会:
- 先从
singletonObjects
中查找是否已经存在实例 - 如果不存在,则根据
beanDefinitionMap
中的定义信息创建Bean - 创建完成后放入
singletonObjects
中,下次直接返回
这就是IoC容器的核心原理:管理Bean的生命周期,提供Bean的获取服务。
第二个豁然开朗:Bean默认单例的秘密
小李:接下来我想了解的是,为什么Spring Bean默认是单例的?这个设计有什么深意?
小王:这是一个很好的问题!让我们通过源码来深入理解Spring Bean默认单例的设计原理。
首先,让我们看看Spring中Bean的作用域是如何定义的。在Spring 5.2.25.RELEASE中,Bean的作用域是通过Scope
接口来管理的:
public interface Scope {
Object get(String name, ObjectFactory<?> objectFactory);
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
Object resolveContextualObject(String key);
String getConversationId();
}
小李:这个Scope
接口看起来很简单,它怎么就能控制Bean是单例还是多例呢?
小王:好问题!让我们看看Spring是如何通过不同的Scope
实现来控制Bean的生命周期的。
在Spring中,主要有两种作用域:单例(Singleton)和原型(Prototype)。让我们先看看单例作用域的实现:
public class SingletonScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 单例作用域的核心逻辑:每次都返回同一个对象
return objectFactory.getObject();
}
@Override
public Object remove(String name) {
// 单例作用域不支持移除
throw new UnsupportedOperationException("Singleton scope does not support removal");
}
// ... 其他方法
}
小李:等等,这个SingletonScope
的实现看起来很简单啊,它怎么保证每次都返回同一个对象呢?
小王:哈哈,你观察得很仔细!实际上,SingletonScope
本身并不负责缓存对象,真正的单例管理是在DefaultSingletonBeanRegistry
中实现的。
让我们看看Spring是如何在DefaultSingletonBeanRegistry
中管理单例Bean的:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 存储单例Bean的Map - 这是单例模式的核心!
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 存储单例Bean的工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 存储早期暴露的单例Bean(用于解决循环依赖)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
@Override
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 首先从一级缓存中获取完全初始化好的Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 如果一级缓存中没有,且Bean正在创建中,尝试从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 如果二级缓存中也没有,尝试从三级缓存获取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
小李:哇!现在我明白了!原来单例的核心就是这个singletonObjects
Map!每次获取Bean时,Spring都会先从这个Map中查找,如果找到了就直接返回,如果没找到才创建新的。
小王:完全正确!这就是Spring单例模式的精髓。让我们通过一个具体的例子来理解这个过程:
@Component
public class UserService {
private String name = "default";
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
当我们第一次调用getBean("userService")
时:
- 检查缓存:Spring首先检查
singletonObjects
中是否有名为"userService"的Bean - 创建Bean:如果没有,Spring会创建UserService的实例
- 放入缓存:创建完成后,将实例放入
singletonObjects
中 - 返回实例:返回这个实例
当我们第二次调用getBean("userService")
时:
- 检查缓存:Spring再次检查
singletonObjects
- 直接返回:发现已经存在,直接返回缓存的实例,不会创建新的
让我们看看这个过程的源码实现:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 再次检查是否已经存在
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 如果不存在,才创建新的实例
singletonObject = singletonFactory.getObject();
// 创建完成后,放入缓存
addSingleton(beanName, singletonObject);
}
return singletonObject;
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
小李:那为什么要设计成默认单例呢?有什么好处吗?
小王:这是一个很好的问题!Spring设计默认单例有几个重要原因:
1. 性能优化
// 单例模式避免了重复创建对象的开销
// 每次getBean()都是同一个实例,不需要new操作
UserService userService1 = context.getBean("userService");
UserService userService2 = context.getBean("userService");
// userService1 和 userService2 是同一个对象
2. 内存优化
// 在大型应用中,如果每个Bean都是多例,会占用大量内存
// 单例模式确保每个Bean只有一个实例
@Component
public class DatabaseConnection {
// 数据库连接池,只需要一个实例就够了
}
3. 状态共享
@Component
public class CacheManager {
private Map<String, Object> cache = new HashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
}
// 多个组件可以共享同一个CacheManager实例
4. 资源管理
@Component
public class ThreadPoolManager {
private ExecutorService executorService = Executors.newFixedThreadPool(10);
public void execute(Runnable task) {
executorService.execute(task);
}
}
// 线程池管理器,通常只需要一个实例
小李:那如果我不想用单例,想要每次都是新的实例怎么办?
小王:好问题!Spring提供了多种方式来创建非单例Bean:
1. 使用@Scope注解
@Component
@Scope("prototype") // 每次获取都是新实例
public class PrototypeBean {
private String name = "prototype";
public void setName(String name) {
this.name = name;
}
}
2. 在XML配置中指定
<bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype"/>
3. 使用@Bean方法
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
让我们看看原型作用域的实现:
public class PrototypeScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 原型作用域:每次都创建新实例
return objectFactory.getObject();
}
@Override
public Object remove(String name) {
// 原型作用域不支持移除,因为没有缓存
return null;
}
}
小李:现在我完全明白了!单例模式的核心就是通过Map缓存来实现的,每次获取Bean时先检查缓存,有就直接返回,没有才创建新的。
小王:没错!这就是Spring单例模式的精髓。通过这种设计,Spring既保证了性能,又提供了灵活性。你可以根据实际需求选择单例还是原型作用域。
总结一下,Spring Bean默认单例的原因:
- 性能考虑:避免重复创建对象的开销
- 内存优化:减少内存占用
- 状态共享:某些Bean需要在多个组件间共享状态
- 资源管理:如数据库连接池、线程池等资源通常只需要一个实例
这种设计体现了Spring框架"约定优于配置"的理念,既提供了合理的默认行为,又保持了足够的灵活性。
第三个豁然开朗:Bean命名规则的真相
小李:还有一个让我困惑的问题,为什么Spring Bean的名字默认是类名首字母小写?这个规则是怎么实现的?
小王:这个问题问得很好!让我们看看Spring 5.2.25.RELEASE源码中Bean命名规则的实现。
在Spring 5.2.25.RELEASE中,Bean命名规则主要通过AnnotationBeanNameGenerator
来实现:
public class AnnotationBeanNameGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
if (beanClassName != null) {
// 获取类名
String shortClassName = ClassUtils.getShortNameAsProperty(beanClassName);
// 应用首字母小写规则
return Introspector.decapitalize(shortClassName);
}
return null;
}
}
小李:这个Introspector.decapitalize
方法是什么?能详细解释一下吗?
小王:Introspector.decapitalize
是Java标准库中的方法,在Spring 5.2.25.RELEASE中被用来处理Bean的命名。让我们看看它的实现逻辑:
// 这是Java标准库中的方法,Spring直接使用
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
这个方法的设计考虑了Java命名规范的特殊情况:
- 普通类名:
UserService
→userService
- 全大写缩写:
URL
→URL
(保持不变) - 混合情况:
URLService
→URLService
(保持不变)
让我们通过一个具体的例子来理解:
@Component
public class UserService {
// 这个Bean的默认名称将是 "userService"
}
@Component
public class URLService {
// 这个Bean的默认名称将是 "URLService"(不是 "urlService")
}
@Component
public class URL {
// 这个Bean的默认名称将是 "URL"(保持不变)
}
小李:这个设计真是太巧妙了!既遵循了Java的命名规范,又保持了代码的可读性。
第四个豁然开朗:Bean创建过程的精妙设计
小李:我还想了解Bean的创建过程,这个过程在源码中是如何实现的?
小王:Bean的创建过程是Spring IoC容器的核心,让我们通过源码来理解这个过程。
在AbstractBeanFactory
中,getBean()
方法是Bean创建的入口:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
// 1. 转换Bean名称
String beanName = transformedBeanName(name);
// 2. 尝试从缓存中获取单例Bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
return (T) sharedInstance;
}
// 3. 检查Bean定义是否存在
BeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 4. 创建Bean实例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
return createBean(beanName, mbd, args);
});
return (T) sharedInstance;
}
}
}
小李:这个getSingleton
方法看起来很有意思,它接受一个ObjectFactory
参数,这是什么设计模式?
小王:很好的观察!这里使用的是回调模式(Callback Pattern),也称为模板方法模式的变体。
让我们看看getSingleton
的实现:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 再次检查是否已经存在
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 调用回调方法创建Bean
singletonObject = singletonFactory.getObject();
// 添加到单例缓存
addSingleton(beanName, singletonObject);
}
return singletonObject;
}
}
}
小李:这个设计真是太精妙了!通过回调模式,既保证了线程安全,又实现了延迟创建。
小王:没错!这种设计有几个优点:
- 线程安全:使用synchronized保证并发安全
- 延迟创建:只有在真正需要时才创建Bean
- 避免重复创建:双重检查锁定模式
- 解耦:创建逻辑与获取逻辑分离
第五个豁然开朗:依赖注入的实现机制
小李:我还想了解依赖注入是如何实现的,这个过程在源码中是怎么体现的?
小王:依赖注入是Spring IoC容器的另一个核心特性。让我们看看它的实现机制。
在AbstractAutowireCapableBeanFactory
中,Bean的创建和依赖注入是同时进行的:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化Bean
Object beanInstance = doCreateBean(beanName, mbd, args);
// 2. 应用后置处理器
Object exposedObject = beanInstance;
try {
// 3. 属性注入
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
// 异常处理
}
return exposedObject;
}
}
小李:这个populateBean
方法就是负责依赖注入的吗?
小王:是的!populateBean
方法负责属性注入,让我们看看它的实现:
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
// 1. 获取属性值
PropertyValues pvs = mbd.getPropertyValues();
if (bw == null) {
if (!pvs.isEmpty()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Cannot apply property values to null instance");
}
return;
}
// 2. 应用后置处理器
boolean continueWithPropertyPopulation = true;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false;
break;
}
}
}
}
if (!continueWithPropertyPopulation) {
return;
}
// 3. 自动装配
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// 按名称自动装配
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// 按类型自动装配
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
// 4. 应用属性值
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
小李:这个过程中涉及了很多后置处理器,它们的作用是什么?
小王:后置处理器是Spring框架的扩展点,它们允许我们在Bean生命周期的不同阶段进行干预。主要的后置处理器包括:
- InstantiationAwareBeanPostProcessor:在实例化前后调用
- BeanPostProcessor:在初始化前后调用
- DestructionAwareBeanPostProcessor:在销毁时调用
让我们看一个具体的例子:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Bean初始化前:" + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean初始化后:" + beanName);
return bean;
}
}
小李:这种设计真是太灵活了!通过后置处理器,我们可以实现很多自定义的功能。
第六个豁然开朗:循环依赖的解决方案
小李:我还想了解Spring是如何解决循环依赖的,这个问题在源码中是如何处理的?
小王:循环依赖是Spring IoC容器中的一个经典问题。Spring通过三级缓存机制来解决这个问题。
让我们看看DefaultSingletonBeanRegistry
中的三级缓存:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 一级缓存:存储完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存储早期暴露的Bean(未完全初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:存储Bean的工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
小李:这三个缓存的作用是什么?它们是如何配合工作的?
小王:让我通过一个具体的例子来说明。假设有两个类A和B相互依赖:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
当Spring创建Bean A时,过程如下:
- 创建A的实例(此时A还未完全初始化)
- 将A的工厂对象放入三级缓存
- 开始注入A的依赖,发现需要B
- 创建B的实例
- B需要注入A,从三级缓存中获取A的早期引用
- B创建完成,放入一级缓存
- A的依赖注入完成,A也放入一级缓存
让我们看看关键的源码实现:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 创建Bean实例
Object beanInstance = createBeanInstance(beanName, mbd, args);
// 2. 将Bean的工厂对象放入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性注入
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 将工厂对象放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 从二级缓存中移除
this.earlySingletonObjects.remove(beanName);
}
}
}
小李:这个设计真是太巧妙了!通过三级缓存,既解决了循环依赖问题,又保证了Bean的正确初始化。
总结:源码调试的价值
小李:通过今天的源码调试,我对Spring IoC容器有了全新的认识。以前只是知道概念,现在真正理解了实现的原理。
小王:这就是源码调试的魅力所在!通过源码调试,我们不仅能够:
- 验证理论知识:将抽象的概念转化为具体的代码实现
- 学习设计模式:在Spring源码中,我们可以看到单例模式、工厂模式、模板方法模式等的实际应用
- 提升编程能力:学习优秀的代码设计和实现技巧
- 解决实际问题:当遇到问题时,能够从源码层面找到根本原因
小李:那对于想要深入理解Spring的开发者,你有什么建议吗?
小王:我建议按照以下步骤进行:
- 从简单的Bean创建开始:先理解基本的Bean创建流程
- 逐步深入复杂场景:研究依赖注入、循环依赖等复杂情况
- 结合设计模式学习:在源码中识别和应用各种设计模式
- 动手调试:在IDE中设置断点,跟踪Bean的创建过程
- 记录心得:将每次的"豁然开朗"瞬间记录下来,形成自己的知识体系
小李:谢谢小王的详细讲解!通过今天的源码调试,我对Spring的理解更加深入了。
小王:不客气!记住,源码是最好的老师。在技术学习的道路上,理论结合实践,特别是通过源码调试来验证和理解原理,是最有效的学习方法。希望你在后续的学习中能够发现更多令人兴奋的"豁然开朗"瞬间!
结语
源码调试不仅是一种技术手段,更是一种学习态度。通过深入Spring IoC容器的源码世界,我们不仅理解了其工作原理,更重要的是学会了如何通过源码来验证和理解技术原理。
在技术学习的道路上,愿我们都能保持这种探索精神,在源码的海洋中发现更多令人兴奋的"原来如此"时刻!
本文基于Spring 5.2.25.RELEASE版本,通过对话的形式,深入浅出地介绍了Spring IoC容器创建Bean过程中的关键实现细节。希望这篇文章能够帮助读者更好地理解Spring框架的核心原理,并在源码调试的过程中获得更多的"豁然开朗"体验。