美文网首页字节码增强
ByteBuddy(十四)—添加和删除Java注释

ByteBuddy(十四)—添加和删除Java注释

作者: Wpixel | 来源:发表于2023-02-17 17:04 被阅读0次

    本章介绍如何动态添加和删除在构造函数、Java方法和实例变量上注解的Java注解。

    本章被认为是复杂的。
    本概述有助于加深对本章解释的理解:

    • 使用net.bytebuddy.dynamic.DynamicType.Builder向Java类添加注释
    • 使用net.bytebuddy.asm.MemberAttributeExtension.ForMethod向Java方法和构造函数添加注释
    • 使用net.bytebuddy.asm.MemberAttributeExtension.ForField向实例变量添加注释
    • 使用net.bytebuddy.asm.jar.ClassVisitor从Java类中删除注释
    • 使用net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWritter从Java方法和构造函数中删除注释
    • 使用net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper从实例变量中删除注释

    结构

    DynamicType (public class)
    |-Builder (public static inner interface)
    
    MemberAttributeExtension (public class)
    |-ForMethod (public static inner class)
    |
    |-ForField (public static inner class)
    
    ClassVisitor (public class)
    
    AsmVisitorWrapper (public class)
    |-ForDeclaredMethods (public static inner class)
    |  |-MethodVisitorWrapper (public static inner interface)
    |
    |-ForDeclaredFields (public static inner class)
    |  |-FieldVisitorWrapper (public static inner interface)
    

    DataProducer.java是本章的功能代码:

    @Deprecated
    public class DataProducer {
        private int recordId = -3;
        @Deprecated
        public DataProducer(int p1, int p2, @Other int p3){
        }
        @Deprecated
        private String data;
        @Deprecated
        public void createData(){
        }
        public void create(){ 
        }
        public void createRecord(){
        }
        public void createFile(int param0, String param1,
                      @Other long param2, @Other double param3){
        }
    }
    

    DataProducer.java使用一个JDK注解:@Deprecated和两个自定义注解:@MyAnnotation@Other
    在编译之后,这是DataProducer.class

    /**
     * @deprecated
     */
    @MyAnnotation
    public class DataProducer implements Producer, Serializable{
        /**
         * @deprecated
         */
        @MyAnnotation
        private String data;
        
        /**
         * @deprecated
         */
        @MyAnnotation
        public DataProducer(@MyAnnotation int p1, int p2, int p3){
        }
        
        /**@deprecated*/
        public void createData(){}
        
        @Deprecated(since="", forRemoval=false)
        public void create(){}
        
        @RecordAnnotation({
            @com.wpixel.annotation.ColumnAnnotation(name="username",  value="user01"),
            @com.wpixel.annotation.ColumnAnnotation(name="age", value="25")})
        public void createRecord(){}
    
        @Deprecated(since="jdk5", forRemoval=false)
        @MyAnnotation
        public void createFile(@MyAnnotation int p0,   
                    @Deprecated(since="jdk5", forRemoval=false)
                    @MyAnnotation String param1, 
                    long param2,
                    @Other double param3){    
        }
    }
    

    除了注解,编译过程还向DataProducer.class添加了两个新的Java接口。
    本章中的所有代码生成都在InterceptorPlugin.JavaClassAnnotationRemove.java中实现

    将Java接口添加到Java类

    首先,本章解释了如何将Java接口添加到Java类中。
    在编译之前,DataProducer.java没有java接口。
    Plugin程序将向DataProducer.java添加java.io.Serializablecom.wpixel.Producer.java接口。
    为了向java类添加java接口,请使用net.bytebuddy.dynamic.DynamicType.Builder的实现方法:

    builder = builder.implement(Producer.class, Serializable.class);
    

    implement方法接受java.lang.Class的可变长度参数,其中类的类型是java接口。
    传递给implement方法参数的java接口的任何java.lang.Class都将导致ByteBuddy将java接口添加到目标类。
    因此,ByteBuddy为DataProducer.java生成此代码

    /** @deprecated */
    @Deprecated
    public class Data14Producer implements Producer, Serializable { }
    ####################
    /**
     * @deprecated
     */
    @MyAnnotation
    public class DataProducer implements Producer, Serializable{
    }
    

    向Java类添加注释

    在编译过程之后,DataProducer.classimplements子句一起添加。
    观察到DataProducer.class的类级注解也发生了变化。@Deprecated注解在注释块中被标记,新的注解@MyAnnotation被添加到DataProducer.class中,发生更改是因为Plugin程序调用以下代码行:

    builder = builder.annotateType(myAnnotation);
    ##################
    builder.annotateType(myAnnotation)
    .visit(newClassAnnotationRemoval(
    Deprecated.class));
    

    annotateType方法是向Java类添加注释的方法。
    其方法参数接受net.bytebuddy.description.annotation.AnnotationDescription的实例若要将注释添加到Java类,请将所需Java注释的AnnotationDescription实例传递到此参数。
    因为程序要添加@MyAnnotation,所以该方法与MyAnnotation变量一起传递。这是声明myAnnotation变量的代码:

    AnnotationDescription myAnnotation = AnnotationDescription
            .Builder.ofType(MyAnnotation.class).build();
    

    net.bytebuddy.description.annotation.AnnotationDescription是bytebuddy中注解编程中使用的基本Java类。
    AnnotationDescription用于创建表示Java注解的变量。
    在本例中,这个Java注解是@MyAnnotation
    因此,该声明将MyAnnotation.class传递给AnnotationDescription.Builder.Type方法,并调用build方法。
    之后,生成的myAnnotation变量可以在annotationType方法中使用。
    annotateType方法在类级别将@MyAnnotation(就是普通的注解类)添加到DataProducer.class

    @Deprecated
    @MyAnnotation
    public class Data14Producer implements Producer, Serializable {
    }
    

    从Java类中删除注释

    Plugin程序从DataProducer.class中删除@Deprecated注解。
    若要删除类级别的注解,请开发一个继承net.bytebuddy.asm.AsmVisitorWrapper.AbstractBase的派生类。
    在本例中,该派生类是ClassAnnotationRemove.java,用于删除@Depdecated注解:

    旧
    @deprecated
    @MyAnnotation
    public class DataProducer implements Producer, Serializable{
    }
    
    新
    /**
     * @deprecated
     */
    @MyAnnotation
    public class DataProducer implements Producer, Serializable{
    }
    

    派生类是实现超类包装方法所必需的。
    wrap方法有八个参数。

    public class ClassAnnotationRemove extends AsmVisitorWrapper.AbstractBase {
        private Class targetAnnotationClass;
        public ClassAnnotationRemove(Class c){
            targetAnnotationClass = c;
        }
        @Override
        public ClassVisitor wrap(TypeDescription typeDescription,
                                 ClassVisitor classVisitor,
                                 Implementation.Context context,
                                 TypePool typePool,
                                 FieldList<FieldDescription.InDefinedShape> fieldList,
                                 MethodList<?> methods,
                                 int writerFlags,
                                 int readerFlags) {
            return null;
        }
    }
    

    方法实现将使用这八个参数中的唯一一个参数:ClassVisitor,包装方法的第二个参数。
    ByteBuddy将在编译时为该参数提供ClassViitor的实例,该实例包含Plugin程序生成字节码所需的服务。
    但是,该实例无法删除注解。
    因此,ClassAnnotationRemove.java使用此对象实例化的另一个ClassVisitor实例,并实现visitAnnotation方法,以便它可以删除注解。
    这是wrap方法的实现:

    return new ClassVisitor(Opcodes.ASM7, classVisitor){
        public AnnotationVisitor visitAnnotation(String desc, boolean visible){
            if(Type.getDescriptor(targetAnnotationClass).equals(desc)){
                return null;
            }
            return super.visitAnnotation(desc, visible);
        }
    };
    

    该实现实例化了ClassVisitor的一个新实例,它是一个匿名类。
    构造函数接受两个参数:Opcodes.ASM7classVisitorwrap方法的第二个参数)。

    匿名类实现visitAnnotation方法。
    visitAnnotation方法用于访问和处理Java类的注解。
    visitAnnotation方法将在检测过程中隐式调用。
    当调用该方法时,它会逐个访问Java类的注解。
    如果Java类有一个注解,那么这个方法将被调用一次。
    如果Java类有两个注解,那么这个方法将被调用两次,依此类推。
    visitAnnotation方法的第一个参数descString类型,它包含注释描述符的值。

    例如,“Ljava/lang/Deprecated”@Deprecated注解描述符。
    因此,desc参数用于匹配要删除的目标注解:

    if(Type.getDescriptor(targetAnnotationClass).equals(desc)){
        return null;
    }
    

    targetAnnotationClass变量是一个java.lang.Class对象,该对象表示@Deprecated
    Type.getDescriptor方法将targetAnnotatonClass实例变量从java.lang.Class转换为注解描述符,以便将其用于与desc变量进行比较。
    因此,当注解匹配时,返回空值。空值将导致编译过程在注释块中标记@Deprecated注解,从DataProducer.class中删除@Deprecated注解。
    若要结束visitAnnotation方法,请取消超类的visitAnnotation方法:

    return super.visitAnnotation(desc, visible);
    

    targetAnnotationClassClassAnnotationRemove.java中声明的实例变量。
    它在构造函数中实例化:

    private Class targetAnnotationClass;
    public ClassAnnotationRemove(Class c){
        targetAnnotationClass = c;
    }
    

    Plugin程序在visit方法的参数中创建ClassAnnotationRemove.java的实例。
    Plugin程序将Deprecated.class实例传递给ClassAnnotationRemove.Class构造函数的第一个参数:

    builder = builder.annotateType(myAnnotation)
                    .visit(new ClassAnnotationRemove(Deprecated.class));
    

    因此,上面的代码应该将@MyAnnotation添加到DataProducer.class中,
    并从DataProducer.class中删除@Deprecated

    向Java方法添加注释

    接下来,Plugin程序将@Deprecated注解添加到DataProducer.classcreate方法中。
    这是添加@Deprecated注解的代码:

    builder = builder.visit(new MemberAttributeExtension.ForMethod()
                            .annotateMethod(defDeprecated)
                            .on(named("create")));
    

    代码使用visit方法执行检测。
    检测需要net.bytebuddy.asm.MemberAttributeExtension.ForMethod的实例。
    Plugin程序将使用ForMethod的这两种方法进行注解编程:

    • annotateMethod:在方法级别添加注解
    • annotateParameter: 在方法参数级别添加注解

    annotationMethod方法用于添加@Deprecated注解。
    annotateMethod方法使用defDeprecated变量作为其参数。
    这是声明defDeprecated变量的代码:

    AnnotationDescription defDeprecated = AnnotationDescription
                    .Builder.ofType(Deprecated.class).build();
    

    defDeprecatedAnnotationDescription的实例,表示@Deprecated批注。AnnotationDescription.BuilderType方法使用Deprecatedjava.lang.Class实例化生成器。然后,build方法完成实例化并将实例存储到defDeprecated变量中。

    builder = builder.visit(new MemberAttributeExtension.ForMethod()
                            .annotateMethod(defDeprecated)
                            .on(named("create")));
    

    Plugin程序只是完成了上面访问方法的annotateMethod方法。
    Plugin程序需要指定目标函数方法,将调用on方法以匹配用于添加注解的目标方法。
    在本例中,目标方法是create方法,因此,在执行此代码后,编译过程应向create方法添加@Deprecated注解:

    @Deprecated(
        since = "",
        forRemoval = false
    )
    public void create() {
    }
    

    观察到·@Deprecated:sinceforRemoval的属性包含在注解中,并且提供了默认值。
    即使Advice代码不包含该属性,编译过程也会隐式添加该属性,因为这是ByteBuddy的默认行为。

    向Java方法添加嵌套注解

    接下来,Plugin程序将注解容器添加到createRecord方法中。
    注解容器是包含嵌套注解的注解。例如

    @RecordAnnotation({
        @ColumnAnnotation(name=”username”, value=”user01”),
        @ColumnAnnotation(name=”age”, value=”25”)})
    public void createRecord(){}
    

    @RecordAnnotation是注解容器,因为它包含@ColumnAnnotation
    在Plugin程序将@RecordAnnotation添加到createRecord方法之前,Plugin程序必须声明表示@RecordAnnannotationAnnotationDescription实例及其嵌套注释@ColumnAnnAnnotation
    这是ColumnAnnotation.java的代码

    @Repeatable(RecordAnnotation.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.FIELD})
    public @interface ColumnAnnotation{
        String name();
        String value();
    }
    

    这是声明@ColumnAnnotationAnnotationDescription实例的代码:

    AnnotationDescription c1 = AnnotationDescription.Builder.ofType(
                    ColumnAnnotation.class)
                    .define("name", "username")
                    .define("value", "user01").build();
    

    当程序想要为注解定义属性值时,将使用define方法。
    这里,define方法定义name属性的值为username,然后,调用build方法创建AnnotationDescription的实例,并将其存储到c1变量中。

    同样,这是在name属性中声明@ColumnAnnotation定义age属性和value属性的代码,c2是存储 AnnotationDescription实例的变量:

    AnnotationDescription c2 = AnnotationDescription.Builder.ofType(
                    ColumnAnnotation.class)
                    .define("name", "age")
                    .define("value", "25").build();
    

    创建嵌套注释的AnnotationDescription实例后,为@RecordAnnotation创建AnnotationDescription实例

    AnnotationDescription nestedAnnotation = AnnotationDescription.Builder
                    .ofType(RecordAnnotation.class)
                    .defineAnnotationArray("value", 
                            new TypeDescription.ForLoadedType(ColumnAnnotation.class), 
                            c1, c2).build();
    

    这是RecordAnnotation.java的代码

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.FIELD})
    public @interface RecordAnnotation {
        ColumnAnnotation[] value();
    }
    

    RecordAnnotation.java有一个数据类型为ColumnAnnotation数组的值属性。
    要将多个@ColumnAnnotation添加到@RecordAnnotation,请使用defineAnnotationArray方法。

    defineAnnotationArray方法需要三个参数:
    1、String
    2、TypeDescription
    3、AnnotationDescription

    • 第一个参数用于指定包含嵌套注解数组的@RecordAnnotation的属性名。
      @RecordAnnotation使用"value"属性来包含嵌套的注解,因此第一个参数的值是"value"。
    • 第二个参数用于指定RecordAnnotation.javavalue属性的数据类型。
      因此,程序通过第二个参数中的TypeDescripton.ForLoadedType(ColumnAnnotation.class)实例化TypeDescription的实例。
    • 第三个参数是可变长度参数,用于指定RecordAnnotation.javacontent-of-value属性所以将c1c2变量传递给这个参数。
    • 之后,调用build方法结束方法链。
      build方法创建表示RecordAnnotationAnnotationDescription实例,该实例存储在nestedAnnotation变量中。

    之后,nestedAnnotation变量可以用于在createRecord方法上添加@RecordAnnotation
    因此插件程序调用访问方法:

    builder = builder.visit(new MemberAttributeExtension.ForMethod()
                    .annotateMethod(nestedAnnotation)
                    .on(named("createRecord")));
    

    创建ForMethod的实例,并将nestedAnnotation变量传递给其annotationMethod方法。
    调用on方法以匹配DataProducer.class中的createRecord方法。
    因此,此代码应将@RecordAnnotation添加到createRecord方法中:

        @RecordAnnotation({
            @ColumnAnnotation(name = "username", value = "user01"), 
            @ColumnAnnotation(name = "age", value = "25")})
        public void createRecord() {
        }
    

    从Java方法中删除注解

    接下来,Plugin程序从createData方法中删除@Deprecated注解。
    为此,Plugin程序需要实现net.bytebuddy.asm.AsmVisitor Wrapper.ForDeclaredMethods.MethodVisitorWrapper的自定义派生类Plugin程序将以匿名类格式创建此派生类,它是在Plugin程序的methodDeprecatedRemover方法中创建的:

        private AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper methodDeprecatedRemover(){
            return new AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper(){
                @Override
                public net.bytebuddy.jar.asm.MethodVisitor wrap(
                        TypeDescription typeDescription,
                        MethodDescription methodDescription,
                        net.bytebuddy.jar.asm.MethodVisitor methodVisitor,
                        Implementation.Context context,
                        TypePool typePool,
                        int writeFlag,
                        int readFlag) {
                    return new MethodVisitor(Opcodes.ASM7, methodVisitor){
                        @Override
                        public AnnotationVisitor visitAnnotation(String desc, boolean visible){
                            if(Type.getDescriptor(Deprecated.class).equals(desc)){
                                return null;
                            }
                            return super.visitAnnotation(desc, visible);
                        }
                    };
                }
            };
        }
    

    MethodVisitorWrapper的派生类需要实现包装方法。
    在这个实现中,wrap方法返回net.bytebuddy.jar.asm.MethodVisitor的实例。
    wrap方法的实现以匿名类格式创建MethodVisitor实例,而这个匿名类实现visitAnnotation方法。
    ClassVisitor类似,当过程遇到方法中的注解时,检测过程将调用visitAnnotation方法。
    当前检查注解的注解描述符可通过desc参数获得。
    因此,程序使用desc参数来匹配程序要删除的目标注解:

    if(Type.getDescriptor(Deprecated.class).equals(desc)){
        return null;
    }
    

    如果找到Deprecated.class的注解描述符,则返回null。
    空值导致检测过程在注解块中标记@Deprecated
    要结束visitAnnotation方法,请在方法末尾调用super.visitAnnuation方法。

    现在,methodDeprecatedRemover方法的实现已经完成。
    Plugin程序将在访问方法中使用此方法:

    builder = builder.visit(
                    new AsmVisitorWrapper.ForDeclaredMethods()
                            .method(named("createData"), 
                                    methodDeprecatedRemover()));
    

    程序实例化ForDeclaredMethods的实例。然后,调用ForDeclaredMethodsmethod方法。
    method方法需要两个参数。

    • 第一个参数需要ElementMatchers。在本例中,matcher尝试匹配createData方法,因此使用ElementMatchers.named方法。
    • 第二个参数采用MethodVisitorWrapper的实例,此参数中调用methodDeprecatedRemover方法。
      在这之后,ForDeclaredMethods的实例具有必要的匹配器和方法访问者包装器。
      编译过后应该从createData方法中删除@Deprecated注解。
      这是插入过程之后的createData方法:
       /** @deprecated */
        public void createData() {
        }
    

    向方法参数添加注解

    接下来,Plugin程序向DataProducer.classcreateFile方法添加一些注解。
    这是Plugin过程之前的原始createFile方法:

    public void createFile(int param0, String param1,
                               @Other long param2, @Other double param3){
    }
    

    在编译过后,这是插入指令的createFile方法

    @Deprecated(since = "jdk5", forRemoval = false)
    @MyAnnotation
    public void createFile(@MyAnnotation int param0, 
                  @Deprecated(since = "jdk5",forRemoval = false) @MyAnnotation String param1, 
                  long param2, 
                  @Other double param3) {
    }
    

    这个例子很复杂:

    • 编译过程将@Deprecated@MyAnnotation添加到createFile方法中。
      @Deprecated注解的since属性值为jdk5
    • 第一个参数添加了一个@MyAnnotation
    • 第二个参数添加了@MyAnnotation@Deprecated注解。
    • 第三个参数中删除@Other注解。
    • 第四个参数没有变化。

    这是用于此目的的代码:

    builder.visit(new MemberAttributeExtension.ForMethod()
                            .annotateMethod(jdk5Deprecated)
                            .annotateMethod(myAnnotation)
                            .annotateParameter(0, myAnnotation)
                            .annotateParameter(1, jdk5Deprecated)
                            .annotateParameter(1, myAnnotation).on(named("createFile")))
                    .visit(new AsmVisitorWrapper.ForDeclaredMethods()
                            .method(named("createFile"), parameterOtherRemover()));
    

    本例将解释这些新概念:

    • 如何向方法参数添加注释
    • 如何从方法参数中删除注释
      观察到代码有两种访问方法。第一次访问方法将相关注释添加到方法和参数中。第二个访问方法从方法参数中删除注释。

    在首次访问方法中,MemberAttributeExtension用于添加注解。
    第2行和第3行调用annotateMethod方法来添加注解。
    有一个jdk5Depresected变量,它是AnnotationDescription的一个实例:

    AnnotationDescription jdk5Deprecated = AnnotationDescription.Builder
                    .ofType(Deprecated.class)
                    .define("since", "jdk5")
                    .build();
    

    jdk5Deprecated变量表示@Deprecated注解,其since属性的值为“jdk5”。
    因此,jdk5Dprecated变量已创建,可以在第2行中使用。

    代码继续,第4、5和6行调用annotateParameter方法将注解添加到参数。
    annotateParameter方法有两个参数:intAnnotationDescription
    int参数用于指定参数的索引编号。

    .annotateParameter(0, myAnnotation)
    

    这段代码将@MyAnnotation添加到createFile方法的第一个参数中。

    .annotateParameter(1, jdk5Deprecated)
    .annotateParameter(1, myAnnotation)
    

    这两行代码将@Deprecated(since="jdk5",for Remove=false)@MyAnnotation添加到createFile方法的第二个参数。

    从方法参数中删除注释

    之后,Plugin程序从第三个参数中删除@Other参数:

    builder = builder.visit(
                    new AsmVisitorWrapper.ForDeclaredMethods()
                            .method(named("createFile"), parameterOtherRemover()));
    

    访问方法用于执行检测过程。
    编译需要ForDeclaredMethods的实例ForDeclaredmethod的方法方法需要一个ElementMatcher和一个methodVisitorWrapper的实例

    参数OtherRemover方法是可以返回方法VisitorWrapper实例的方法。
    这是parameterOtherRemover方法的实现:

        private AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper parameterOtherRemover(){
            return new AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper(){
                @Override
                public MethodVisitor wrap(TypeDescription td,
                                          MethodDescription desc,
                                          MethodVisitor methodVisitor,
                                          Implementation.Context ctx,
                                          TypePool pool,
                                          int writeFlag,
                                          int readFlag){
                    return new MethodVisitor(Opcodes.ASM7, methodVisitor){
                        @Override
                        public AnnotationVisitor visitParameterAnnotation(
                                int param,
                                java.lang.String desc,
                                boolean visible){
                            if(param == 2 && Type.getDescriptor(Other.class).equals(desc)){
                                return null;
                            }
                            return super.visitParameterAnnotation(param,desc,visible);
                        }
                    };
                }
            };
        }
    

    实现wrap方法。
    wrap方法以内部匿名类格式返回MethodVisitor的一个实例,并实现visitParameterAnnotation方法。

    VisitParameterAnnotation方法有三个参数:int、String和boolean。
    int参数用于指定参数的索引编号。
    String参数是当前检查注释的注释描述符。
    此示例中未使用布尔参数。因此,希望从第三个参数中删除@Other,这是用于此目的的逻辑:

    If(param == 2 && Type.getDescriptor(Other.class).equals(desc)){
        return null;
    }
    

    要匹配第三个参数的索引号,请使用param ==2。然后Type.getDescriptor方法将Other.class转换为注解描述符,并将其与desc变量进行比较。
    当检测到@Other注解时,返回null值,null值将导致检测过程删除注解。

    要结束visitParameterAnnotation方法,return语句调用超类的visitParameterAnnotation方法。

    现在可以使用parameterOtherRemover方法了。它用于第二次访问方法,并在方法方法的第二个参数中调用:

                    .visit(new AsmVisitorWrapper.ForDeclaredMethods()
                            .method(named("createFile"), parameterOtherRemover()));
    

    使用此代码,@Other注释参数将从createFile方法的第三个参数中删除。

    向实例变量添加注解

    在这个阶段,本章已经解释了方法和Java类的注解处理。
    接下来,将解释如何添加和删除在实例变量上的注解。

    Plugin程序将向DataProducer.class的数据实例变量添加@MyAnnotation
    这是插入过程之前的数据实例:

    @Deprecated
    private String data;
    

    在编译过程之后,@Deprecated被删除,@MyAnnotation被添加:

    /**
    *@Deprecated
    */
    @MyAnnotation
    private String data;
    

    这是添加和删除在数据实例变量上注解的代码:

    builder = builder.visit(new MemberAttributeExtension.ForField()
                    .annotate(myAnnotation)
                    .on(named("data")))
                    .visit(new AsmVisitorWrapper.ForDeclaredFields()
                            .field(named("data"), fieldDeprecatedRemover()));
    

    MemberAttributeExtension.ForField用于将注解添加到实例变量。从第1行到第4行的代码是用于此目的的代码段。

    调用annotation方法将@MyAnnotation添加到数据实例变量,并使用on方法配置ElementMatchers.named

    从实例变量中删除注解

    然后,第二个访问方法从数据实例变量中删除@Deprecated注解。为此,程序需要ForDeclaredFieldsFieldVisitorWrapper的实例。

    这是返回FieldVisitorWrapper实例的fieldDeprecatedRemover方法的实现

        private AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper fieldDeprecatedRemover(){
            return new AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper(){
                @Override
                public FieldVisitor wrap(
                        TypeDescription td,
                        FieldDescription.InDefinedShape sh,
                        FieldVisitor fieldVisitor){
                    return new FieldVisitor(Opcodes.ASM7, fieldVisitor){
                        @Override
                        public AnnotationVisitor visitAnnotation(String desc, boolean visible){
                            if(Type.getDescriptor(Deprecated.class).equals(desc)){
                                return null;
                            }
                            return super.visitAnnotation(desc, visible);
                        }
                    };
                }};
        }
    

    MethodVisitor非常类似,FieldVisitorvisitAnnotation方法被实现为匹配@Deprecated注解,并返回null值以signiy到检测过程以删除@Deprecated注解。在实现fieldDeprecatedRemover方法之后,在代码中调用fieldDeprecatedRemover方法。此后,会删除数据实例变量的@Deprecated注解。

    在构造函数上添加注解

    毕竟,本章将展示最后一个示例,该示例添加和删除在构造函数上注释的注解。这里将仅使用一个构造函数来说明注释插入的工作原理。这是检测过程之前的构造函数:

    @Deprecated
    public DataProducer(int p1, int p2, @Other int p3){
    }
    

    这是检测过程之后的构造函数:

    /**
     *@Deprecated
     */
    @MyAnnotation
    public DataProducer(@MyAnnotation int p1, int p2, int p3){
    }
    

    @Deprecated注解将从构造函数中删除。第一个参数添加了一个@MyAnnotation注解,而@Other注解从第三个参数中删除。
    大多数添加和删除注解的代码与Java普通方法的代码类似。这是提供注解插入的代码:

    builder = builder.visit(new MemberAttributeExtension.ForMethod()
                            .annotateMethod(myAnnotation)
                            .annotateParameter(0,myAnnotation)
                            .on(isConstructor()))
                    .visit(new AsmVisitorWrapper.ForDeclaredMethods()
                                    .constructor(isConstructor(),
                                            methodDeprecatedRemover()))
                    .visit(new AsmVisitorWrapper.ForDeclaredMethods()
                                    .constructor(isConstructor(),
                                            parameterOtherRemover()));
    

    有三种访问方法来实现仪器。第一次访问方法将@MyAnnotation添加到构造函数及其第一个参数。第一次访问方法使用MemberAttributeExtension及其内部类ForMethod添加注解。
    尽管是构造函数,但ForMethod也可以应用于构造函数。
    因此,此行将@MyAnnotation添加到构造函数中:

    .annotateMethod(myAnnotation)
    

    此行将@MyAnnotation添加到构造函数的第一个参数:

    .annotateParameter(0, myAnnotation)
    

    ElementMatchers中使用了不同的匹配条件。ElementMatchers使用isConstructor方法,这将导致Advice代码应用于DataProducer.class中的所有构造函数。

    从构造函数中删除注释

    第二个访问方法从构造函数中删除@Deprecated注解。第二个访问方法使用AsmVisitorWrapper及其内部类ForDeclaredMethod然后,通过将ElementMatchers.isConstructor传递给其第一个参数,并将MethodVisitorWrapper的实例传递给其第二个参数来调用构造函数方法。methodDeprecatedRemover方法是创建MethodVisitorWrapper实例的方法方法DeprecatedRemover可用于构造函数。
    第三个访问方法从第三个参数中删除@Other注解。
    第三个访问方法使用AsmVisitorWrapper及其内部类ForDeclaredMethodparameterOtherRemover也可用于构造函数参数。

    结论

    本章说明:
    *如何向Java类添加注解
    *如何在Java类中添加implement子句
    *如何使用MemberAttributeExtension向方法、实例变量和构造函数添加注解
    *如何使用AsmVisitorWrapper、自定义ClassVisitorMethodVisitorFieldVisitor删除注解


    bytebuddy书籍《Java Interceptor Development with ByteBuddy: Fundamental》

    ----END----

    喜欢就点个👍吧

    相关文章

      网友评论

        本文标题:ByteBuddy(十四)—添加和删除Java注释

        本文链接:https://www.haomeiwen.com/subject/lfogxdtx.html