在企业级应用开发中,审计日志和权限控制是两个关键的非功能性需求。传统实现方式往往导致代码重复度高、维护成本大。本文将通过Spring AOP(Aspect-Oriented Programming)结合自定义注解,演示如何优雅地实现这两个核心功能。文章包含完整代码示例、切面编程原理剖析及生产环境最佳实践。
AOP通过横切关注点(Cross-Cutting Concerns)将通用功能(如日志、事务、安全)从业务逻辑中解耦。其核心思想是:通过预编译或运行时动态代理,在不修改源代码的情况下增强方法功能。
术语 | 说明 |
---|---|
Aspect | 模块化的横切关注点(如日志切面) |
Join Point | 程序执行过程中的某个点(如方法调用) |
Pointcut | 匹配Join Point的谓词表达式 |
Advice | 在特定Join Point执行的动作(Before/After/Around等) |
Weaving | 将切面代码植入目标对象的过程 |
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
String value() default "";
boolean trackParams() default true;
boolean trackResult() default false;
}
@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
获取完整方法签名@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String[] value();
}
@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
确保方法执行前进行校验@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
控制多个切面的执行顺序本文实现了基于Spring AOP的两种典型应用场景,展示了如何通过:
扩展方向建议:
通过AOP实现关注点分离,可使业务代码保持简洁,同时提升系统的可维护性和扩展性。