Appearance
Spring
一、为什么需要 Spring?
Java EE(EJB)时代,开发一个功能需要写大量样板代码(Home 接口、Remote 接口、部署描述符),配置繁琐,测试困难,侵入性强。
Spring 的核心思想是简化 Java 开发,通过两大武器:IoC(控制反转),让容器管理对象创建和依赖,你只需声明"我需要什么";AOP(面向切面编程),将横切关注点(日志、事务、安全)从业务代码中分离。
二、IoC 容器
2.1 原理:控制反转
没有 IoC 时: 你需要什么对象,自己 new。
java
// 传统方式:主动创建依赖
public class OrderService {
private OrderDao orderDao = new OrderDaoImpl(); // 紧耦合
}有了 IoC 后: 容器创建对象并注入依赖,你只需声明"我需要 OrderDao"。
java
// Spring 方式:声明依赖,容器注入
@Service
public class OrderService {
@Autowired
private OrderDao orderDao; // 松耦合,容器自动注入
}2.2 为什么叫"控制反转"?
控制权从"你主动创建对象"反转为"容器创建好对象后给你"。谁控制谁?容器控制对象。反转了什么?获取依赖的方式反转了。
2.3 DI(依赖注入)的三种方式
| 方式 | 注解 | 适用场景 |
|---|---|---|
| 字段注入 | @Autowired 在字段上 | 简洁,但不利于单元测试 |
| 构造器注入 | @Autowired 在构造器上 | 推荐,不可变,测试友好 |
| Setter 注入 | @Autowired 在 setter 上 | 可选依赖 |
java
// 构造器注入(推荐):依赖不可变,测试时可直接传入 mock
@Service
public class OrderService {
private final OrderDao orderDao;
private final InventoryService inventoryService;
public OrderService(OrderDao orderDao, InventoryService inventoryService) {
this.orderDao = orderDao;
this.inventoryService = inventoryService;
}
}2.4 Bean 的作用域
| 作用域 | 说明 |
|---|---|
| singleton | 默认,整个容器只有一个实例 |
| prototype | 每次获取都创建新实例 |
| request | 每个 HTTP 请求一个实例(Web 环境) |
| session | 每个 HTTP 会话一个实例(Web 环境) |
为什么默认是 singleton? 无状态的服务类(Service、Dao)不需要多个实例,单例节省内存,减少 GC 压力。
2.5 Bean 生命周期
实例化 → 属性赋值 → Aware 接口回调 → BeanPostProcessor 前置处理
→ InitializingBean.afterPropertiesSet() → init-method
→ BeanPostProcessor 后置处理(AOP 代理在此生成)
→ Bean 就绪 → 使用中
→ DisposableBean.destroy() → destroy-method → 销毁关键: AOP 代理是在 BeanPostProcessor.postProcessAfterInitialization() 中生成的,所以 Spring 容器中实际使用的是代理对象,不是原始 Bean。
三、AOP
3.1 原理:面向切面编程
为什么需要 AOP? 日志、事务、权限校验这些代码散落在每个方法中,与业务逻辑无关但必不可少。AOP 将这些横切关注点抽取为"切面",动态织入到目标方法,业务代码保持纯净。
java
// 没有 AOP:每个方法都写日志
public void createOrder() {
log.info("开始创建订单"); // 横切关注点
// 业务逻辑
log.info("订单创建完成"); // 横切关注点
}
// 有了 AOP:日志代码只需写一次
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
log.info("开始调用: {}", pjp.getSignature());
Object result = pjp.proceed();
log.info("调用完成: {}", pjp.getSignature());
return result;
}
}3.2 AOP 的核心概念
| 概念 | 说明 |
|---|---|
| JoinPoint | 可以插入切面的点(方法调用、异常抛出等) |
| Pointcut | 匹配 JoinPoint 的表达式 |
| Advice | 在切入点执行的代码(Before/After/Around/AfterThrowing/AfterReturning) |
| Aspect | 切面 = Pointcut + Advice |
| Weaving | 将切面织入目标对象的过程(编译期/类加载期/运行期) |
3.3 Spring AOP 的实现原理
Spring AOP 基于动态代理:
- 有接口的目标类 → JDK 动态代理(
Proxy.newProxyInstance) - 无接口的目标类 → CGLIB 代理(子类继承)
为什么 Spring AOP 不能拦截内部方法调用? 代理对象中,this.method() 调用的是目标对象自身的方法,绕过了代理。需要用 AopContext.currentProxy() 获取代理对象再调用。
java
@Service
public class OrderService {
public void methodA() {
// 错误:this.methodB() 不会被 AOP 拦截
// 正确:((OrderService) AopContext.currentProxy()).methodB()
}
@Transactional
public void methodB() { }
}四、事务管理
4.1 原理:声明式事务
Spring 通过 AOP 实现声明式事务。@Transactional 注解的方法会被 AOP 代理拦截,在方法执行前开启事务,执行后提交或回滚。
java
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
orderDao.insert(order);
inventoryService.deduct(order.getProductId(), order.getQuantity());
}4.2 事务传播行为
| 传播行为 | 说明 | 场景 |
|---|---|---|
| REQUIRED(默认) | 有事务则加入,无则新建 | 大多数情况 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 | 日志记录(独立提交) |
| SUPPORTS | 有事务则加入,无则非事务运行 | 查询 |
| NOT_SUPPORTED | 非事务运行,挂起当前事务 | 不需要事务的耗时操作 |
| MANDATORY | 必须在事务中,否则抛异常 | 必须被事务包裹的写操作 |
| NEVER | 必须非事务运行,否则抛异常 | 明确不需要事务 |
| NESTED | 嵌套事务,可独立回滚 | 批量操作中部分失败 |
REQUIRED vs REQUIRES_NEW: 如订单服务(REQUIRED)调用日志服务(REQUIRES_NEW),日志记录失败不会回滚订单。
4.3 事务失效场景
java
// 1. 非 public 方法
@Transactional
private void doSomething() { } // 失效!AOP 无法代理 private
// 2. 同类内部调用
public void methodA() {
this.methodB(); // this 不是代理对象,事务不生效
}
// 3. 异常被吃掉
@Transactional
public void method() {
try {
// 数据库操作
} catch (Exception e) {
// 吞掉异常,事务不回滚
}
}
// 4. rollbackFor 未指定
@Transactional // 默认只回滚 RuntimeException 和 Error
public void method() throws Exception {
throw new Exception(); // 不会回滚!
}五、面试要点
Q:IoC 和 DI 有什么区别?
IoC 是设计思想(控制反转,容器管理对象),DI 是 IoC 的实现方式(依赖注入)。IoC 是目的,DI 是手段。
Q:Spring AOP 的实现原理?
基于动态代理:有接口用 JDK 动态代理,无接口用 CGLIB。在 BeanPostProcessor 后置处理中生成代理对象,方法调用时通过代理执行切面逻辑。
Q:Spring 事务的传播行为?
7 种。REQUIRED(默认,有则加入无则新建)、REQUIRES_NEW(总是新建独立事务)、NESTED(嵌套事务可独立回滚)最常用。
Q:Spring Bean 的生命周期?
实例化 → 属性赋值 → Aware 回调 → BeanPostProcessor 前置 → init → BeanPostProcessor 后置(AOP 代理生成)→ 就绪 → 销毁。
