notes/Spring-AOP-自定义注解详解.md
root 2a876d1d38 添加Spring AOP学习笔记
- 创建Spring AOP详细学习笔记,包含基础概念、使用方式、切入点表达式等
- 创建Spring AOP自定义注解详解,包含实战案例和最佳实践
- 涵盖日志、权限、缓存、限流、重试等常见应用场景

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-26 01:15:22 +08:00

27 KiB
Raw Blame History

Spring AOP 自定义注解详解

一、为什么使用自定义注解配合AOP

1.1 优势

  1. 代码简洁:只需在方法上添加注解即可
  2. 语义明确:注解名称直接表达意图
  3. 易于维护:修改切面逻辑不影响业务代码
  4. 灵活配置:通过注解参数传递配置信息
  5. 解耦合:业务逻辑与横切关注点分离

1.2 对比传统方式

// 传统方式:基于方法名匹配
@Pointcut("execution(* com.example.service.*.*(..))")

// 注解方式:更精确、更灵活
@Pointcut("@annotation(com.example.annotation.Log)")

二、自定义注解的创建

2.1 基本结构

@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 创建日志注解

@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 创建日志切面

@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 使用示例

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping
    @SysLog(value = "新增用户", type = OperationType.INSERT)
    public Result<User> createUser(@RequestBody User user) {
        User savedUser = userService.save(user);
        return Result.success(savedUser);
    }
    
    @PutMapping("/{id}")
    @SysLog(value = "修改用户信息", type = OperationType.UPDATE)
    public Result<User> 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<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return Result.success();
    }
    
    @GetMapping("/{id}")
    @SysLog(value = "查询用户详情", type = OperationType.SELECT, saveParams = false)
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return Result.success(user);
    }
}

四、实战案例集合

4.1 权限控制注解

// 权限注解定义
@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<String> 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<String> 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<User> 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 缓存注解

// 缓存注解
@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<String, Object> 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<String> 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<Product> 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 限流注解

// 限流注解
@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<String, Object> 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 重试注解

// 重试注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retry {
    // 重试次数
    int maxAttempts() default 3;
    
    // 重试间隔(毫秒)
    long delay() default 1000;
    
    // 指数退避
    boolean exponentialBackoff() default false;
    
    // 需要重试的异常类型
    Class<? extends Throwable>[] retryFor() default {Exception.class};
    
    // 不需要重试的异常类型
    Class<? extends Throwable>[] 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<? extends Throwable> noRetryClass : retry.noRetryFor()) {
            if (noRetryClass.isAssignableFrom(e.getClass())) {
                return false;
            }
        }
        
        // 检查需要重试的异常
        for (Class<? extends Throwable> 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 参数校验注解

// 参数校验注解
@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<ConstraintViolation<Object>> violations = validator.validate(arg, validateParams.groups());
                    if (!violations.isEmpty()) {
                        throw new ValidationException(buildErrorMessage(violations));
                    }
                }
            }
        }
        
        // 执行方法
        Object result = point.proceed();
        
        // 校验响应结果
        if (validateParams.validateResponse() && result != null) {
            Set<ConstraintViolation<Object>> 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<ConstraintViolation<Object>> 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<User> createUser(@RequestBody User user) {
        // 自动校验User对象的Create组约束
        return Result.success(userService.save(user));
    }
    
    @PutMapping("/{id}")
    @ValidateParams(groups = {Update.class}, validateResponse = true)
    public Result<User> 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 组合注解

// 组合多个注解功能
@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 注解继承

// 基础审计注解
@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 动态注解处理

@Aspect
@Component
public class DynamicAnnotationAspect {
    
    @Around("@annotation(anno)")
    public Object handleDynamically(ProceedingJoinPoint point, Annotation anno) throws Throwable {
        // 动态获取注解类型
        Class<? extends Annotation> 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 测试建议

@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中非常强大和优雅的编程方式能够让代码更加模块化、可维护和可扩展。