自定义注解配合反射实现通用字段校验,核心是“标记+扫描+执行”三步闭环:通过运行时注解声明校验意图,反射定位带注解字段,再调用验证逻辑;不依赖Spring Validation,也无需重复if判断,真正一次定义、多处复用。

自定义注解配合反射实现通用字段校验,核心在于“标记 + 扫描 + 执行”三步闭环:用注解声明校验意图,用反射定位带注解的字段,再调用对应验证逻辑完成检查。它不依赖 Spring Validation 框架,也不需要为每个字段写 if 判断,真正实现一次定义、多处复用。
一、定义运行时注解,明确校验语义
注解必须保留到运行时(@Retention(RUNTIME)),且能标注在字段上(@Target(FIELD))。例如定义一个非空校验注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
String message() default "字段不能为空";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
注意:message、groups、payload 是 JSR-303 规范要求的三个标准属性,即使不用 Bean Validation 框架,也建议保留,便于后续扩展或与框架兼容。
二、编写通用校验器,用反射遍历并执行检查
校验器是一个普通工具类,接收任意对象,通过反射获取其所有字段,判断是否带有指定注解,再根据注解类型执行对应逻辑:
- 调用
obj.getClass().getDeclaredFields()获取全部字段 - 对每个字段调用
field.isAnnotationPresent(NotNull.class) - 设为可访问(
field.setAccessible(true)),再用field.get(obj)取值 - 若值为 null,抛出含
annotation.message()的异常或收集错误信息
关键点:字段可能在父类中,需递归扫描 getSuperclass();私有字段必须设为 accessible;基本类型需处理包装类与默认值差异(如 int 默认 0,但不是 null)。
三、支持多注解组合与动态扩展
一个字段可同时标注多个校验注解(如 @NotNull + @Range(min=1, max=100)),校验器应统一扫描、逐个触发:
- 把不同注解映射到不同验证器(如
NotNullValidator、RangeValidator) - 用
Map<class extends annotation>, Validator></class>管理策略 - 新增校验规则只需定义新注解 + 新验证器,无需修改主校验流程
例如枚举校验可定义 @EnumValue(enumClass = Status.class),验证器内部用 enumClass.getDeclaredMethod("valueOf", String.class).invoke(null, value) 尝试转换,失败即不合法——完全泛化,不耦合具体枚举类型。
四、集成使用:手动触发或结合 AOP 自动拦截
最轻量用法是在业务入口手动调用校验工具:
ValidationUtils.validate(user); // 抛出 ValidationException 或返回 List<Error>
若希望无感生效,可结合 Spring AOP,在 Controller 方法参数前织入校验逻辑:
- 切点匹配
@annotation(org.springframework.web.bind.annotation.PostMapping)或自定义@Validated - 从
JoinPoint.getArgs()中提取 DTO 对象 - 委托给上述通用校验器执行
这样既保持低侵入,又避免重复调用,适合微服务中统一参数守门。

















