泛型本身不触发注解校验,真正实现编译期校验的是注解处理器对泛型结构的解析与代码生成;需用TYPE_USE目标、递归解析泛型实参、生成桩代码而非依赖ConstraintValidator。

泛型本身不触发注解校验,但能提升类型安全和复用性;真正实现编译期校验的,是注解处理器(Processor)对泛型结构的解析与代码生成能力。关键不在泛型声明本身,而在处理器能否准确识别泛型实参并据此生成校验逻辑。
注解必须标注在 TYPE_USE 位置
如果想校验泛型内部元素(比如 List<@NotBlank String> 中的 String),注解不能只加在字段上(如 @NotBlank List<String>),而要作用于类型使用处:
- 声明时用
@Target(ElementType.TYPE_USE),确保支持泛型参数、数组元素、方法返回值等上下文 - 例如:
Map<@NotNull String, @Size(max = 50) User> users;,此时注解绑定的是类型参数,而非整个 Map - 运行时反射无法获取这类信息,必须依赖 Processor 在编译期通过
TypeMirror和Types.asElement()解析
Processor 需解析泛型实参,而非 raw type
处理器不能只看字段声明类型(如 List 或 Response),必须递归提取泛型参数的具体类型:
- 调用
element.asType()获取字段类型镜像,再用Types.directSupertypes()或DeclaredType.getTypeArguments()拆解泛型参数 - 对每个类型参数(如
User),检查其是否含约束注解;若为嵌套泛型(如Response<List<@Email String>>),需多层展开 - 避免使用匿名泛型(如
new ArrayList<>() { ... })或局部泛型变量,它们会导致TypeVariable无法解析为具体类型
校验逻辑需分层设计,不依赖 ConstraintValidator 泛型擦除后类型
ConstraintValidator<A, T> 中的 T 是擦除后的类型(如 Object 或 String),无法直接反映泛型容器内容。因此编译期校验应绕过运行时校验器,转为生成桩代码:
立即学习“Java免费学习笔记(深入)”;
- 对
@Valid Result<User>字段,Processor 提取User类型,生成类似ResultValidator_user.check(result)的调用 - 对集合元素校验(如
List<@Email String>),外层生成长度检查,内层为每个字符串生成独立的EmailValidator.validate()调用 - Spring Validation 默认不递归校验泛型集合元素,所以必须显式加
@Valid,且确保泛型实参可被 Processor 反射获取(即非动态生成类、非 Lambda 表达式内部类)
避免泛型擦除导致的校验失效
泛型信息在字节码中被擦除,所以所有校验必须在编译期完成,不能寄希望于运行时补救:
- 不要在
ConstraintValidator实现里试图通过反射读取泛型字段的实际类型——它拿到的永远是Object - 若需支持通配符(如
List<? extends Person>),应在 Processor 中结合Types.isSubtype()做上界推断,而非硬编码类型匹配 - 测试时用
javac -processorpath手动触发 Processor,并检查生成的校验代码是否包含对泛型实参的正确引用


















