程云来 / 杭州
Back to Blog·Spring
·About 5 min

Spring(二):容器不只是存对象,它还管理一条生命周期

从 Bean 的定义、创建、注入到销毁,理解 ApplicationContext 如何把对象图变成可运行的应用。


有了依赖注入,还差一个运行时

上一篇把 OrderService 的依赖交给外部组装,但真实应用不只有两个对象。订单服务可能依赖支付网关、库存仓库和事件发布器,支付网关又依赖 HTTP 客户端。

如果每个入口都手动写一遍组装代码,依赖关系仍然会散落在各处。Spring 引入容器,是为了集中管理这张对象图。

正在绘制图表…
查看 Mermaid 源码
flowchart TD
    context[ApplicationContext] --> order[OrderService]
    context --> payment[PaymentGateway]
    context --> stock[StockRepository]
    order --> payment
    order --> stock

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

容器保存的不是已经创建好的对象清单,而是“如何创建对象”的定义,以及对象之间的依赖关系。

Bean 从哪里开始,到哪里结束

把一个 Bean 的生命周期压缩成一条可观察的轨迹:

text
读取 Bean 定义
    ↓
实例化对象
    ↓
注入依赖
    ↓
执行后置处理器
    ↓
调用初始化回调
    ↓
对外提供 Bean
    ↓
容器关闭时执行销毁回调

这里的关键是“对外提供 Bean”发生在初始化之后。业务代码拿到的通常不是一个半成品对象。

可以用一个需要连接池的组件表示初始化和销毁:

java
@Component
class ReportExporter {
    private ConnectionPool pool;

    @Autowired
    void setPool(ConnectionPool pool) {
        this.pool = pool;
    }

    @PostConstruct
    void open() {
        pool.checkHealth();
    }

    @PreDestroy
    void close() {
        pool.shutdown();
    }
}

@PostConstruct@PreDestroy 只是生命周期回调的声明方式。真正负责调用它们的是容器。

组装根应该在哪里

一个应用总要有一个地方决定“使用哪些实现”。在 Spring 中,这个位置通常是配置类和启动入口:

java
@Configuration
class OrderModule {
    @Bean
    PaymentGateway paymentGateway(HttpClient http) {
        return new StripePaymentGateway(http);
    }

    @Bean
    OrderService orderService(
        PaymentGateway payment,
        StockRepository stock
    ) {
        return new OrderService(payment, stock);
    }
}

容器看到 orderService 需要两个参数,就会继续寻找对应的 Bean。这个过程可以抽象为:

  1. 读取 OrderService 的构造器参数;
  2. 按类型或限定符找到 PaymentGatewayStockRepository
  3. 先创建它们,再调用 OrderService 构造器;
  4. 缓存结果,按作用域决定后续是否复用。

这就是“对象图”的具体含义:每个节点是一个对象,每条边是一个依赖。

单例不是全局变量

Spring 默认的 singleton scope 表示:在一个容器中,同一个 Bean 定义通常对应一个共享实例。它仍然由容器管理,可以被替换、装饰和销毁。

因此,单例 Bean 最好保持无请求状态:

java
@Service
class OrderService {
    // 可以保存不可变依赖
    private final PaymentGateway payment;

    // 不要把当前用户、当前订单写进字段
}

把请求数据放进单例字段,会让并发请求互相覆盖。容器负责生命周期,不会自动替你修复线程安全问题。

循环依赖暴露了什么

如果 OrderService 依赖 AuditService,而 AuditService 又依赖 OrderService,容器无法按构造器顺序完成创建:

text
创建 OrderService → 需要 AuditService
创建 AuditService → 又需要 OrderService

这通常说明两个组件的职责边界有问题。不要先想着换字段注入绕过启动错误,先把共同职责提取成第三个组件,或者让事件承担单向通知。

容器的报错在这里是一种结构反馈:它把手动组装时容易被忽略的依赖环显式暴露出来。

下一篇会继续追问:对象已经由容器创建好了,事务和日志又是怎样在不修改业务方法的情况下被加进去的?

参考:Spring Bean ScopesBean Lifecycle