深入浅出Spring AOP:原理、实战与最佳实践

一、什么是AOP?

AOP(Aspect-Oriented Programming) 是一种编程范式,用于解耦横切关注点(如日志、事务、权限)。通过将通用功能从业务逻辑中剥离,提升代码可维护性和复用性。

核心概念

术语说明示例
Aspect横切关注点的模块化@Aspect注解的类
Join Point程序执行中的特定点(如方法调用)UserService.addUser()
Advice在连接点执行的动作@Before@After
Pointcut匹配连接点的表达式execution(* com.service.*.*(..))
Target被代理的对象普通的Bean实例

二、Spring AOP 实现原理

动态代理机制

  1. JDK动态代理(默认)

    • 要求目标类实现接口

    • 运行时生成接口的代理类

    Proxy.newProxyInstance(
         target.getClass().getClassLoader(),
         target.getClass().getInterfaces(),
         new InvocationHandler() { ... }
    );

  2. CGLIB代理(无接口时)

    • 通过继承目标类生成子类代理

    • 需添加spring-core依赖

    <dependency>
         <groupId>cglib</groupId>
         <artifactId>cglib</artifactId>
         <version>3.3.0</version>
    </dependency>

代理选择优先级:有接口 → JDK代理;无接口 → CGLIB(可通过@EnableAspectJAutoProxy(proxyTargetClass=true)强制使用CGLIB)


三、Spring AOP 实战步骤
1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义切面
@Aspect
@Component
public class LoggingAspect {
    
    // 定义切入点:匹配service包下所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    // Before通知:方法执行前
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("【前置通知】调用方法: " + methodName);
    }
    
    // Around通知:包裹目标方法
    @Around("serviceLayer()")
    public Object logTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // 执行目标方法
        long time = System.currentTimeMillis() - start;
        System.out.println("【环绕通知】方法执行耗时: " + time + "ms");
        return result;
    }
}
3. 常用通知类型
注解执行时机是否可修改返回值
@Before方法执行前
@After方法执行后(无论是否异常)
@AfterReturning方法成功返回后
@AfterThrowing方法抛出异常时
@Around包裹目标方法

四、切点表达式详解

常用表达式语法

// 匹配包路径
execution(* com.service.*.*(..))

// 匹配注解
@annotation(com.annotation.RequireAuth)

// 匹配类
within(com.service.UserService+)

// 组合表达式
@Before("serviceLayer() && args(userId, ..)")
public void logWithParam(Long userId) { ... }

表达式组合运算符

  • &&(与)

  • ||(或)

  • !(非)


五、最佳实践与避坑指南
  1. 代理失效场景

    • 同类内部方法调用(通过this调用而非代理对象)
      解决方案

    @Autowired 
    private ApplicationContext context;
    
    public void internalCall() {
       // 错误:this.method() 
       context.getBean(this.getClass()).method(); // 从容器获取代理对象
    }

  2. 执行顺序控制

    • 使用@Order注解指定切面顺序(值越小优先级越高)

    @Aspect
    @Order(1) // 最先执行
    public class SecurityAspect { ... }

  3. 性能优化

    • 避免在频繁调用的方法上使用复杂切点

    • 优先选择execution而非annotation(前者在编译期优化)


六、实际应用场景
  1. 统一日志记录

  2. 声明式事务管理@Transactional底层基于AOP)

  3. 权限校验(结合自定义注解)

    @Retention(RetentionPolicy.RUNTIME)
    public @interface AdminOnly {}
    
    @Aspect
    public class AuthAspect {
         @Before("@annotation(AdminOnly)")
         public void checkAdmin() { ... }
    }
  4. 接口限流与熔断

  5. 自动缓存处理


七、总结
  • 优势:解耦横切逻辑、提升代码复用、降低维护成本

  • 局限:仅作用于Spring容器中的Bean、无法拦截私有方法

  • 适用场景:需要统一处理的非功能性需求

Spring AOP vs AspectJ

特性Spring AOPAspectJ
织入方式运行时编译期/类加载期
性能中等
功能完整性基础全面(如字段拦截)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值