欢迎光临
我们一直在努力

从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(2)

深入理解 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:初始化

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

  • @PostConstruct 注解方法
  • InitializingBean 接口的 afterPropertiesSet()
  • 自定义 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:销毁

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

  • @PreDestroy 注解方法
  • DisposableBean 接口的 destroy()
  • 自定义 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<>();
    }

    工作原理:

  • A 创建时,把自己提前暴露到三级缓存
  • A 需要注入 B,去创建 B
  • B 需要注入 A,从三级缓存拿到 A 的早期引用(放入二级缓存)
  • B 创建完成
  • 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 的魔法不再神秘,它只是一套精心设计的、帮助我们管理对象的框架。

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

    下期见!

    赞(0)
    未经允许不得转载:171主机测评 » 从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(2)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址