如何用Java ASM在构建时检测字段的直接/间接@NotNull注解?
如何用ASM区分字段上的@NotNull是直接标注还是通过元注解间接存在
我在SampleClass类中有一个标注了@MyAnnotation的字段,目前可以通过ASM访问者模式检测到字段name上的@MyAnnotation,但不知道如何判断@NotNull是直接标注在字段上,还是作为@MyAnnotation的元注解间接存在。
相关代码示例
SampleClass.java
import jakarta.validation.constraints.NotNull; import javax.persistence.*; @Entity public class SampleClass { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @MyAnnotation("John") public String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
MyAnnotation.java
import jakarta.validation.constraints.NotNull; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @NotNull @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD}) public @interface MyAnnotation { String value() default ""; }
问题根源
当前你的ASM代码只检查了字段直接标注的注解,而@NotNull是标注在@MyAnnotation上的元注解,ASM不会自动递归解析元注解,所以无法直接检测到。要实现需求,需要手动读取元注解所在的注解类的字节码,检查其是否包含目标注解。
实现方案
核心思路
- 定义两个变量分别记录两种
@NotNull的存在情况:directNotNull:字段直接标注了@NotNullindirectNotNull:字段标注的注解本身带有@NotNull元注解
- 解析字段注解时,针对不同注解做分支处理:
- 若为
@NotNull,直接标记directNotNull - 若为其他注解(如
@MyAnnotation),读取该注解类的字节码,检查其是否标注了@NotNull
- 若为
修改后的Gradle ASM代码
替换原tasks.compileJava中的doLast逻辑:
tasks.compileJava { doLast { val notNullDescriptor = Type.getType("Ljakarta/validation/constraints/NotNull;").descriptor val myAnnotationDescriptor = Type.getType("Lorg/example/MyAnnotation;").descriptor // 读取SampleClass字节码 val sampleClassBytes = file("build/classes/java/main/org/example/SampleClass.class").readBytes() val classReader = ClassReader(sampleClassBytes) val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) classReader.accept(object : ClassVisitor(ASM9, classWriter) { override fun visitField( access: Int, name: String, descriptor: String, signature: String?, value: Any? ): FieldVisitor { val fieldVisitor = super.visitField(access, name, descriptor, signature, value) return object : FieldVisitor(Opcodes.ASM9, fieldVisitor) { var directNotNull = false var indirectNotNull = false override fun visitAnnotation(annoDescriptor: String, visible: Boolean): AnnotationVisitor { val av = super.visitAnnotation(annoDescriptor, visible) // 检查是否为直接标注的@NotNull if (annoDescriptor == notNullDescriptor) { directNotNull = true } // 检查是否为@MyAnnotation,解析其元注解 else if (annoDescriptor == myAnnotationDescriptor) { val myAnnotationClassPath = "build/classes/java/main/org/example/MyAnnotation.class" val myAnnotationBytes = file(myAnnotationClassPath).readBytes() val myAnnoClassReader = ClassReader(myAnnotationBytes) // 仅解析注解部分,跳过无关内容提升效率 myAnnoClassReader.accept(object : ClassVisitor(ASM9) { override fun visitAnnotation(annoDesc: String, visible: Boolean): AnnotationVisitor? { if (annoDesc == notNullDescriptor) { indirectNotNull = true } return super.visitAnnotation(annoDesc, visible) } }, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) } return av } override fun visitEnd() { println("字段 $name:") println(" 直接标注@NotNull: $directNotNull") println(" 通过元注解间接存在@NotNull: $indirectNotNull") println(" 最终是否存在@NotNull: ${directNotNull || indirectNotNull}") super.visitEnd() } } } }, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) // 若不需要修改类文件,可省略写回操作 file("build/classes/java/main/org/example/SampleClass.class").writeBytes(classWriter.toByteArray()) } }
扩展说明
- 如果需要支持多层嵌套元注解(比如
@MyAnnotation的元注解还带有其他元注解),可以将解析元注解的逻辑封装成递归方法,逐层检查每个注解的元注解。 - 确保类路径正确,避免因找不到注解类字节码导致解析失败。
- 使用
ClassReader.SKIP_CODE等参数跳过字节码中无关的部分,可大幅提升解析效率。
内容的提问来源于stack exchange,提问作者bobbyrne01
相关产品推荐
相关产品推荐

