byte-buddy字节码增强工具使用示例

3,171 阅读9分钟

byte-buddy官网:bytebuddy.net

中文翻译文档[部分文字内容不全]:notes.diguage.com/byte-buddy-…

官方英文文档:bytebuddy.net/#/tutorial

简介:

Byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类。

0. Instrumentation简介

内容摘抄自:www.ibm.com/developerwo…

Instrumentation 是 Java SE 5 的新特性,使用 Instrumentation可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。

在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能,这使以前无法完成的功能 —— 对 native 接口的 instrumentation 可以在 Java SE 6 中,通过一个或者一系列的 prefix 添加而得以完成。Java SE 6 里的 Instrumentation 也增加了动态添加 class path 的功能。所有这些新的功能,都使得 instrument 包的功能更加丰富,从而使 Java 语言本身更加强大。

java.lang.instrument包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入,整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI),而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。事实上,java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。除开 Instrumentation 功能外,JVMTI 还在虚拟机内存管理,线程控制,方法和变量操作等等方面提供了大量有价值的函数。关于 JVMTI 的详细信息,请参考 Java SE 6 文档(docs.oracle.com/javase/6/do…)当中的介绍。

1. JavaAgent中使用

pom.xml引入byte-buddy依赖和Agent打包插件:

// pom.xml
...
    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.19</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <!-- 修改为正确的Premain地址 -->
                                        <Premain-Class>xxx.AgentPremain</Premain-Class>
                                    </manifestEntries>
                                </transformer>
                            </transformers>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

        </plugins>
    </build>
...

共用代码:

public interface Operation<K,V> {
    void put(K key,V value);
    V get(K key);
    void dump();
}
public class DefaultOperation implements Operation<String, String> {

    private Map<String, String> map = new HashMap<>();

    public DefaultOperation() {
        //
    }

    @Override
    public void put(String key, String value) {
        map.put(key, value);
    }

    @Override
    public String get(String key) {
        return map.get(key);
    }

    @Override
    public void dump() {
        if (map.isEmpty()) {
            System.out.println("{}");
        }
        StringBuilder sb = new StringBuilder("{");
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
            sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
        }
        if (sb.lastIndexOf(", ") == sb.length() - 2) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append("}");
        System.out.println(sb.toString());
    }
}

Agent模块通用代码:

// Agent增强结果回调
public class TransformListener implements AgentBuilder.Listener {

    @Override
    public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
    }

    @Override
    public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
    }

    @Override
    public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
    }

    @Override
    public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
        System.out.printf("Agent throw error before running, typeName=%s, message=%s%n", typeName, throwable.getMessage());
        throwable.printStackTrace();
    }

    @Override
    public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
    }
}

1.1 添加成员变量并在构造函数中赋值

// 定义构造函数通知
public class ConstructorAdvice {
    @Advice.OnMethodExit
    public static void onMethodExit(@Advice.FieldValue(value = "counter", readOnly = false) AtomicInteger counter) {
        counter = new AtomicInteger();
    }
}
public class AgentPremain {

    public static void premain(String arguments, Instrumentation instrumentation) {
    
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                // 限定作用范围,为接口Operation的实现类
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        // 定义类成员变量,命名counter,类型AtomicInteger,作用域public
                        .defineField("counter", AtomicInteger.class, Visibility.PUBLIC)
                        // 确定切点为所有构造函数,紧跟这使用.intercept(...)进行通知织入
                        .constructor(ElementMatchers.any())                
                        // 通知织入上面的切点
                        .intercept(Advice.to(ConstructorAdvice.class)))
                .with(new TransformListener())
                .installOn(instrumentation);
    }
}

maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar

运行测试类:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Operation<String, String> operation = new DefaultOperation();
        Field field = operation.getClass().getDeclaredField("counter");
        Object object = field.get(operation);
        int counterValue = ((AtomicInteger) object).incrementAndGet();
        System.out.println(counterValue);
    }

输出结果:

Agent premain!
1

使用Arthas的jad命令反编译出DefaultOperation类:

public class DefaultOperation implements Operation<String, String> {

    private Map<String, String> map;
    public AtomicInteger counter;

    @Override
    public void dump() {
        if (this.map.isEmpty()) {
            System.out.println("{}");
        }
        StringBuilder sb = new StringBuilder("{");
        for (Map.Entry<String, String> entry : this.map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
            sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
        }
        if (sb.lastIndexOf(", ") == sb.length() - 2) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append("}");
        System.out.println(sb.toString());
    }

    public DefaultOperation() {
        DefaultOperation defaultOperation = this;
        defaultOperation(null);
        this.counter = new AtomicInteger();
    }

    private /* synthetic */ DefaultOperation(auxiliary.vawGY1XQ vawGY1XQ2) {
        this.map = new HashMap<String, String>();
    }

    static {
        ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, -923819650);
    }

    @Override
    public String get(String key) {
        return this.map.get(key);
    }

    @Override
    public void put(String key, String value) {
        this.map.put(key, value);
    }
}

1.2 添加方法

// 新方法定义
public class AddMethod {
    
    // @Argument表示方法入参引用, @FieldValue表示类成员变量引用
    
    @RuntimeType
    public static void method(@Argument(0) String key,
                              @Argument(1) String value,
                              @FieldValue("map") Map map) {
        map.put(key, value);
    }

}
public class AgentPremain {


    public static void premain(String arguments, Instrumentation instrumentation) {
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        // 定义新方法,方法名为put2,返回类型为void,作用于为public
                        .defineMethod("put2", void.class, Visibility.PUBLIC)
                        // 定义新方法的入参数量和类型
                        .withParameters(Arrays.asList(String.class, String.class))
                        // 将新方法的实现进行代理
                        .intercept(MethodDelegation.to(AddMethod.class))
                )
                .with(new TransformListener())
                .installOn(instrumentation);
    }

}

maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar

运行测试类:

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Operation<String, String> operation = new DefaultOperation();
        Method put2 = operation.getClass().getDeclaredMethod("put2", String.class, String.class);
        put2.invoke(operation, "A", "a");
        put2.invoke(operation, "B", "b");
        operation.dump();
    }

输出结果:

Agent premain!
{"A":"a", "B":"b"}

1.3 拦截方法(使用Advice)

// 构造函数Advice
public class ConstructorMethodAdvice {
    @Advice.OnMethodEnter
    public static void onMethodEnter(@Advice.Origin String methodName) {
        System.out.printf("constructor %s start. \n", methodName);
    }

    @Advice.OnMethodExit
    public static void onMethodExit(@Advice.Origin String methodName) {
        System.out.printf("constructor %s end. \n", methodName);
    }
}
// put(K,V)方法Advice
public class PutMethodAdvice {
    @Advice.OnMethodEnter
    public static Long onMethodEnter(@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
        Long start = System.currentTimeMillis();
        if (value instanceof String) {
            value = "_" + value;
        }
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return start;
    }

    @Advice.OnMethodExit
    public static void onMethodExit(@Advice.Origin Method method,
                                    @Advice.Enter Long start) {

        System.out.printf("method %s cost %d ms. \n", method.getName(), System.currentTimeMillis() - start);
    }

}
public class AgentPremain {


    public static void premain(String arguments, Instrumentation instrumentation) {
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        .method(ElementMatchers.named("put"))
                        .intercept(Advice.to(PutMethodAdvice.class))
                        .constructor(ElementMatchers.any())
                        .intercept(Advice.to(ConstructorMethodAdvice.class))
                )
                .with(new TransformListener())
                .installOn(instrumentation);
    }

}

maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar

运行测试类:

    public static void main(String[] args) {
        Operation<String, String> operation = new DefaultOperation();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();
    }

输出结果:

Agent premain!
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() start. 
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() end. 
method put cost 21 ms. 
method put cost 20 ms. 
method put cost 20 ms. 
{"A":"_a", "B":"_b", "C":null}

使用Arthas的jad命令反编译DefaultOperation类:

public class DefaultOperation implements Operation<String, String> {
    private Map<String, String> map;

    @Override
    public void dump() {
        if (this.map.isEmpty()) {
            System.out.println("{}");
        }
        StringBuilder sb = new StringBuilder("{");
        for (Map.Entry<String, String> entry : this.map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
            sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
        }
        if (sb.lastIndexOf(", ") == sb.length() - 2) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append("}");
        System.out.println(sb.toString());
    }

    private /* synthetic */ void put$original$QVENVsOQ(String key, String value) {
        this.map.put(key, value);
    }

    public DefaultOperation() {
        System.out.printf("constructor %s start. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()");
        DefaultOperation defaultOperation = this;
        defaultOperation(null);
        System.out.printf("constructor %s end. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()");
    }

    private /* synthetic */ DefaultOperation(auxiliary.8KQdfbi7 kQdfbi7) {
        this.map = new HashMap<String, String>();
    }

    static {
        ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 0x91E11EE);
    }

    @Override
    public String get(String key) {
        return this.map.get(key);
    }

    @Override
    public void put(String string, String string2) {
        Long l = System.currentTimeMillis();
        if (string2 instanceof String) {
            string2 = "_" + string2;
        }
        try {
            TimeUnit.MILLISECONDS.sleep(20L);
        }
        catch (InterruptedException interruptedException) {
            interruptedException.printStackTrace();
        }
        DefaultOperation defaultOperation = this;
        String string3 = string;
        String string4 = string2;
        defaultOperation.put$original$QVENVsOQ(string3, string4);
        System.out.printf("method %s cost %d ms. \n", DefaultOperation.class.getMethod("put", String.class, String.class).getName(), System.currentTimeMillis() - l);
    }
}

1.4 拦截方法(使用MethodDelegation)

// put(K,V)方法的Delegation
public class PutMethodDelegation {

    @RuntimeType
    public static void put(@RuntimeType @Argument(0) Object key,
                           @RuntimeType @Argument(1) Object value,
                           @SuperCall Runnable runnable) {
        System.out.printf("enter method put, key=%s, value=%s \n", key, value);
        try {
            runnable.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit method put");
    }
}
// get(K)方法的Delegation
public class GetMethodDelegation {

    @RuntimeType
    public static Object get(@RuntimeType @Argument(0) Object key,
                             @SuperCall Callable<Object> callable) {
        System.out.println("enter method get");
        Object result = null;
        try {
            // @SuperCall修饰的Callable<Object>为对原方法调用的封装,返回类型为void时为Runnable
            result = callable.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit method get");
        return result;
    }
}
public class AgentPremain {

    public static void premain(String arguments, Instrumentation instrumentation) {
        System.out.println("Agent premain!");
        new AgentBuilder.Default()
                .type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                )
                .transform((builder, typeDescription, classLoader, module) -> builder
                        .method(ElementMatchers.named("put"))
                        .intercept(MethodDelegation.to(PutMethodDelegation.class))
                        .method(ElementMatchers.named("get"))
                        .intercept(MethodDelegation.to(GetMethodDelegation.class))
                )
                .with(new TransformListener())
                .installOn(instrumentation);
    }
}

maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar

运行测试类:

    public static void main(String[] args) {
        Operation<String, String> operation = new DefaultOperation();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();
    }

输出结果:

Agent premain!
enter method put, key=A, value=a 
exit method put
enter method put, key=B, value=b 
exit method put
enter method put, key=C, value=null 
exit method put
{"A":"a", "B":"b", "C":null}

使用Arthas的jad命令反编译DefaultOperation类:

public class DefaultOperation implements Operation<String, String> {
    private Map<String, String> map = new HashMap<String, String>();

    @Override
    public void dump() {
        if (this.map.isEmpty()) {
            System.out.println("{}");
        }
        StringBuilder sb = new StringBuilder("{");
        for (Map.Entry<String, String> entry : this.map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
            sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
        }
        if (sb.lastIndexOf(", ") == sb.length() - 2) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append("}");
        System.out.println(sb.toString());
    }

    private /* synthetic */ void put$original$FYSPUuUe(String key, String value) {
        this.map.put(key, value);
    }

    private /* synthetic */ String get$original$FYSPUuUe(String key) {
        return this.map.get(key);
    }

    final /* synthetic */ String get$original$FYSPUuUe$accessor$qaEMliZ2(String string) {
        return this.get$original$FYSPUuUe(string);
    }

    final /* synthetic */ void put$original$FYSPUuUe$accessor$qaEMliZ2(String string, String string2) {
        this.put$original$FYSPUuUe(string, string2);
    }

    static {
        ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 420827887);
    }

    @Override
    public String get(String string) {
        return (String)GetMethodDelegation.get(string, new DefaultOperation$auxiliary$zU9K9I52(this, string));
    }

    @Override
    public void put(String string, String string2) {
        PutMethodDelegation.put(string, string2, new DefaultOperation$auxiliary$ywsBi3iU(this, string, string2));
    }
}

2. 在Runtime中使用

在pom.xml中添加依赖

        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.19</version>
        </dependency>

        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.10.19</version>
        </dependency>

2.1 拦截方法(使用Advice)

public class PutMethodAdvice {
    @Advice.OnMethodEnter
    public static Long onEnter(@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object key,
                               @Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
        key = "_" + key;
        value = "_" + (value != null ? value : "");
        Long startMillis = System.currentTimeMillis();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return startMillis;
    }
    
    @Advice.OnMethodExit
    public static void onExit(@Advice.Enter Long startMillis) {
        System.out.printf("method put cost %d ms \n", System.currentTimeMillis() - startMillis);
    }
}
public class InterceptMethodEnhancer {
    public static void interceptByAdvice(){
        ByteBuddyAgent.install();
        new ByteBuddy()
                .rebase(DefaultOperation.class)
                .visit(Advice.to(PutMethodAdvice.class).on(ElementMatchers.named("put")))
                .visit(Advice.to(GetMethodAdvice.class).on(ElementMatchers.named("get")))
                .make()
                .load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
    }
}

运行测试类:

        Operation<String, String> operation = new DefaultOperation();
        InterceptMethodEnhancer.interceptByAdvice();
        operation.put("A", "a");
        operation.put("B", "b");
        operation.put("C", null);
        operation.dump();

输出结果:

method put cost 19 ms 
method put cost 20 ms 
method put cost 11 ms 
{"_A":"_a", "_B":"_b", "_C":"_"}

2.2 添加注解(在类、成员变量、方法上)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Mark {
    String value() default "";

}
public class AddAnnotationEnhancer {
    public static void addOnClassAndFieldAndMethod() {
        ByteBuddyAgent.install();
        // 构建annotation description
        AnnotationDescription annotationDescription = AnnotationDescription.Latent.Builder
                // 设置注解常量值
                .ofType(Mark.class).define("value", "marked").build();
        new ByteBuddy()
                .redefine(DefaultOperation.class)
                .annotateType(annotationDescription)
                .visit(new MemberAttributeExtension.ForField()
                        .annotate(annotationDescription)
                        .on(ElementMatchers.named("map")))
                .visit(new MemberAttributeExtension.ForMethod()
                        .annotateMethod(annotationDescription)
                        .on(ElementMatchers.named("put")))
                .make()
                .load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
    }
}

运行测试类:

        Operation<String, String> operation = new DefaultOperation();
        AddAnnotationEnhancer.addOnClassAndFieldAndMethod();
        Field field = operation.getClass().getDeclaredField("map");
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        Mark markOnClass = operation.getClass().getAnnotation(Mark.class);
        Mark markOnField = field.getAnnotation(Mark.class);
        Mark markOnMethod = operation.getClass().getMethod("put", String.class, String.class).getAnnotation(Mark.class);
        System.out.printf("Class's Mark value is %s \n", markOnClass.value());
        System.out.printf("Field's Mark value is %s \n", markOnField.value());
        System.out.printf("Method's Mark value is %s \n", markOnMethod.value());

输出结果:

Class's Mark value is marked 
Field's Mark value is marked 
Method's Mark value is marked