Skip to content

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 代理生成)→ 就绪 → 销毁。