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
六、生产级优化建议
- 切面执行顺序:通过
@Order
控制多个切面的执行顺序 - 性能优化:高频方法建议关闭详细日志记录
- 异常处理:在切面中统一处理异常并记录
- 异步日志:使用AsyncAppender避免阻塞主线程
- 权限缓存:将权限数据缓存到Redis提升验证效率
七、总结
本文实现了基于Spring AOP的两种典型应用场景,展示了如何通过:
- 自定义注解声明元数据
- 切面实现横切关注点
- 动态代理实现方法增强
扩展方向建议:
- 结合Spring Security进行权限体系升级
- 集成EL表达式实现动态权限控制
- 使用MDC实现请求链路追踪
- 对接日志分析平台(ELK/Splunk)
通过AOP实现关注点分离,可使业务代码保持简洁,同时提升系统的可维护性和扩展性。