Using Java ASM library to manipulate bytecode at build time, how can I check if an annotation on a field contains other specific annotations?

huangapple go评论69阅读模式
英文:

Using Java ASM library to manipulate bytecode at build time, how can I check if an annotation on a field contains other specific annotations?

问题

SampleClass 类中有一个字段,该字段被注释为 MyAnnotation

我可以使用 ASM 访问者模式检测到 MyAnnotation 是否存在于字段 name 上,但不确定如何检查 @NotNull 是否包含在 MyAnnotation 中。

如何检测 @NotNull 是否直接存在于字段上或通过另一个注释存在于字段上?

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 "";
}

Gradle 构建脚本:

import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.ASM9
import java.util.*


plugins {
    java
}

group "org.example"
version "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.ow2.asm:asm:9.5")
    implementation("jakarta.persistence:jakarta.persistence-api:2.2.3")
    implementation("jakarta.validation:jakarta.validation-api:3.0.0")
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.ow2.asm:asm:9.5")
        classpath("jakarta.persistence:jakarta.persistence-api:2.2.3")
        classpath("jakarta.validation:jakarta.validation-api:3.0.0")
    }
}

tasks.compileJava {
    doLast {

        val notNullAnnotation = Type.getType("Ljakarta/validation/constraints/NotNull")

        val classReader = ClassReader(file("build/classes/java/main/org/example/SampleClass.class").readBytes())
        val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)

        classReader.accept(object : ClassVisitor(ASM9, classWriter) {
            var isEntity = false

            override fun visit(
                version: Int,
                access: Int,
                name: String,
                signature: String?,
                superName: String?,
                interfaces: Array<out String>?
            ) {
                super.visit(version, access, name, signature, superName, interfaces)
            }

            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 hasNotNullAnnotation = false

                    override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {

                        val av = super.visitAnnotation(descriptor, visible)

                        if (descriptor.contains(notNullAnnotation.descriptor)) {
                            hasNotNullAnnotation = true
                        }
                    }

                    override fun visitEnd() {

                        System.out.println("hasNotNullAnnotation: " + hasNotNullAnnotation); // always false

                        super.visitEnd()
                    }
                }
            }
        }, 0)

        file("build/classes/java/main/org/example/SampleClass.class").writeBytes(classWriter.toByteArray())
    }
}

val asmConfiguration = project.configurations.getByName("implementation")
tasks.compileJava {

    doLast {
        asmConfiguration
    }
}
英文:

SampleClass has a field, that is annotated with MyAnnotation.

I can detect that MyAnnotation is present on the field name using ASM visitor pattern, but unsure how to check if @NotNull is part of MyAnnotation.

How can I detect if @NotNull is present either directly on the field or via another annotation?

import jakarta.validation.constraints.NotNull;
import javax.persistence.*;
@Entity
public class SampleClass {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@MyAnnotation(&quot;John&quot;)
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 &quot;&quot;;
}

Gradle build script:

import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.ASM9
import java.util.*
plugins {
java
}
group &quot;org.example&quot;
version &quot;1.0-SNAPSHOT&quot;
repositories {
mavenCentral()
}
dependencies {
implementation(&quot;org.ow2.asm:asm:9.5&quot;)
implementation(&quot;jakarta.persistence:jakarta.persistence-api:2.2.3&quot;)
implementation(&quot;jakarta.validation:jakarta.validation-api:3.0.0&quot;)
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath(&quot;org.ow2.asm:asm:9.5&quot;)
classpath(&quot;jakarta.persistence:jakarta.persistence-api:2.2.3&quot;)
classpath(&quot;jakarta.validation:jakarta.validation-api:3.0.0&quot;)
}
}
tasks.compileJava {
doLast {
val notNullAnnotation = Type.getType(&quot;Ljakarta/validation/constraints/NotNull&quot;)
val classReader = ClassReader(file(&quot;build/classes/java/main/org/example/SampleClass.class&quot;).readBytes())
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
classReader.accept(object : ClassVisitor(ASM9, classWriter) {
var isEntity = false
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array&lt;out String&gt;?
) {
super.visit(version, access, name, signature, superName, interfaces)
}
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 hasNotNullAnnotation = false
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {
val av = super.visitAnnotation(descriptor, visible)
if (descriptor.contains(notNullAnnotation.descriptor)) {
hasNotNullAnnotation = true
}
}
override fun visitEnd() {
System.out.println(&quot;hasNotNullAnnotation: &quot; + hasNotNullAnnotation); // always false
super.visitEnd()
}
}
}
}, 0)
file(&quot;build/classes/java/main/org/example/SampleClass.class&quot;).writeBytes(classWriter.toByteArray())
}
}
val asmConfiguration = project.configurations.getByName(&quot;implementation&quot;)
tasks.compileJava {
doLast {
asmConfiguration
}
}

答案1

得分: 1

你的示例基本上不起作用,因为你只检查@NotNull注解是否直接存在于字段上,而没有处理真正的情况,即@NotNull是一个元注解的情况。你可以尝试使用显式的自定义AnnotationVisitor,如下所示:

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 hasNotNullAnnotation = false

        override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {
            val av = super.visitAnnotation(descriptor, visible)

            return object : AnnotationVisitor(ASM9, av) {
                override fun visitAnnotation(name: String, descriptor: String): AnnotationVisitor {
                    if (descriptor.contains(notNullAnnotation.descriptor)) {
                        hasNotNullAnnotation = true
                    }
                    return this
                }
            }
        }

        override fun visitEnd() {
            System.out.println("hasNotNullAnnotation: " + hasNotNullAnnotation); 
            super.visitEnd()
        }
    }
}
英文:

Your example basically doesn't work because you're only checking if the @NotNull annotation is directly present on the field, but not handling the real case, where @NotNull is a meta-annotation. You might try to use the explicit custom AnnotationVisitor instead:

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 hasNotNullAnnotation = false

        override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {
            val av = super.visitAnnotation(descriptor, visible)

            return object : AnnotationVisitor(ASM9, av) {
                override fun visitAnnotation(name: String, descriptor: String): AnnotationVisitor {
                    if (descriptor.contains(notNullAnnotation.descriptor)) {
                        hasNotNullAnnotation = true
                    }
                    return this
                }
            }
        }

        override fun visitEnd() {
            System.out.println(&quot;hasNotNullAnnotation: &quot; + hasNotNullAnnotation); 
            super.visitEnd()
        }
    }
}

huangapple
  • 本文由 发表于 2023年7月13日 21:05:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76679708.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定