侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计可扩展的注解处理器实现动态权限校验

2025-12-13 / 0 评论 / 4 阅读

题目

设计可扩展的注解处理器实现动态权限校验

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

自定义注解定义, 反射动态处理注解, 注解生命周期控制, 复杂条件判断

快速回答

实现步骤:

  1. 定义@RequiresPermission注解包含权限码和操作类型
  2. 通过AOP或拦截器创建注解处理器,使用反射获取方法注解
  3. 解析注解参数,结合ThreadLocal获取当前用户上下文
  4. 实现权限校验逻辑,支持动态条件判断
  5. 处理嵌套注解和继承关系
  6. 添加缓存优化反射性能

注意事项:

  • 确保@Retention(RetentionPolicy.RUNTIME)
  • 处理桥接方法和接口默认方法
  • 校验失败抛出带注解信息的异常
## 解析

问题场景

在复杂权限系统中,需要根据方法注解动态校验权限。例如:@RequiresPermission(perm="order:delete", condition="#order.owner == principal.id") 要求当前用户必须是订单所有者才能删除。

核心实现

1. 定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermission {
    String value();  // 权限标识符
    String condition() default "";  // SpEL表达式
}

2. 注解处理器(AOP示例)

@Aspect
@Component
public class PermissionAspect {

    // 使用缓存提升反射性能
    private final Map<Method, RequiresPermission> annotationCache = new ConcurrentHashMap<>();

    @Around("@within(org.springframework.stereotype.Service)")
    public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        // 处理桥接方法
        if (method.isBridge()) {
            method = BridgeMethodResolver.findBridgedMethod(method);
        }

        // 从缓存或反射获取注解
        RequiresPermission ann = annotationCache.computeIfAbsent(method, m -> 
            AnnotationUtils.findAnnotation(m, RequiresPermission.class)
        );

        if (ann != null) {
            // 获取当前用户(实际从SecurityContext获取)
            User user = CurrentUserHolder.get();

            // 解析SpEL表达式
            EvaluationContext context = new StandardEvaluationContext();
            context.setVariable("principal", user);
            context.setVariable("args", pjp.getArgs());

            ExpressionParser parser = new SpelExpressionParser();
            if (!parser.parseExpression(ann.condition()).getValue(context, Boolean.class)) {
                throw new PermissionDeniedException("缺少权限: " + ann.value());
            }
        }
        return pjp.proceed();
    }
}

关键难点解决

1. 方法继承处理

使用Spring的AnnotationUtils.findAnnotation()而非JDK原生方法,可处理:

  • 接口上的注解
  • 父类方法注解元注解(注解上的注解)

2. 性能优化

  • 缓存设计:ConcurrentHashMap缓存Method与注解的映射
  • 避免重复解析:预编译SpEL表达式

3. 条件表达式进阶

// 支持引用方法参数
@RequiresPermission(
  value = "order:update",
  condition = "#order.id != null && #order.owner == principal.id"
)
public void updateOrder(Order order) { ... }

常见错误

错误类型 后果 解决方案
忘记设置@Retention(RUNTIME) 运行时无法获取注解 显式声明保留策略
直接使用method.getAnnotation() 无法获取继承的注解 使用Spring AnnotationUtils
未处理桥接方法 泛型方法注解丢失 调用BridgeMethodResolver

最佳实践

  1. 防御式编程:校验注解属性非空
  2. 异常设计:抛出包含注解信息的业务异常
  3. 单元测试:覆盖以下场景:
    • 接口默认方法注解
    • 父类抽象方法注解
    • 条件表达式边界值

扩展知识

  • 编译时处理:使用APT生成权限元数据
  • JVM底层原理:注解信息存储在Class文件的RuntimeVisibleAnnotations属性
  • 替代方案
    • JDK动态代理:InvocationHandler处理接口方法
    • Byte Buddy:直接修改字节码实现更高效处理