# Spring AOP 自定义注解详解 ## 一、为什么使用自定义注解配合AOP ### 1.1 优势 1. **代码简洁**:只需在方法上添加注解即可 2. **语义明确**:注解名称直接表达意图 3. **易于维护**:修改切面逻辑不影响业务代码 4. **灵活配置**:通过注解参数传递配置信息 5. **解耦合**:业务逻辑与横切关注点分离 ### 1.2 对比传统方式 ```java // 传统方式:基于方法名匹配 @Pointcut("execution(* com.example.service.*.*(..))") // 注解方式:更精确、更灵活 @Pointcut("@annotation(com.example.annotation.Log)") ``` ## 二、自定义注解的创建 ### 2.1 基本结构 ```java @Target(ElementType.METHOD) // 注解作用目标 @Retention(RetentionPolicy.RUNTIME) // 注解保留策略 @Documented // 生成文档 public @interface Log { String value() default ""; // 注解参数 String module() default ""; // 模块名 LogLevel level() default LogLevel.INFO; // 日志级别 } // 枚举类型 public enum LogLevel { DEBUG, INFO, WARN, ERROR } ``` ### 2.2 元注解说明 - **@Target**:指定注解可以用在哪些地方 - ElementType.METHOD:方法 - ElementType.TYPE:类 - ElementType.FIELD:字段 - ElementType.PARAMETER:参数 - **@Retention**:指定注解的生命周期 - RetentionPolicy.SOURCE:源码期 - RetentionPolicy.CLASS:编译期 - RetentionPolicy.RUNTIME:运行期(AOP必须使用) - **@Documented**:注解信息包含在JavaDoc中 - **@Inherited**:子类可以继承父类的注解 ## 三、完整示例:日志注解 ### 3.1 创建日志注解 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { // 操作描述 String value() default ""; // 操作类型 OperationType type() default OperationType.OTHER; // 是否保存请求参数 boolean saveParams() default true; // 是否保存响应结果 boolean saveResult() default true; } // 操作类型枚举 public enum OperationType { INSERT("新增"), UPDATE("修改"), DELETE("删除"), SELECT("查询"), EXPORT("导出"), IMPORT("导入"), OTHER("其他"); private final String desc; OperationType(String desc) { this.desc = desc; } public String getDesc() { return desc; } } ``` ### 3.2 创建日志切面 ```java @Aspect @Component @Slf4j public class SysLogAspect { @Autowired private SysLogService sysLogService; @Autowired private HttpServletRequest request; // 使用@annotation匹配带有@SysLog注解的方法 @Pointcut("@annotation(com.example.annotation.SysLog)") public void logPointCut() {} @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); // 获取注解信息 SysLog sysLog = getAnnotation(point); // 创建日志对象 SysLogEntity logEntity = new SysLogEntity(); logEntity.setOperation(sysLog.value()); logEntity.setType(sysLog.type().getDesc()); logEntity.setCreateTime(new Date()); // 获取请求信息 logEntity.setIp(getIpAddress(request)); logEntity.setUrl(request.getRequestURL().toString()); logEntity.setMethod(request.getMethod()); // 获取用户信息 String username = getCurrentUsername(); logEntity.setUsername(username); // 获取方法信息 String className = point.getTarget().getClass().getName(); String methodName = point.getSignature().getName(); logEntity.setClassName(className); logEntity.setMethodName(methodName); // 保存请求参数 if (sysLog.saveParams()) { Object[] args = point.getArgs(); String params = JSON.toJSONString(args); logEntity.setParams(params); } try { // 执行方法 Object result = point.proceed(); // 保存响应结果 if (sysLog.saveResult() && result != null) { String resultStr = JSON.toJSONString(result); logEntity.setResult(resultStr); } // 设置执行时间 long time = System.currentTimeMillis() - beginTime; logEntity.setTime(time); logEntity.setStatus(1); // 成功 return result; } catch (Throwable e) { // 记录异常信息 logEntity.setStatus(0); // 失败 logEntity.setError(e.getMessage()); logEntity.setTime(System.currentTimeMillis() - beginTime); throw e; } finally { // 异步保存日志 CompletableFuture.runAsync(() -> sysLogService.save(logEntity)); } } // 获取注解对象 private SysLog getAnnotation(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); return method.getAnnotation(SysLog.class); } // 获取IP地址 private String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } // 获取当前用户名(示例) private String getCurrentUsername() { // 从Security Context或Session中获取 return "admin"; } } ``` ### 3.3 使用示例 ```java @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping @SysLog(value = "新增用户", type = OperationType.INSERT) public Result createUser(@RequestBody User user) { User savedUser = userService.save(user); return Result.success(savedUser); } @PutMapping("/{id}") @SysLog(value = "修改用户信息", type = OperationType.UPDATE) public Result updateUser(@PathVariable Long id, @RequestBody User user) { user.setId(id); User updatedUser = userService.update(user); return Result.success(updatedUser); } @DeleteMapping("/{id}") @SysLog(value = "删除用户", type = OperationType.DELETE, saveResult = false) public Result deleteUser(@PathVariable Long id) { userService.delete(id); return Result.success(); } @GetMapping("/{id}") @SysLog(value = "查询用户详情", type = OperationType.SELECT, saveParams = false) public Result getUser(@PathVariable Long id) { User user = userService.findById(id); return Result.success(user); } } ``` ## 四、实战案例集合 ### 4.1 权限控制注解 ```java // 权限注解定义 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequiresPermission { // 权限标识 String[] value(); // 逻辑关系:AND 或 OR Logical logical() default Logical.AND; enum Logical { AND, OR } } // 角色注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequiresRole { String[] value(); Logical logical() default Logical.AND; } // 权限切面 @Aspect @Component public class PermissionAspect { @Autowired private AuthService authService; @Before("@annotation(permission)") public void checkPermission(RequiresPermission permission) { String[] requiredPerms = permission.value(); Logical logical = permission.logical(); Set userPerms = authService.getCurrentUserPermissions(); boolean hasPermission = false; if (logical == Logical.AND) { // 需要所有权限 hasPermission = userPerms.containsAll(Arrays.asList(requiredPerms)); } else { // 只需要其中一个权限 hasPermission = Arrays.stream(requiredPerms) .anyMatch(userPerms::contains); } if (!hasPermission) { throw new UnauthorizedException("权限不足:需要权限 " + Arrays.toString(requiredPerms)); } } @Before("@annotation(role)") public void checkRole(RequiresRole role) { String[] requiredRoles = role.value(); Set userRoles = authService.getCurrentUserRoles(); boolean hasRole = Arrays.stream(requiredRoles) .anyMatch(userRoles::contains); if (!hasRole) { throw new UnauthorizedException("角色不足:需要角色 " + Arrays.toString(requiredRoles)); } } } // 使用示例 @RestController @RequestMapping("/admin") public class AdminController { @GetMapping("/users") @RequiresPermission("user:view") public List listUsers() { // 查看用户列表 } @PostMapping("/users") @RequiresPermission({"user:create", "user:edit"}) public User createUser(@RequestBody User user) { // 创建用户(需要同时有创建和编辑权限) } @DeleteMapping("/users/{id}") @RequiresRole("ADMIN") @RequiresPermission("user:delete") public void deleteUser(@PathVariable Long id) { // 删除用户(需要管理员角色和删除权限) } } ``` ### 4.2 缓存注解 ```java // 缓存注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Cache { // 缓存key前缀 String prefix() default ""; // 缓存key(支持SpEL表达式) String key() default ""; // 过期时间(秒) int expire() default 300; // 条件(SpEL表达式,返回true时才缓存) String condition() default ""; } // 清除缓存注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheEvict { String prefix() default ""; String key() default ""; boolean allEntries() default false; } // 缓存切面 @Aspect @Component public class CacheAspect { @Autowired private RedisTemplate redisTemplate; private final SpelExpressionParser parser = new SpelExpressionParser(); @Around("@annotation(cache)") public Object cache(ProceedingJoinPoint point, Cache cache) throws Throwable { // 构建缓存key String key = buildKey(point, cache.prefix(), cache.key()); // 检查条件 if (!checkCondition(point, cache.condition())) { return point.proceed(); } // 尝试从缓存获取 Object cached = redisTemplate.opsForValue().get(key); if (cached != null) { log.debug("缓存命中: {}", key); return cached; } // 执行方法 Object result = point.proceed(); // 存入缓存 if (result != null) { redisTemplate.opsForValue().set(key, result, cache.expire(), TimeUnit.SECONDS); log.debug("缓存存储: {}", key); } return result; } @After("@annotation(evict)") public void evict(JoinPoint point, CacheEvict evict) { if (evict.allEntries()) { // 清除所有缓存 Set keys = redisTemplate.keys(evict.prefix() + "*"); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } } else { // 清除指定缓存 String key = buildKey(point, evict.prefix(), evict.key()); redisTemplate.delete(key); } } // 构建缓存key private String buildKey(JoinPoint point, String prefix, String key) { if (StringUtils.isEmpty(key)) { // 默认使用方法签名作为key return prefix + ":" + point.getSignature().toLongString(); } // 解析SpEL表达式 EvaluationContext context = new StandardEvaluationContext(); Method method = ((MethodSignature) point.getSignature()).getMethod(); String[] paramNames = new LocalVariableTableParameterNameDiscoverer() .getParameterNames(method); Object[] args = point.getArgs(); for (int i = 0; i < paramNames.length; i++) { context.setVariable(paramNames[i], args[i]); } Expression expression = parser.parseExpression(key); return prefix + ":" + expression.getValue(context, String.class); } // 检查条件 private boolean checkCondition(JoinPoint point, String condition) { if (StringUtils.isEmpty(condition)) { return true; } // 解析条件表达式 EvaluationContext context = new StandardEvaluationContext(); // ... 设置变量 Expression expression = parser.parseExpression(condition); return Boolean.TRUE.equals(expression.getValue(context, Boolean.class)); } } // 使用示例 @Service public class ProductService { @Cache(prefix = "product", key = "#id", expire = 600) public Product findById(Long id) { // 从数据库查询 return productRepository.findById(id); } @Cache(prefix = "product", key = "#category + ':' + #page", expire = 300) public List findByCategory(String category, int page) { // 分页查询 return productRepository.findByCategory(category, page); } @CacheEvict(prefix = "product", key = "#product.id") public void updateProduct(Product product) { // 更新产品,清除缓存 productRepository.save(product); } @CacheEvict(prefix = "product", allEntries = true) public void clearAllCache() { // 清除所有产品缓存 } } ``` ### 4.3 限流注解 ```java // 限流注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimit { // 限流key String key() default ""; // 时间窗口(秒) int period() default 60; // 限流次数 int count() default 100; // 限流类型 LimitType limitType() default LimitType.IP; enum LimitType { IP, // 根据IP限流 USER, // 根据用户限流 GLOBAL, // 全局限流 CUSTOM // 自定义key } } // 限流切面 @Aspect @Component @Slf4j public class RateLimitAspect { @Autowired private RedisTemplate redisTemplate; @Autowired private HttpServletRequest request; @Before("@annotation(rateLimit)") public void limit(JoinPoint point, RateLimit rateLimit) { String key = buildLimitKey(point, rateLimit); // 使用Redis实现滑动窗口限流 long currentTime = System.currentTimeMillis(); long windowStart = currentTime - rateLimit.period() * 1000; // 移除窗口外的记录 redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart); // 获取窗口内的请求数 Long count = redisTemplate.opsForZSet().count(key, windowStart, currentTime); if (count != null && count >= rateLimit.count()) { throw new RateLimitException("访问太频繁,请稍后再试"); } // 添加当前请求 redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTime); // 设置过期时间 redisTemplate.expire(key, rateLimit.period(), TimeUnit.SECONDS); } private String buildLimitKey(JoinPoint point, RateLimit rateLimit) { StringBuilder key = new StringBuilder("rate_limit:"); switch (rateLimit.limitType()) { case IP: key.append(getIpAddress()).append(":"); break; case USER: key.append(getCurrentUserId()).append(":"); break; case GLOBAL: key.append("global:"); break; case CUSTOM: key.append(rateLimit.key()).append(":"); break; } // 添加方法签名 key.append(point.getSignature().toLongString()); return key.toString(); } private String getIpAddress() { // 获取真实IP String ip = request.getHeader("X-Real-IP"); if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } private String getCurrentUserId() { // 从认证信息中获取用户ID return "user123"; } } // 使用示例 @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/search") @RateLimit(period = 60, count = 10, limitType = LimitType.IP) public Result search(@RequestParam String keyword) { // IP限流:每个IP每分钟最多10次 } @PostMapping("/order") @RateLimit(period = 3600, count = 100, limitType = LimitType.USER) public Result createOrder(@RequestBody Order order) { // 用户限流:每个用户每小时最多100单 } @GetMapping("/hot-deal") @RateLimit(period = 1, count = 1000, limitType = LimitType.GLOBAL) public Result getHotDeal() { // 全局限流:每秒最多1000次请求 } } ``` ### 4.4 重试注解 ```java // 重试注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Retry { // 重试次数 int maxAttempts() default 3; // 重试间隔(毫秒) long delay() default 1000; // 指数退避 boolean exponentialBackoff() default false; // 需要重试的异常类型 Class[] retryFor() default {Exception.class}; // 不需要重试的异常类型 Class[] noRetryFor() default {}; } // 重试切面 @Aspect @Component @Slf4j public class RetryAspect { @Around("@annotation(retry)") public Object retry(ProceedingJoinPoint point, Retry retry) throws Throwable { int attempts = 0; long delay = retry.delay(); while (attempts < retry.maxAttempts()) { attempts++; try { return point.proceed(); } catch (Throwable e) { // 检查是否需要重试 if (!shouldRetry(e, retry)) { throw e; } if (attempts >= retry.maxAttempts()) { log.error("重试{}次后仍然失败", retry.maxAttempts()); throw e; } log.warn("方法执行失败,{}ms后进行第{}次重试", delay, attempts); Thread.sleep(delay); // 指数退避 if (retry.exponentialBackoff()) { delay = delay * 2; } } } throw new IllegalStateException("不应该到达这里"); } private boolean shouldRetry(Throwable e, Retry retry) { // 检查不需要重试的异常 for (Class noRetryClass : retry.noRetryFor()) { if (noRetryClass.isAssignableFrom(e.getClass())) { return false; } } // 检查需要重试的异常 for (Class retryClass : retry.retryFor()) { if (retryClass.isAssignableFrom(e.getClass())) { return true; } } return false; } } // 使用示例 @Service public class PaymentService { @Retry(maxAttempts = 3, delay = 2000, retryFor = {TimeoutException.class, ConnectException.class}) public PaymentResult processPayment(PaymentRequest request) { // 支付处理,可能超时 return paymentGateway.process(request); } @Retry(maxAttempts = 5, delay = 1000, exponentialBackoff = true) public void sendNotification(String message) { // 发送通知,使用指数退避策略 notificationService.send(message); } } ``` ### 4.5 参数校验注解 ```java // 参数校验注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ValidateParams { // 是否校验请求参数 boolean validateRequest() default true; // 是否校验响应结果 boolean validateResponse() default false; // 校验组 Class[] groups() default {}; } // 校验切面 @Aspect @Component @Slf4j public class ValidationAspect { @Autowired private Validator validator; @Around("@annotation(validateParams)") public Object validate(ProceedingJoinPoint point, ValidateParams validateParams) throws Throwable { // 校验请求参数 if (validateParams.validateRequest()) { Object[] args = point.getArgs(); for (Object arg : args) { if (arg != null && !isBasicType(arg)) { Set> violations = validator.validate(arg, validateParams.groups()); if (!violations.isEmpty()) { throw new ValidationException(buildErrorMessage(violations)); } } } } // 执行方法 Object result = point.proceed(); // 校验响应结果 if (validateParams.validateResponse() && result != null) { Set> violations = validator.validate(result, validateParams.groups()); if (!violations.isEmpty()) { log.error("响应数据校验失败: {}", buildErrorMessage(violations)); } } return result; } private boolean isBasicType(Object obj) { return obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj instanceof Character; } private String buildErrorMessage(Set> violations) { return violations.stream() .map(v -> v.getPropertyPath() + " " + v.getMessage()) .collect(Collectors.joining(", ")); } } // 使用示例 @RestController @RequestMapping("/user") public class UserController { @PostMapping @ValidateParams(groups = {Create.class}) public Result createUser(@RequestBody User user) { // 自动校验User对象的Create组约束 return Result.success(userService.save(user)); } @PutMapping("/{id}") @ValidateParams(groups = {Update.class}, validateResponse = true) public Result updateUser(@PathVariable Long id, @RequestBody User user) { // 校验请求和响应 return Result.success(userService.update(user)); } } // 实体类 @Data public class User { @Null(groups = Create.class) @NotNull(groups = Update.class) private Long id; @NotBlank(message = "用户名不能为空") @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间") private String username; @Email(message = "邮箱格式不正确") private String email; @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; } ``` ## 五、高级技巧 ### 5.1 组合注解 ```java // 组合多个注解功能 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @SysLog @RequiresPermission @RateLimit public @interface SecureApi { // 日志描述 String value() default ""; // 需要的权限 String[] permissions() default {}; // 限流配置 int rateLimit() default 100; int ratePeriod() default 60; } // 使用组合注解 @PostMapping("/sensitive-operation") @SecureApi( value = "执行敏感操作", permissions = {"admin:sensitive:operate"}, rateLimit = 10, ratePeriod = 60 ) public Result sensitiveOperation(@RequestBody Request request) { // 一个注解实现:日志记录 + 权限校验 + 限流 } ``` ### 5.2 注解继承 ```java // 基础审计注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Auditable { boolean enabled() default true; } // 继承审计功能的业务注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Auditable public @interface BankingOperation { String operationType(); RiskLevel riskLevel() default RiskLevel.LOW; enum RiskLevel { LOW, MEDIUM, HIGH, CRITICAL } } // 统一处理所有审计注解 @Aspect @Component public class AuditAspect { @Around("@annotation(auditable) || @annotation(banking)") public Object audit(ProceedingJoinPoint point, Auditable auditable, BankingOperation banking) throws Throwable { // 处理审计逻辑 } } ``` ### 5.3 动态注解处理 ```java @Aspect @Component public class DynamicAnnotationAspect { @Around("@annotation(anno)") public Object handleDynamically(ProceedingJoinPoint point, Annotation anno) throws Throwable { // 动态获取注解类型 Class annotationType = anno.annotationType(); // 根据不同注解类型执行不同逻辑 if (annotationType == Cache.class) { return handleCache(point, (Cache) anno); } else if (annotationType == Log.class) { return handleLog(point, (Log) anno); } return point.proceed(); } } ``` ## 六、最佳实践总结 ### 6.1 设计原则 1. **单一职责**:每个注解只负责一个功能 2. **语义清晰**:注解名称要表达明确意图 3. **参数简洁**:只提供必要的配置参数 4. **默认值合理**:为参数提供合理的默认值 ### 6.2 性能优化 1. **异步处理**:日志等操作使用异步方式 2. **缓存结果**:避免重复计算 3. **条件判断**:提前判断是否需要执行切面逻辑 ### 6.3 错误处理 1. **异常传播**:不要吞掉业务异常 2. **降级处理**:切面异常不应影响业务执行 3. **日志记录**:记录关键错误信息 ### 6.4 测试建议 ```java @SpringBootTest public class AopTest { @MockBean private SysLogService logService; @Autowired private UserService userService; @Test public void testLogAspect() { // 测试日志切面 User user = new User(); userService.createUser(user); // 验证日志服务被调用 verify(logService, times(1)).save(any(SysLogEntity.class)); } } ``` 自定义注解配合AOP是Spring中非常强大和优雅的编程方式,能够让代码更加模块化、可维护和可扩展。