题目
设计可扩展的注解处理器实现动态权限校验
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
自定义注解定义, 反射动态处理注解, 注解生命周期控制, 复杂条件判断
快速回答
实现步骤:
- 定义
@RequiresPermission注解包含权限码和操作类型 - 通过AOP或拦截器创建注解处理器,使用反射获取方法注解
- 解析注解参数,结合ThreadLocal获取当前用户上下文
- 实现权限校验逻辑,支持动态条件判断
- 处理嵌套注解和继承关系
- 添加缓存优化反射性能
注意事项:
- 确保
@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 |
最佳实践
- 防御式编程:校验注解属性非空
- 异常设计:抛出包含注解信息的业务异常
- 单元测试:覆盖以下场景:
- 接口默认方法注解
- 父类抽象方法注解
- 条件表达式边界值
扩展知识
- 编译时处理:使用APT生成权限元数据
- JVM底层原理:注解信息存储在Class文件的RuntimeVisibleAnnotations属性
- 替代方案:
- JDK动态代理:
InvocationHandler处理接口方法 - Byte Buddy:直接修改字节码实现更高效处理
- JDK动态代理: