返回首页
📝309 ⏱️2 分钟📅2025-12-16📄Java Frameworks#Java / Spring / AOP / Backend / Design Patterns

Spring AOP 核心原理解析与实战

Spring AOP 核心原理解析与实战

AOP 基础概念

AOP (Aspect-Oriented Programming,面向切面编程) 是一种旨在通过分离横切关注点 (Cross-cutting Concerns) 来提高代码模块化程度的编程范式。它通过在现有代码程序中动态添加功能,而无需修改源代码,从而降低了业务逻辑与系统服务(如日志、事务)之间的耦合。

核心术语体系

术语英文说明形象理解
切面Aspect横切关注点的模块化实现 (通常是一个类)。就像把蛋糕横着切开,加入的一层奶油。
连接点JoinPoint程序执行过程中的特定点 (如方法调用、异常抛出)。程序执行流程中所有“可以被介入”的时机。
切入点Pointcut匹配连接点的表达式,定义了“在哪里”执行切面逻辑。从众多连接点中筛选出真正需要增强的那个点。
通知Advice在特定切入点执行的具体动作/代码。在切入点“要做什么” (增强逻辑)。
目标对象Target被切面织入的对象,包含核心业务逻辑。原原本本的业务对象。

核心原理:代理模式

笔记: AOP 的底层实现依赖于 代理模式 (Proxy Pattern)
在 Spring 中,如果目标对象实现了接口,默认使用 JDK 动态代理;如果未实现接口,则使用 CGLIB 代理

通知类型 (Advice Types)

Spring AOP 提供了五种增强类型,覆盖了方法执行的各个阶段:

类型注解说明执行时机
前置通知@Before在目标方法执行前执行方法调用之前
后置通知@After在目标方法执行后执行方法返回或抛出异常后 (类似 finally)
返回通知@AfterReturning在方法正常返回后执行只有代码成功运行完才执行
异常通知@AfterThrowing在方法抛出异常后执行只有发生异常才执行
环绕通知@Around包围目标方法的执行手动控制方法执行,可修改返回值或阻止执行

代码实战

以下是一个使用 Spring AOP 实现日志记录的典型示例:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 1. 标记为切面类
@Component // 注入 Spring 容器
public class LoggingAspect {

    // 定义切入点表达式:匹配 com.example.service 包下所有类的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    @Before("serviceLayer()") // 2. 引用切入点
    public void logBefore(JoinPoint joinPoint) {
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println(">>> [AOP Log] 方法执行前: " + methodName);
    }
}

应用场景

AOP 非常适合处理那些与核心业务逻辑无关,但又必须在多个模块中重复出现的代码:

  • 日志记录: 统一记录 API 请求参数与响应时间。
  • 事务管理: Spring @Transactional 的底层实现。
  • 权限控制: 方法级别的角色与权限校验。
  • 性能监控: 统计方法执行耗时。
  • 全局异常处理: 统一捕获特定异常并转换格式。
  • 缓存管理: 如 @Cacheable,自动查询或更新缓存。

优势与注意事项

核心优势

  • 高内聚: 业务代码只关注业务逻辑,非业务逻辑被剥离。
  • 低耦合: 横切关注点统一维护,不再分散在各个业务方法中。
  • 易扩展: 添加新功能(如监控)时无需修改旧代码。

开发注意事项

💡 Best Practices:

  1. 避免滥用: 只有真正的横切关注点才使用 AOP。如果逻辑属于业务本身,应直接写在代码里,否则会降低代码可读性(“魔法”代码)。
  2. 切入点优化: 尽量编写精确的 Pointcut 表达式 (execution),避免意外拦截了不该拦截的方法(如 toString() 或内部辅助方法)。
  3. 内部调用失效: 在同一个类中,方法 A 调用方法 B,方法 B 的 AOP 增强不会生效(因为绕过了代理对象,直接使用了 this)。