实现含带闭包注解字段Trait的类丢失对应字段注解
Groovy中Trait字段带闭包注解的行为解析与解决方案
先来看你给出的Groovy脚本和输出结果:
原脚本代码
#!/usr/bin/env groovy import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @Retention(RetentionPolicy.RUNTIME) @interface AnnotationWithClosure { Class closure() default { true } } trait TraitWithAnnotatedField { @AnnotationWithClosure(closure = {}) String foo @AnnotationWithClosure() String bar } class Main implements TraitWithAnnotatedField { def printFields() { this.class.declaredFields.each { println "${it} is annotated ${it.isAnnotationPresent(AnnotationWithClosure.class)}" } } } new Main().printFields()
控制台输出
private java.lang.String Main.TraitWithAnnotatedField__bar is annotated : true private java.lang.String Main.TraitWithAnnotatedField__foo is annotated : false
行为原因解析
这个现象是Groovy 2.4.x版本处理Trait字段注解的一个特定问题:
- 注解参数类型与传递差异:你的
AnnotationWithClosure注解中,closure()属性定义的是Class类型,但你给foo字段的注解直接传入了{}闭包实例(Groovy的语法糖,会自动把这个闭包编译成匿名类)。 - Trait合成字段的注解复制逻辑:当类实现Trait时,Groovy会生成带前缀的合成字段(比如
TraitWithAnnotatedField__bar)。对于使用默认值的注解(比如bar字段用的default { true }),Groovy能正确把注解信息复制到合成字段上;但对于直接传入闭包实例的注解(foo字段的closure = {}),Groovy在合成字段时没有正确保留注解的参数信息,导致isAnnotationPresent检测不到注解。
简单说:默认值的闭包Class是在注解定义时就确定的,能被正确传递;而动态传入的闭包实例对应的匿名类,在Trait字段合成到实现类时,注解信息丢失了。
正确获取并处理Trait字段上的带闭包注解
针对这个问题,有两种可行的解决思路:
思路1:规范注解属性类型(推荐)
Java注解的属性只能是基本类型、String、Class、枚举、注解或这些类型的数组,直接传Closure实例是Groovy的语法糖,但在Trait场景下不稳定。我们可以把注解属性改为Class<? extends Closure>类型,符合规范的同时避免问题:
#!/usr/bin/env groovy import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @Retention(RetentionPolicy.RUNTIME) @interface AnnotationWithClosure { // 改为限定Closure子类的Class类型 Class<? extends Closure> closure() default TrueClosure.class } // 定义默认的闭包实现类 class TrueClosure extends Closure<Boolean> { TrueClosure() { super(null) } Boolean doCall() { true } } trait TraitWithAnnotatedField { // 指定自定义闭包类作为注解参数 @AnnotationWithClosure(closure = EmptyClosure.class) String foo @AnnotationWithClosure() String bar } // 自定义空闭包实现类 class EmptyClosure extends Closure<Void> { EmptyClosure() { super(null) } Void doCall() { null } } class Main implements TraitWithAnnotatedField { def printFields() { // 直接从Trait本身获取字段注解,再关联到实现类的合成字段 def traitFields = TraitWithAnnotatedField.declaredFields this.class.declaredFields.each { implField -> // 匹配Trait对应的字段 def traitField = traitFields.find { implField.name.endsWith("__${it.name}") } if (traitField) { def hasAnnotation = traitField.isAnnotationPresent(AnnotationWithClosure.class) println "${implField} is annotated : ${hasAnnotation}" // 如果需要执行闭包,实例化对应的Class即可 if (hasAnnotation) { def annotation = traitField.getAnnotation(AnnotationWithClosure.class) Closure closure = annotation.closure().newInstance() println " 闭包执行结果: ${closure.call()}" } } } } } new Main().printFields()
思路2:直接从Trait类获取注解(兼容原有注解定义)
如果不想修改注解定义,可以跳过实现类的合成字段,直接从Trait本身的字段获取注解信息:
class Main implements TraitWithAnnotatedField { def printFields() { // 直接遍历Trait的字段,获取注解 TraitWithAnnotatedField.declaredFields.each { traitField -> def hasAnnotation = traitField.isAnnotationPresent(AnnotationWithClosure.class) // 关联到实现类的合成字段 def implField = this.class.declaredFields.find { it.name.endsWith("__${traitField.name}") } println "${implField ?: traitField} is annotated : ${hasAnnotation}" } } }
这个方法的核心是:Trait本身的字段注解是完整保留的,问题出在实现类的合成字段上,所以直接从Trait类读取注解即可。
内容的提问来源于stack exchange,提问作者Iaroslav Churinov
相关产品推荐
相关产品推荐

