Bootstrap

20、《Spring AOP:日志记录与权限控制实战》

Spring AOP:日志记录与权限控制实战

引言

在企业级应用开发中,审计日志权限控制是两个关键的非功能性需求。传统实现方式往往导致代码重复度高、维护成本大。本文将通过Spring AOP(Aspect-Oriented Programming)结合自定义注解,演示如何优雅地实现这两个核心功能。文章包含完整代码示例、切面编程原理剖析及生产环境最佳实践。


一、AOP核心概念解析

1.1 什么是切面编程

AOP通过横切关注点(Cross-Cutting Concerns)将通用功能(如日志、事务、安全)从业务逻辑中解耦。其核心思想是:通过预编译或运行时动态代理,在不修改源代码的情况下增强方法功能。

1.2 AOP核心术语

术语说明
Aspect模块化的横切关注点(如日志切面)
Join Point程序执行过程中的某个点(如方法调用)
Pointcut匹配Join Point的谓词表达式
Advice在特定Join Point执行的动作(Before/After/Around等)
Weaving将切面代码植入目标对象的过程

二、环境准备(Spring Boot)

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

三、审计日志实战

3.1 自定义日志注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
    String value() default "";
    boolean trackParams() default true;
    boolean trackResult() default false;
}

3.2 日志切面实现

@Aspect
@Component
public class AuditLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class);

    @Around("@annotation(auditLog)")
    public Object logAround(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
        
        // 记录入参
        if (auditLog.trackParams()) {
            Object[] args = joinPoint.getArgs();
            logger.info("[Audit] 方法 {} 入参: {}", methodName, Arrays.toString(args));
        }

        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long elapsed = System.currentTimeMillis() - start;

        // 记录出参
        if (auditLog.trackResult()) {
            logger.info("[Audit] 方法 {} 返回: {}", methodName, result);
        }

        logger.info("[Audit] 方法 {} 执行耗时: {}ms", methodName, elapsed);
        return result;
    }
}

关键说明:

  • @Around 环绕通知可完全控制方法执行
  • ProceedingJoinPoint.proceed() 用于继续执行被拦截的方法
  • 通过MethodSignature获取完整方法签名
  • 条件日志记录通过注解参数灵活控制

四、权限控制实战

4.1 自定义权限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
    String[] value();
}

4.2 权限校验切面

@Aspect
@Component
public class AuthorizationAspect {
    @Autowired
    private UserContext userContext; // 假设已实现用户上下文

    @Before("@annotation(requiresPermission)")
    public void checkPermission(JoinPoint joinPoint, RequiresPermission requiresPermission) {
        Set<String> userPermissions = userContext.getCurrentUserPermissions();
        String[] requiredPermissions = requiresPermission.value();

        boolean hasPermission = Arrays.stream(requiredPermissions)
                .allMatch(userPermissions::contains);

        if (!hasPermission) {
            throw new AccessDeniedException("权限不足,需要权限: " 
                + Arrays.toString(requiredPermissions));
        }
    }
}

注意事项:

  • 使用@Before确保方法执行前进行校验
  • 权限验证逻辑应放在切面而非Controller
  • 建议配合Spring Security使用更完善的权限体系

五、集成测试示例

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @AuditLog(trackParams = true, trackResult = false)
    @RequiresPermission({"USER_QUERY"})
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    @AuditLog("创建用户")
    @RequiresPermission({"USER_MANAGE"})
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

测试结果示例:

[Audit] 方法 com.example.UserController.getUser 入参: [123]
[Audit] 方法 com.example.UserController.getUser 执行耗时: 45ms

六、生产级优化建议

  1. 切面执行顺序:通过@Order控制多个切面的执行顺序
  2. 性能优化:高频方法建议关闭详细日志记录
  3. 异常处理:在切面中统一处理异常并记录
  4. 异步日志:使用AsyncAppender避免阻塞主线程
  5. 权限缓存:将权限数据缓存到Redis提升验证效率

七、总结

本文实现了基于Spring AOP的两种典型应用场景,展示了如何通过:

  1. 自定义注解声明元数据
  2. 切面实现横切关注点
  3. 动态代理实现方法增强

扩展方向建议:

  • 结合Spring Security进行权限体系升级
  • 集成EL表达式实现动态权限控制
  • 使用MDC实现请求链路追踪
  • 对接日志分析平台(ELK/Splunk)

通过AOP实现关注点分离,可使业务代码保持简洁,同时提升系统的可维护性和扩展性。

;