在一次美团的面试中,有读者被问到关于项目中如何使用 AOP 的相关问题。

图片

以下是一个参考答案,实际面试中,务必根据自己的项目背景进行详细介绍。

AOP(Aspect-Oriented Programming:面向切面编程) 使得能够将与业务逻辑无关,但被多个业务模块共同调用的逻辑(如事务管理、日志记录、权限控制等)进行封装。这种方式不仅能减少系统中的重复代码降低模块间的耦合度,而且能够提升系统的可扩展性和可维护性

以下是我在项目中实际应用 AOP 的几个方面:

  1. 使用 AOP 实现统一的日志管理。
  2. 结合 Redisson 和 AOP 实现接口限流,通过一个注解即可控制单个用户在指定时间段内的请求次数。
  3. 利用 Spring Security 提供的 @PreAuthorize 注解实现权限控制,而其底层也基于 AOP。

在面试过程中,可以根据自身经验介绍 AOP 的应用,以下是一些常见的实际案例。

日志管理

通过 AOP 来记录日志,只需在 Controller 方法上添加自定义的 @Log 注解,即可将用户操作记录到数据库。

@Log(description = "新增用户")  
@PostMapping(value = "/users")  
public ResponseEntity create(@Validated @RequestBody User resources){  
    checkLevel(resources);  
    return new ResponseEntity(userService.create(resources), HttpStatus.CREATED);  
}  

AOP 切面类 LogAspect 负责拦截带有 @Log 注解的方法并进行日志处理:

@Aspect  
@Component  
public class LogAspect {  
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);  
    // 定义切点,拦截带有 @Log 注解的方法  
    @Pointcut("@annotation(com.example.annotation.Log)")  // 需根据实际包名调整  
    public void logPointcut() {  
    }  
    // 环绕通知,用于记录日志  
    @Around("logPointcut()")  
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {  
      //...  
    }  
}  

接口限流

通过 AOP 对接口进行限流,只需在 Controller 的方法上使用自定义的 @RateLimit 注解:

/**  
 * 该接口 60 秒内最多只能访问 10 次,保存到 redis 的键名为 limit_test,  
 */  
@RateLimit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")  
public int test() {  
    return ATOMIC_INTEGER.incrementAndGet();  
}  

AOP 切面类 RateLimitAspect 用来拦截带有 @RateLimit 注解的方法并进行限流处理:

@Slf4j  
@Aspect  
public class RateLimitAspect {  
    // 拦截所有带有 @RateLimit 注解的方法  
    @Around("@annotation(rateLimit)")  
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {  
        //...  
    }  
}  

关于限流的实现,这里补充一下,我是利用 Redisson 中的 RRateLimiter 来实现分布式限流,背后的实现方式是基于 Lua 脚本和令牌桶算法。

关于常见的限流算法与方案,您可以阅读我撰写的文章:服务限流详解。

权限控制

Spring Security 使用 AOP 进行方法拦截。在调用 update 方法之前,Spring 会检查当前用户的权限,只有在用户具备相应的权限条件时,才能执行该方法。

@Log(description = "修改菜单")  
@PutMapping(value = "/menus")  
// 用户拥有 `admin` 或 `menu:edit` 权限中的任意一个即可访问 `update` 方法  
@PreAuthorize("hasAnyRole('admin','menu:edit')")  
public ResponseEntity update(@Validated @RequestBody Menu resources){  
    //...  
}  

希望本文对理解 AOP 在实际项目中的应用有所帮助。