🔍Spring Bean创建机制揭秘:6个让你

🔍Spring Bean创建机制揭秘:6个让你"原来如此"的源码瞬间

Scroll Down

🔍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:

  1. beanDefinitionMap:存储Bean的定义信息(BeanDefinition)
  2. singletonObjects:存储已经创建好的单例Bean实例

当你调用getBean()方法时,Spring会:

  1. 先从singletonObjects中查找是否已经存在实例
  2. 如果不存在,则根据beanDefinitionMap中的定义信息创建Bean
  3. 创建完成后放入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")时:

  1. 检查缓存:Spring首先检查singletonObjects中是否有名为"userService"的Bean
  2. 创建Bean:如果没有,Spring会创建UserService的实例
  3. 放入缓存:创建完成后,将实例放入singletonObjects
  4. 返回实例:返回这个实例

当我们第二次调用getBean("userService")时:

  1. 检查缓存:Spring再次检查singletonObjects
  2. 直接返回:发现已经存在,直接返回缓存的实例,不会创建新的

让我们看看这个过程的源码实现:

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默认单例的原因:

  1. 性能考虑:避免重复创建对象的开销
  2. 内存优化:减少内存占用
  3. 状态共享:某些Bean需要在多个组件间共享状态
  4. 资源管理:如数据库连接池、线程池等资源通常只需要一个实例

这种设计体现了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命名规范的特殊情况:

  1. 普通类名UserServiceuserService
  2. 全大写缩写URLURL(保持不变)
  3. 混合情况URLServiceURLService(保持不变)

让我们通过一个具体的例子来理解:

@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;
        }
    }
}

小李:这个设计真是太精妙了!通过回调模式,既保证了线程安全,又实现了延迟创建。

小王:没错!这种设计有几个优点:

  1. 线程安全:使用synchronized保证并发安全
  2. 延迟创建:只有在真正需要时才创建Bean
  3. 避免重复创建:双重检查锁定模式
  4. 解耦:创建逻辑与获取逻辑分离

第五个豁然开朗:依赖注入的实现机制

小李:我还想了解依赖注入是如何实现的,这个过程在源码中是怎么体现的?

小王:依赖注入是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生命周期的不同阶段进行干预。主要的后置处理器包括:

  1. InstantiationAwareBeanPostProcessor:在实例化前后调用
  2. BeanPostProcessor:在初始化前后调用
  3. 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时,过程如下:

  1. 创建A的实例(此时A还未完全初始化)
  2. 将A的工厂对象放入三级缓存
  3. 开始注入A的依赖,发现需要B
  4. 创建B的实例
  5. B需要注入A,从三级缓存中获取A的早期引用
  6. B创建完成,放入一级缓存
  7. 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容器有了全新的认识。以前只是知道概念,现在真正理解了实现的原理。

小王:这就是源码调试的魅力所在!通过源码调试,我们不仅能够:

  1. 验证理论知识:将抽象的概念转化为具体的代码实现
  2. 学习设计模式:在Spring源码中,我们可以看到单例模式、工厂模式、模板方法模式等的实际应用
  3. 提升编程能力:学习优秀的代码设计和实现技巧
  4. 解决实际问题:当遇到问题时,能够从源码层面找到根本原因

小李:那对于想要深入理解Spring的开发者,你有什么建议吗?

小王:我建议按照以下步骤进行:

  1. 从简单的Bean创建开始:先理解基本的Bean创建流程
  2. 逐步深入复杂场景:研究依赖注入、循环依赖等复杂情况
  3. 结合设计模式学习:在源码中识别和应用各种设计模式
  4. 动手调试:在IDE中设置断点,跟踪Bean的创建过程
  5. 记录心得:将每次的"豁然开朗"瞬间记录下来,形成自己的知识体系

小李:谢谢小王的详细讲解!通过今天的源码调试,我对Spring的理解更加深入了。

小王:不客气!记住,源码是最好的老师。在技术学习的道路上,理论结合实践,特别是通过源码调试来验证和理解原理,是最有效的学习方法。希望你在后续的学习中能够发现更多令人兴奋的"豁然开朗"瞬间!


结语

源码调试不仅是一种技术手段,更是一种学习态度。通过深入Spring IoC容器的源码世界,我们不仅理解了其工作原理,更重要的是学会了如何通过源码来验证和理解技术原理。

在技术学习的道路上,愿我们都能保持这种探索精神,在源码的海洋中发现更多令人兴奋的"原来如此"时刻!


本文基于Spring 5.2.25.RELEASE版本,通过对话的形式,深入浅出地介绍了Spring IoC容器创建Bean过程中的关键实现细节。希望这篇文章能够帮助读者更好地理解Spring框架的核心原理,并在源码调试的过程中获得更多的"豁然开朗"体验。