深入理解 Spring IoC 容器:Bean 的生命周期与作用域

前言:回顾与展望

在上一篇文章中,我们从“没有 Spring 的世界”开始,逐步理解了 IoC(控制反转)和 DI(依赖注入)的核心思想。我们知道了:

  • IoC:将对象的创建和管理权交给容器
  • DI:容器负责建立对象之间的依赖关系
  • Spring 容器:一个强大的 IoC 容器,帮我们管理对象和依赖

文章最后留下了一个思考题:

如果我们有多个同类型的 Bean,Spring 该如何决定注入哪一个?

今天,我们就从这个思考题出发,深入 Spring IoC 容器的内部,一探究竟。同时,我们还将全面解析 Bean 的生命周期作用域,让你对 Spring 的理解从“怎么用”进阶到“为什么这样设计”。


第一部分:同类型 Bean 的歧义问题

场景还原

假设我们有这样一个场景:系统中有两个用户服务实现,一个从数据库查询,一个从缓存查询。

@Component
public class DatabaseUserService implements UserService {
    @Override
    public User getUserById(Long id) {
        System.out.println("从数据库查询用户: " + id);
        return new User(id, "数据库用户");
    }
}

@Component
public class CacheUserService implements UserService {
    @Override
    public User getUserById(Long id) {
        System.out.println("从缓存查询用户: " + id);
        return new User(id, "缓存用户");
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService;  // 这里该注入哪个?
    
    public void processOrder(Long userId) {
        User user = userService.getUserById(userId);
        System.out.println("处理订单: " + user.getName());
    }
}

启动 Spring 容器时,你会看到这样的错误:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.example.UserService' available: 
expected single matching bean but found 2: databaseUserService, cacheUserService

Spring 很诚实地说:“我不知道该注入哪个,你给我明确一下。”

解决方案一:@Primary——指定首选 Bean

@Component
@Primary  // 标记为首选 Bean
public class DatabaseUserService implements UserService {
    // ...
}

@Component
public class CacheUserService implements UserService {
    // ...
}

这样,当遇到歧义时,Spring 会优先选择带有 @Primary 的 Bean。

解决方案二:@Qualifier——精确指定 Bean 名称

@Component
@Qualifier("database")  // 给 Bean 起个名字
public class DatabaseUserService implements UserService {
    // ...
}

@Component
@Qualifier("cache")
public class CacheUserService implements UserService {
    // ...
}

@Component
public class OrderService {
    @Autowired
    @Qualifier("cache")  // 精确指定要注入哪个
    private UserService userService;
    
    // ...
}

解决方案三:@Resource——按名称注入(JSR-250 标准)

@Component
public class OrderService {
    @Resource(name = "databaseUserService")  // 按 Bean 名称注入
    private UserService userService;
    
    // ...
}

深入思考:为什么 Spring 要这样设计?

这个设计体现了 Spring 的一个重要哲学:明确优于隐式

当存在歧义时,Spring 选择报错而不是猜测,这避免了运行时出现难以排查的诡异问题。你必须明确告诉容器你的意图——这是一种保护机制。


第二部分:Bean 的生命周期全解析

理解了如何解决 Bean 的歧义问题,我们来看看 Bean 在 Spring 容器中从生到死的完整历程。

Bean 生命周期全景图

┌─────────────────────────────────────────────────────────────┐
│                    Bean 生命周期全景图                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. 实例化前                                                  │
│     ↓                                                        │
│  2. 实例化 (new)                                             │
│     ↓                                                        │
│  3. 属性注入                                                  │
│     ↓                                                        │
│  4. 初始化前 (Aware 接口回调)                                  │
│     ↓                                                        │
│  5. 初始化 (InitializingBean, @PostConstruct, init-method)  │
│     ↓                                                        │
│  6. 初始化后 (BeanPostProcessor)                              │
│     ↓                                                        │
│  7. 就绪使用                                                  │
│     ↓                                                        │
│  8. 销毁前 (@PreDestroy, DisposableBean, destroy-method)    │
│     ↓                                                        │
│  9. 销毁                                                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

实战演示:一个 Bean 的完整生命历程

让我们创建一个 Bean,实现各种生命周期回调接口,亲眼看看每个阶段是如何执行的。

@Component
public class LifecycleDemoBean implements BeanNameAware, BeanFactoryAware, 
                                          InitializingBean, DisposableBean {
    
    private String beanName;
    private BeanFactory beanFactory;
    
    public LifecycleDemoBean() {
        System.out.println("1. 构造函数执行:实例化 Bean");
    }
    
    @Autowired
    private SomeDependency someDependency;  // 假设有个依赖
    
    @Autowired
    public void setSomeDependency(SomeDependency someDependency) {
        System.out.println("2. 属性注入:设置依赖");
        this.someDependency = someDependency;
    }
    
    @Override
    public void setBeanName(String name) {
        System.out.println("3. BeanNameAware 回调:设置 Bean 名称为 " + name);
        this.beanName = name;
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("4. BeanFactoryAware 回调:获取 BeanFactory");
        this.beanFactory = beanFactory;
    }
    
    @PostConstruct
    public void postConstruct() {
        System.out.println("5. @PostConstruct 注解方法执行");
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("6. InitializingBean.afterPropertiesSet() 执行");
    }
    
    @Bean(initMethod = "customInit")
    public void customInit() {
        System.out.println("7. 自定义 init-method 执行");
    }
    
    public void doSomething() {
        System.out.println("Bean 正在执行业务方法");
    }
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("8. @PreDestroy 注解方法执行");
    }
    
    @Override
    public void destroy() {
        System.out.println("9. DisposableBean.destroy() 执行");
    }
    
    @Bean(destroyMethod = "customDestroy")
    public void customDestroy() {
        System.out.println("10. 自定义 destroy-method 执行");
    }
}

运行结果:

1. 构造函数执行:实例化 Bean
2. 属性注入:设置依赖
3. BeanNameAware 回调:设置 Bean 名称为 lifecycleDemoBean
4. BeanFactoryAware 回调:获取 BeanFactory
5. @PostConstruct 注解方法执行
6. InitializingBean.afterPropertiesSet() 执行
7. 自定义 init-method 执行
Bean 正在执行业务方法
8. @PreDestroy 注解方法执行
9. DisposableBean.destroy() 执行
10. 自定义 destroy-method 执行

生命周期各阶段详解

阶段1-2:实例化前与实例化

Spring 根据配置或注解找到 Bean 的定义,通过反射调用构造函数创建实例。

阶段3:属性注入

Spring 自动装配依赖(如 @Autowired@Resource 等)。

阶段4:Aware 接口回调

Spring 提供了一系列 Aware 接口,让 Bean 获取容器资源:

public interface BeanNameAware { void setBeanName(String name); }
public interface BeanFactoryAware { void setBeanFactory(BeanFactory factory); }
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext ctx); }
public interface EnvironmentAware { void setEnvironment(Environment env); }
// ... 等等
阶段5-7:初始化

三种初始化方式按顺序执行:

  1. @PostConstruct 注解方法
  2. InitializingBean 接口的 afterPropertiesSet()
  3. 自定义 init-method(如 @Bean(initMethod = "customInit")
阶段8:BeanPostProcessor——Spring 的魔法之源

这是 Spring 最强大的扩展点,AOP、事务管理等都是基于它实现的:

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof LifecycleDemoBean) {
            System.out.println("BeanPostProcessor before:初始化前处理");
        }
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof LifecycleDemoBean) {
            System.out.println("BeanPostProcessor after:初始化后处理,可以生成代理了");
            // 这里可以返回代理对象,实现 AOP
        }
        return bean;
    }
}
阶段9-10:销毁

三种销毁方式按顺序执行:

  1. @PreDestroy 注解方法
  2. DisposableBean 接口的 destroy()
  3. 自定义 destroy-method

第三部分:Bean 的作用域详解

同一个 Bean 定义,可以有不同的“存在范围”。Spring 提供了多种作用域:

1. Singleton(单例)——默认作用域

@Component
@Scope("singleton")  // 默认就是 singleton,可以不写
public class SingletonBean {
    // 整个容器中只有一个实例
}

特点

  • 容器启动时创建(默认 eager 初始化)
  • 所有地方使用同一个实例
  • 线程不安全(要避免成员变量共享)

2. Prototype(原型)

@Component
@Scope("prototype")
public class PrototypeBean {
    // 每次获取都创建新实例
}

特点

  • 每次 getBean() 或注入时创建新实例
  • 懒加载(使用时才创建)
  • 适合有状态的对象

3. Request(请求级)

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
    // 每个 HTTP 请求创建一个实例
}

适用场景:Web 应用中的请求上下文数据

4. Session(会话级)

@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean {
    // 每个 HTTP Session 创建一个实例
}

适用场景:用户登录信息、购物车等

5. Application(应用级)

@Component
@Scope("application")
public class ApplicationBean {
    // 整个 ServletContext 生命周期一个实例
}

作用域选择指南

场景 推荐作用域 原因
无状态服务(Service、DAO) Singleton 节省内存,性能好
有状态对象 Prototype 避免线程安全问题
请求上下文数据 Request 请求间隔离
用户会话数据 Session 用户间隔离

常见陷阱:Singleton 注入 Prototype

@Component
@Scope("singleton")
public class SingletonService {
    @Autowired
    private PrototypeBean prototypeBean;  // 问题:只注入一次
    
    public void doSomething() {
        prototypeBean.doSomething();  // 每次都是同一个实例!
    }
}

解决方案一:ApplicationContext 手动获取

@Component
public class SingletonService {
    @Autowired
    private ApplicationContext context;
    
    public void doSomething() {
        PrototypeBean bean = context.getBean(PrototypeBean.class);
        bean.doSomething();
    }
}

解决方案二:ObjectFactory(推荐)

@Component
public class SingletonService {
    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanFactory;
    
    public void doSomething() {
        PrototypeBean bean = prototypeBeanFactory.getObject();
        bean.doSomething();
    }
}

解决方案三:@Lookup 方法注入

@Component
public abstract class SingletonService {
    @Lookup  // Spring 会动态生成实现类
    protected abstract PrototypeBean getPrototypeBean();
    
    public void doSomething() {
        PrototypeBean bean = getPrototypeBean();
        bean.doSomething();
    }
}

第四部分:源码级解读——Spring 容器内部工作原理

理解了 Bean 的生命周期和作用域,我们来看看 Spring 容器内部到底是怎么工作的。

核心接口设计

// 1. BeanFactory——最基础的容器
public interface BeanFactory {
    Object getBean(String name);
    <T> T getBean(Class<T> requiredType);
    boolean containsBean(String name);
    boolean isSingleton(String name);
    boolean isPrototype(String name);
    // ...
}

// 2. ApplicationContext——高级容器(继承自 BeanFactory)
public interface ApplicationContext extends BeanFactory {
    // 增加了国际化、事件发布、资源访问等功能
}

简化版 IoC 容器实现原理

public class SimpleIoCContainer {
    // 存储 Bean 定义
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    
    // 存储单例 Bean 实例
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
    // 存储正在创建中的 Bean(用于检测循环依赖)
    private Set<String> singletonsCurrentlyInCreation = 
        Collections.newSetFromMap(new ConcurrentHashMap<>());
    
    // 注册 Bean 定义
    public void registerBeanDefinition(String name, BeanDefinition definition) {
        beanDefinitionMap.put(name, definition);
    }
    
    // 获取 Bean(核心方法)
    public Object getBean(String name) {
        // 1. 先从缓存中获取单例
        Object singleton = singletonObjects.get(name);
        if (singleton != null) {
            return singleton;
        }
        
        // 2. 检查是否循环依赖
        if (singletonsCurrentlyInCreation.contains(name)) {
            throw new RuntimeException("循环依赖 detected: " + name);
        }
        
        // 3. 获取 Bean 定义
        BeanDefinition definition = beanDefinitionMap.get(name);
        if (definition == null) {
            return null;
        }
        
        // 4. 创建 Bean
        return createBean(name, definition);
    }
    
    private Object createBean(String name, BeanDefinition definition) {
        try {
            // 标记正在创建
            singletonsCurrentlyInCreation.add(name);
            
            // 4.1 实例化
            Object bean = definition.getBeanClass().newInstance();
            
            // 4.2 属性注入(简化版)
            populateBean(bean, definition);
            
            // 4.3 初始化
            initializeBean(bean, name);
            
            // 4.4 如果是单例,放入缓存
            if (definition.isSingleton()) {
                singletonObjects.put(name, bean);
            }
            
            return bean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            singletonsCurrentlyInCreation.remove(name);
        }
    }
    
    private void populateBean(Object bean, BeanDefinition definition) {
        // 遍历所有字段,找到 @Autowired 注解的字段进行注入
        // 这是简化版,实际 Spring 要复杂得多
    }
    
    private void initializeBean(Object bean, String name) {
        // 调用 Aware 接口
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(name);
        }
        
        // 调用 BeanPostProcessor 的前置处理
        // ...
        
        // 调用初始化方法
        if (bean instanceof InitializingBean) {
            ((InitializingBean) bean).afterPropertiesSet();
        }
        
        // 调用 BeanPostProcessor 的后置处理
        // ...
    }
}

Spring 的三级缓存解决循环依赖

Spring 能解决大部分循环依赖问题,靠的是三级缓存

public class DefaultSingletonBeanRegistry {
    // 一级缓存:成品,完全初始化好的 Bean
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
    // 二级缓存:半成品,已经实例化但未初始化的 Bean
    private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    
    // 三级缓存:单例工厂,用于生成 Bean 的早期引用
    private Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}

工作原理

  1. A 创建时,把自己提前暴露到三级缓存
  2. A 需要注入 B,去创建 B
  3. B 需要注入 A,从三级缓存拿到 A 的早期引用(放入二级缓存)
  4. B 创建完成
  5. A 继续完成初始化

第五部分:最佳实践与常见陷阱

最佳实践清单

优先使用构造函数注入

@Component
public class GoodPractice {
    private final UserService userService;
    
    public GoodPractice(UserService userService) {
        this.userService = userService;
    }
}

明确 Bean 的作用域

@Component
@Scope("prototype")  // 显式声明,避免误解
public class StatefulBean {
    private int state;
}

使用 @Primary 解决歧义

@Component
@Primary
public class MainUserService implements UserService { }

合理使用 @Qualifier

@Component
@Qualifier("cache")
public class CacheUserService implements UserService { }

常见陷阱及解决方案

陷阱1:字段注入导致难以测试

// 错误
@Component
public class OrderService {
    @Autowired
    private UserService userService;  // 测试时无法替换
}

// 正确
@Component
public class OrderService {
    private final UserService userService;
    
    public OrderService(UserService userService) {
        this.userService = userService;
    }
}

陷阱2:Singleton 中使用 Prototype Bean

// 错误
@Component
public class SingletonService {
    @Autowired
    private PrototypeBean prototypeBean;  // 永远只有一个
}

// 正确
@Component
public class SingletonService {
    @Autowired
    private ObjectFactory<PrototypeBean> factory;
    
    public void doSomething() {
        PrototypeBean bean = factory.getObject();
    }
}

陷阱3:忘记处理多线程安全问题

// 错误
@Component  // 默认 singleton
public class CounterService {
    private int count = 0;  // 线程不安全!
    
    public void increment() {
        count++;
    }
}

// 正确
@Component
@Scope("prototype")  // 每个线程有自己的实例
public class CounterService {
    private int count = 0;
    
    public void increment() {
        count++;
    }
}

第六部分:总结与下期预告

本节核心要点

知识点 核心内容 应用场景
同类型 Bean 歧义 @Primary、@Qualifier、@Resource 多实现类注入
Bean 生命周期 实例化 → 注入 → 初始化 → 使用 → 销毁 理解容器工作原理
Bean 作用域 singleton、prototype、request、session 选择合适的范围
循环依赖解决 三级缓存机制 理解 Spring 的设计智慧
最佳实践 构造注入、明确作用域 写出健壮的代码

一个小思考题

Spring 的 @Transactional 注解是如何工作的?它和 Bean 的生命周期有什么关系?

提示:想想 BeanPostProcessor 的 postProcessAfterInitialization 方法。

下期预告

《Spring AOP 解密——从动态代理到声明式事务》

我们将深入剖析 AOP 的实现原理,看看 Spring 是如何通过“织入”来增强 Bean 的功能,以及声明式事务到底是怎么一回事。


写在最后

通过这两篇文章,我们从“没有 Spring 的世界”出发,逐步深入到了 Spring 容器的内部工作原理。现在你应该能理解:

  • 为什么需要 IoC:解耦、易测、职责单一
  • DI 怎么实现:构造器、setter、字段注入
  • Bean 的一生:从实例化到销毁的完整历程
  • Spring 内部原理:三级缓存、BeanPostProcessor 等

Spring 的魔法不再神秘,它只是一套精心设计的、帮助我们管理对象的框架。

如果你觉得文章有帮助,欢迎点赞、收藏、分享。有任何疑问,请在评论区留言,我们一起讨论。

下期见!

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐