spring-aop

5 阅读4分钟

spring-aop

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring AOP是Spring框架中实现面向切面编程的核心模块, 作用 分离关注点

术语解释代码示例
切面(Aspect)横切关注点的模块化(一个类)@Aspect注解的类
连接点(Join Point)程序执行中的点(如方法调用)OrderService.createOrder()
通知(Advice)切面在特定连接点执行的动作@Before@After@Around
切入点(Pointcut)匹配连接点的表达式execution(* com..service.*.*(..))
@Aspect
@Component
public class CompleteAspect {
    
    @Pointcut("execution(* com.example.service.UserService.*(..))")
    public void userServiceMethods() {}
    
    // 1. 前置通知 - 方法执行前 可以获取入参joinPoint.getArgs() + 方法名
    @Before("userServiceMethods()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("【前置】准备执行: " + 
            joinPoint.getSignature().getName());
    }
    
    // 2. 后置通知 - 方法执行后(无论是否异常)
    @After("userServiceMethods()")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("【后置】方法执行完成: " + 
            joinPoint.getSignature().getName());
    }
    
    // 3. 返回通知 - 方法正常返回后 returning指定接收返回值的变量名
    @AfterReturning(
        pointcut = "userServiceMethods()",
        returning = "result"
    )
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("【返回】方法返回: " + result);
    }
    
    // 4. 异常通知 - 方法抛出异常后 throwing指定接收异常的变量名
    @AfterThrowing(
        pointcut = "userServiceMethods()",
        throwing = "ex"
    )
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        System.out.println("【异常】方法抛出异常: " + ex.getMessage());
    }
    
    // 5. 环绕通知 - 最强大,控制整个执行过程【可以修改目标方法入参 拦截目标方法执行 修改目标方法返参】
    @Around("userServiceMethods()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("【环绕前】");
        
        // 可以修改参数
        Object[] args = joinPoint.getArgs();
        if (args.length > 0 && args[0] instanceof String) {
            args[0] = ((String) args[0]).toUpperCase();
        }
        
        // 执行原方法(可以选择不执行!)
        Object result = joinPoint.proceed(args);
        
        // 可以修改返回值
        if (result instanceof String) {
            result = "修改后的: " + result;
        }
        
        System.out.println("【环绕后】");
        return result;
    }
}

常用的切入点表达式

@Aspect
public class PointcutExamples {
    
    // 1. 按方法执行  第1个*匹配所有的返回值类型  第2个*匹配所有的方法名  ..表示参数个数不限定
    @Pointcut("execution(* com.example.service.*(..))")
    public void allPublicServiceMethods() {}  // service包下所有方法
    
    
    // 2. 按注解 注解的全限定类名
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}  // 所有@Transactional方法
  • execution 匹配规则
全写法:[public] 返回值类型 [全限定类路径].add(入参1类型, 入参2类型)
通配符: 
  *  表示任意字符  
  .. 在参数位置,表示多个任意类型参数(包含0个); 在类型位置表示多个层级

通知方法的执行顺序

正常链路 前置通知@Before --- 目标方法 --- 返回通知@AfterReturning --- 后置通知@After
异常链路 前置通知@Before --- 目标方法 --- 异常通知@AfterThrowing  --- 后置通知@After


@Around前半 → @Before → 目标方法 → @AfterReturning/@AfterThrowing@After@Around后半

多个切面的执行顺序

@Order决定切面层级, 数值越小,优先级越高,越在外层

@Aspect @Order(1)  // 第一层
public class Aspect1 {
    @Before("pointcut()") 
    public void before1() { System.out.println("Aspect1-Before"); }
    
    @Around("pointcut()")
    public Object around1(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Aspect1-Around前");
        Object result = pjp.proceed();  // 进入下一层
        System.out.println("Aspect1-Around后");
        return result;
    }
    
    @After("pointcut()")
    public void after1() { System.out.println("Aspect1-After"); }
}

@Aspect @Order(2)  // 第二层  
public class Aspect2 {
    @Before("pointcut()")
    public void before2() { System.out.println("Aspect2-Before"); }
    
    @Around("pointcut()")
    public Object around2(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Aspect2-Around前");
        Object result = pjp.proceed();  // 进入下一层
        System.out.println("Aspect2-Around后");
        return result;
    }
    
    @After("pointcut()")
    public void after2() { System.out.println("Aspect2-After"); }
}

@Aspect @Order(3)  // 第三层(最内层)
public class Aspect3 {
    @Before("pointcut()")
    public void before3() { System.out.println("Aspect3-Before"); }
    
    @Around("pointcut()")
    public Object around3(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Aspect3-Around前");
        Object result = pjp.proceed();  // 执行目标方法
        System.out.println("Aspect3-Around后");
        return result;
    }
    
    @After("pointcut()")
    public void after3() { System.out.println("Aspect3-After"); }
}
  • 上述执行顺序图示
1. Aspect1.around前 → Aspect1.before
2.   ↓ Aspect2.around前 → Aspect2.before  
3.     ↓ Aspect3.around前 → Aspect3.before
4.       ↓ 目标方法执行
5.     ↑ Aspect3.after → Aspect3.around后
6.   ↑ Aspect2.after → Aspect2.around后
7. ↑ Aspect1.after → Aspect1.around后
  • 实际输出结果
Aspect1-Around前     // 最外层开始
Aspect1-Before

Aspect2-Around前     // 第二层
Aspect2-Before

Aspect3-Around前     // 最内层
Aspect3-Before

>>> 目标方法执行 <<<   // 到达核心

Aspect3-After        // 从最内层开始返回
Aspect3-Around后

Aspect2-After
Aspect2-Around后

Aspect1-After
Aspect1-Around后     // 最外层结束

应用举例 权限控制

  • 编写权限注解
// 自定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String value();  // 需要的权限,如 "user:delete"
}

  • 编写切面类
// 权限切面
@Aspect
@Component
public class SecurityAspect {
    
    
    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, 
                                  RequirePermission requirePermission) throws Throwable {
        // 获取注解的值
        String requiredPerm = requirePermission.value();
        // 获取当前用户的权限
        User user = getCurrentUser();
        if (!user.hasPermission(requiredPerm)) {
            throw new AccessDeniedException("权限不足: " + requiredPerm);
        }
        
        return joinPoint.proceed();
    }
}
  • 在控制层注解标记需要权限校验的接口
// 使用:优雅的权限控制
@RestController
public class UserController {
    
    @DeleteMapping("/users/{id}")
    @RequirePermission("user:delete")  // 只需一个注解!
    public void deleteUser(@PathVariable Long id) {
        // 纯业务逻辑
    }
}