题目
设计一个基于反射和注解的运行时方法参数验证框架
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
自定义注解设计,反射动态处理,注解运行时解析,复杂校验逻辑,性能优化
快速回答
实现要点:
- 定义
@ParamCheck注解包含校验规则(非空、正则、范围等) - 通过反射获取方法参数注解和值
- 设计
Validator接口实现多种校验规则 - 使用动态代理/AOP拦截方法调用
- 缓存
Method元数据提升性能 - 校验失败抛出结构化异常
问题场景
在金融系统开发中,需要确保核心方法的输入参数符合严格业务规则(如身份证格式、金额范围等)。要求设计一个可扩展的运行时验证框架,避免在每个方法中重复编写校验代码。
核心实现
1. 自定义注解设计
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface ParamCheck {
boolean notNull() default false;
String regex() default "";
double min() default Double.MIN_VALUE;
double max() default Double.MAX_VALUE;
// 可扩展自定义校验器
Class<? extends Validator> validator() default DefaultValidator.class;
}2. 校验器接口设计
public interface Validator<T> {
boolean validate(T value);
}
// 示例:身份证校验器
public class IdCardValidator implements Validator<String> {
@Override
public boolean validate(String value) {
return value.matches("[1-9]\\d{16}[0-9Xx]");
}
}3. 反射处理核心逻辑
public class ValidationInterceptor implements InvocationHandler {
// 缓存Method元数据提升性能
private static final Map<Method, List<ParamCheck>> cache = new ConcurrentHashMap<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
List<ParamCheck> checks = cache.computeIfAbsent(method, m ->
Arrays.stream(m.getParameters())
.map(p -> p.getAnnotation(ParamCheck.class))
.collect(Collectors.toList())
);
for (int i = 0; i < args.length; i++) {
if (checks.get(i) != null) {
validateParam(args[i], checks.get(i));
}
}
return method.invoke(target, args);
}
private void validateParam(Object value, ParamCheck check) {
// 实现多级校验逻辑
if (check.notNull() && value == null) {
throw new ValidationException("参数不能为空");
}
if (!check.regex().isEmpty() && value instanceof String) {
if (!((String) value).matches(check.regex())) {
throw new ValidationException("格式错误");
}
}
// 调用自定义校验器
Validator validator = check.validator().newInstance();
if (!validator.validate(value)) {
throw new ValidationException("自定义校验失败");
}
}
}最佳实践
- 性能优化:缓存反射获取的
Method和注解信息,避免重复解析 - 校验隔离:每个校验规则独立实现,遵循单一职责原则
- 异常设计:抛出包含参数名、位置、失败原因的详细异常
- 组合注解:支持
@NotNull @Pattern(regex="\d+")组合使用
常见错误
- 反射性能陷阱:未缓存
Method.getParameters()导致频繁触发JNI调用 - 注解误用:将
@Target错误设置为ElementType.TYPE导致无法参数注解 - 线程安全问题:校验器实现中使用了可变状态
- 类型不匹配:对非String参数使用regex校验
扩展知识
- 编译时处理:使用APT在编译期生成校验代码(如Lombok原理),但无法实现动态规则
- Bean Validation:对比JSR 380规范(Hibernate Validator),了解
@NotNull等标准注解实现 - 混合方案:重要路径使用编译时校验,动态规则采用运行时方案
- JDK14+优化:使用
MethodHandles.Lookup替代反射API提升性能