Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制

IoC 容器与 Bean

IoC 容器

如果你做过前端,你一定见过这个模式:

js 复制代码
// Vue 3:全局注册一个依赖,后代组件随意取用
const app = createApp();
app.provide('userService', new UserService());  // 注册到"全局仓库"

// 任何后代组件
const userService = inject('userService');       // 从"全局仓库"取出来

Spring 的 IoC 容器本质上就是"全局仓库"的工业级实现:

概念 Spring Vue 类比 React 类比
容器的角色 ApplicationContext app.provide() 注册表 Context Provider 树
存放的东西 Bean(Java 对象) 注入值 / 服务实例 Context value
注册方式 @Component / @Service 等注解 app.provide(key, value) <Provider value={...}>
取用方式 @Autowired 注入(可标注在字段、Setter、构造器上) inject(key) useContext(SomeContext)

⚠️ 关键区别 :Vue/React 的依赖注入是手动注册 (你显式调用 provide 或写 <Provider>),而 Spring 是自动扫描 ------只要类上标了 @Component,容器启动时自动发现、自动创建、自动管理。

Bean 到底是什么?

Bean 是由 Spring IoC 容器负责创建、组装和管理的 Java 对象

被 Spring 管理这件事带来的好处:

  • 单例:默认只创建一个实例,全局复用(无需自己写单例模式)
  • 依赖注入:自动把需要的其他 Bean 注入进来(无需 new)
  • 生命周期回调:可以在"创建时"和"销毁时"执行自定义逻辑
  • AOP 增强:Spring 可以在你的方法前后偷偷加功能(事务、日志、缓存......)

生命周期全景图

Spring Bean 就像 React/Vue 组件,有自己的"挂载 → 更新 → 卸载"生命周期,只是叫法和细节不同

less 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                     Spring Bean 生命周期(完整 8 步)                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. 构造方法 (new 对象,读配置、校验参数)                                  │
│        │                                                                 │
│        ▼                                                                 │
│  2. 依赖注入 (@Autowired,把需要的其他 Bean 传进来)            │
│        │                                                                 │
│        ▼                                                                 │
│  3. Aware 接口 (BeanNameAware, ApplicationContextAware... 知道"我是谁")     │
│        │                                                                 │
│        ▼                                                                 │
│  4. BeanPostProcessor 前置处理 (postProcessBeforeInitialization)         │
│        │  类比:axios 请求拦截器 ------ 在"初始化"之前拦截                       │
│        ▼                                                                 │
│  5. @PostConstruct 初始化 (缓存预热、连接池初始化、资源准备)                │
│        │                                                                 │
│        ▼                                                                 │
│  6. BeanPostProcessor 后置处理 (postProcessAfterInitialization)          │
│        │  ⭐ AOP 代理在此处生成!(@Transactional、@Async 等的魔法时刻)     │
│        │  类比:axios 响应拦截器 ------ 在"初始化"之后拦截                     │
│        ▼                                                                 │
│  7. Bean 就绪 ------ 可供业务使用                                             │
│        │                                                                 │
│        ▼                                                                 │
│  8. @PreDestroy 销毁 (优雅停机、释放资源)                                  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

构造/注入 → @PostConstruct 初始化 → @PreDestroy 销毁,覆盖日常开发 90% 的场景。

八阶段详解

1.构造方法

前端类比:Spring 构造方法 ≈ React 函数组件执行 / Vue setup() 执行

构造方法示例:

java 复制代码
@Component
public class UserService {
    public UserService() {
        System.out.println("1. 构造器:new 对象");
    }
}

示例场景:构造阶段读取配置并校验

java 复制代码
/**
 * 支付网关客户端 ------ 构造阶段读取配置并校验
 */
@Component
public class PaymentGatewayClient {

    private final String baseUrl;
    private final int connectTimeout;

    // Spring 通过构造器将 application.yml 中的配置值注入
    public PaymentGatewayClient(
            @Value("${payment.gateway.url}") String baseUrl,
            @Value("${payment.gateway.timeout:5000}") int connectTimeout) {

        // ⚠️ 实战要点:参数校验放在构造器,fail-fast
        if (baseUrl == null || baseUrl.isBlank()) {
            throw new IllegalArgumentException(
                "payment.gateway.url 未配置,请检查 application.yml");
        }

        this.baseUrl = baseUrl;
        this.connectTimeout = connectTimeout;
    }

    // 后续业务方法...
}

2.依赖注入

前端类比:Spring 字段注入 @Autowired ≈ Vue inject() ≈ React useContext(),从"全局注册表"中按标识符查找依赖

依赖注入示例:字段注入

java 复制代码
@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;
}

示例场景:业务层之间的相互调用 ------ OrderService 依赖 UserService

java 复制代码
/**
 * 订单服务 ------ 展示依赖注入的三种方式(本质都是 @Autowired)
 */
@Service
public class OrderService {
    private final UserService userService;
    private final InventoryService inventoryService;

    @Autowired  // Spring 4.3+ 如果类只有一个构造器,这个注解可以省略
    public OrderService(UserService userService, InventoryService inventoryService) {
        this.userService = userService;
        this.inventoryService = inventoryService;
    }
}

3.Aware 接口

前端类比:Aware 接口 ≈ Vue 3 的 getCurrentInstance,都是让"内部成员"有能力反查"外部环境"的元信息

常用 Aware 接口一览

接口 获取什么 什么场景用 常用度
BeanNameAware Bean 在容器中的名字 日志输出、错误追踪 ⭐⭐⭐
EnvironmentAware 环境配置(profiles、properties) dev/prod 走不同初始化逻辑 ⭐⭐⭐
ApplicationContextAware 整个 Spring 容器引用 极少数需要手动获取 Bean 的场景(推荐优先用 @Autowired 替代)
ResourceLoaderAware 资源加载器 加载 classpath 下的文件
BeanFactoryAware Bean 工厂(容器底层 API) 非常底层,99% 的业务代码不需要

示例场景一:日志里带上 Bean 名

java 复制代码
/**
 * 短信发送处理器 ------ 实现 BeanNameAware 让日志带上自己的 Bean 名
 * 场景:项目中同时存在 aliSmsHandler、tencentSmsHandler 等同类型 Bean,
 *       出问题时需要一眼看出是哪个渠道在处理
 */
@Component
public class AliSmsHandler implements BeanNameAware {

    private String beanName;

    @Override
    public void setBeanName(String name) {
        this.beanName = name;  // Spring 自动传入,比如 "aliSmsHandler"
    }

    public void send(String phone, String content) {
        System.out.printf("[%s] 发送短信到 %s,内容: %s%n", beanName, phone, content);
        // 调用阿里短信 API...
    }
}

示例场景二:根据环境初始化不同的上传目录

java 复制代码
/**
 * 文件存储服务 ------ 实现 EnvironmentAware 根据 dev/prod 环境选择不同存储路径
 */
@Component
public class FileStorageService implements EnvironmentAware {

    private String uploadDir;

    @Override
    public void setEnvironment(Environment env) {
        // 根据当前激活的 profile 做不同初始化
        String[] profiles = env.getActiveProfiles();
        List<String> activeProfiles = Arrays.asList(profiles);

        if (activeProfiles.contains("dev")) {
            this.uploadDir = "/tmp/uploads";      // 开发环境:本地临时目录
        } else if (activeProfiles.contains("prod")) {
            this.uploadDir = "/data/uploads";     // 生产环境:持久化存储
        } else {
            this.uploadDir = "/var/uploads";      // 默认
        }
    }
}

4.BeanPostProcessor 前置处理

前端类比:BeanPostProcessor 前置处理 ≈ axios 请求拦截器

在容器启动时触发。每个单例 Bean 仅执行一次(初始化阶段)

java 复制代码
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("前置处理: " + beanName);
        return bean;  // 可以返回代理对象替换原 bean
    }
}

5.@PostConstruct 初始化

前端类比:@PostConstruct ≈ React useEffect(() => {}, \[\]) ≈ Vue onMounted()

适用场景:缓存预热,启动时把热点数据从 DB 加载到本地缓存,避免首次请求冷启动

java 复制代码
/**
 * 首页商品缓存 ------ 启动时从数据库预热热点数据
 */
@Service
public class HotProductCache {

    private final ProductMapper productMapper;
    private final Map<Long, Product> cache = new ConcurrentHashMap<>();
    private volatile boolean ready = false;

    public HotProductCache(ProductMapper productMapper) {
        this.productMapper = productMapper;
    }

    @PostConstruct
    public void warmUp() {
        List<Product> hotProducts = productMapper.selectHotProducts(100);
        hotProducts.forEach(p -> cache.put(p.getId(), p));
        this.ready = true;
        System.out.println("首页热门商品缓存就绪,共 " + cache.size() + " 条");
    }

    // 业务方法:对外提供数据
    public List<Product> getHotProducts() {
        return ready ? new ArrayList<>(cache.values()) : Collections.emptyList();
    }
}

6.BeanPostProcessor 后置处理

前端类比:BeanPostProcessor 前置处理 ≈ axios 响应拦截器

后置处理是AOP的关键,@Transactional、@Async、@Cacheable 这些注解之所以能生效,就是因为 Spring 在后置处理这一步,偷偷把原始 Bean 换成了一个增强后的代理对象。类似于JavaScript 的Proxy

scss 复制代码
Bean 初始化完成后                    后置处理中...
─────────────────────              ─────────────────────
  OrderService                       Spring 发现 OrderService 上有 @Transactional
  ├── placeOrder()                   ↓
  ├── cancelOrder()                 包装成代理对象返回:
  └── queryOrders()                 ┌─────────────────────────────────┐
                                    │ OrderServiceProxy              │
                                    │ ├── placeOrder() {             │
                                    │ │    开启事务                   │
                                    │ │    调用原始 placeOrder()      │
                                    │ │    提交/回滚事务              │
                                    │ │   }                          │
                                    │ ├── cancelOrder() { ... }      │
                                    │ └── queryOrders()  { ... }     │
                                    └─────────────────────────────────┘

代码示例

java 复制代码
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("6. 后置处理: " + beanName);
        // ⭐ Spring AOP 在这里生成代理对象!
        return bean;
    }
}

7. Bean 就绪

Bean 走完前 6 步后进入"就绪"状态,随时可以处理业务请求。

8. 销毁

前端类比:@PreDestroy ≈ React useEffect cleanup ≈ Vue onUnmounted()

适用场景:业务代码中的绝大多数资源释放(如关闭线程池、缓存清理、关闭数据库连接)

java 复制代码
@Component
public class UserService {
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("释放资源...");
    }
}
相关推荐
IT_陈寒1 小时前
Redis内存飙升的锅,原来是我没搞懂这个过期策略
前端·人工智能·后端
云浪1 小时前
前端二进制数组完全指南:ArrayBuffer、TypedArray、DataView 一次讲透
前端·javascript
张风捷特烈1 小时前
Flutter 类库大揭秘#02 | path_provider 各平台实现
前端·flutter
铁皮饭盒2 小时前
26年bunjs, elysia+pg一把梭, redis都省了
前端·javascript·后端
plainGeekDev2 小时前
ButterKnife → ViewBinding
android·java·kotlin
葫芦和十三10 小时前
图解 MongoDB 19|Oplog:复制的真正载体,不是文档是操作
后端·mongodb·agent
葫芦和十三10 小时前
图解 MongoDB 20|复制延迟与 catch up:Secondary 为什么跟不上
后端·mongodb·agent