You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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不会自动递归解析元注解,所以无法直接检测到。要实现需求,需要手动读取元注解所在的注解类的字节码,检查其是否包含目标注解。

实现方案

核心思路

  1. 定义两个变量分别记录两种@NotNull的存在情况:
    • directNotNull:字段直接标注了@NotNull
    • indirectNotNull:字段标注的注解本身带有@NotNull元注解
  2. 解析字段注解时,针对不同注解做分支处理:
    • 若为@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 12:55:10