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("释放资源...");
}
}