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

本章介绍如何动态添加和删除在构造函数、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----

喜欢就点个👍吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容