bytebuddy之advice详解 & 注解详解

注解列表

  • 一、 注解列表
  • 二、 demo 解析
    • 2.1 测试工具介绍
      • 2.1.1 目标的类得classloader
      • 2.1.2 获取当前jvm得 Instrumentation
    • 2.2 @OnMethodEnter 和 @OnMethodExit的简单应用
      • 2.2.1 @OnMethodEnter 进入方法时
        • 2.2.1.1 skipOn() 跳过
          • 1. void 默认不跳过的
          • 2 OnDefaultValue 默认值
          • 3 OnNonDefaultValue 非默认值
          • 4 自定义类型
        • 2.2.1.2 inline() 默认true ,内联编译
        • 2.2.1.3 suppress 处理告警,默认是抑制告警
      • 2.2.2 @OnMethodExit
        • 2.2.2.1 repeatOn() 重复
        • 2.2.2.2 onThrowable() 异常的捕获和 @Thrown搭配使用
    • 2.3 @This注解
      • 2.3.1 default : optional() = false,typing()=Assigner.Typing.STATIC;
      • 2.3.2 default : optional() = true,typing()=Assigner.Typing.STATIC;
      • 2.3.3 default : optional() = true,typing()=Assigner.Typing.DYNAMIC;
    • 2.4 @Argument 和 @AllArguments
      • 2.4.1 @AllArguments
      • 2.4.2 @Argument
    • 2.5 @Return 获取返回值
    • 2.6 @Thrown 获取异常
    • 2.7 @FieldValue 获取目标类中的变量
    • 2.8 @Origin
    • 2.9 @Enter
    • 2.10 @Exit
    • 2.11 @Local
      • 1 获取
    • 2.12 @StubValue
    • 2.13 @Unused

bytebuddy实现原理分析 &源码分析(一)
bytebuddy实现原理分析 &源码分析 (二)
bytebuddy实现原理分析 &源码分析 (三)
bytebuddy实现原理分析 &源码分析 (四)

一、 注解列表

注解
@OnMethodEnter 表示这个方法会在,进入目标方法时调用,这个注解声明的方法必须是static。当目标的方法是constructor构造器时,@This只能写field,不能读field,或者调用方法 skipOn() 跳过一些方法
prependLineNumber() 如果为true,会改目标方法的行号
inline() 标识方法应该被内联到目标的方法中去
suppress() 忽略某些异常
@OnMethodExit 表示这个方法会在,目标方法结束时调用,这个注解声明的方法必须是static。如果方法提前终止了,那么这个就不i呗调用 repeatOn() 标识目标方法是否被重复执行
onThrowable() 一般被织入的方法抛出了某些异常,可以有响应的handler处理
backupArguments() 备份所有被执行方法的类型,开始会影响效率
inline() 标识方法应该被内联到目标的方法中去
suppress() 忽略某些异常
@This 表示被注解的参数,应该是被修改对象的引用,不能用在静态方法和构造器上 optional() 决定被修改的类型是否需要被设置为null,如果目标方法类型是static或者在一个constructor
readOnly() 只读不能修改
typing() 类型转化,默认要求静态转化,就是转化不能改变类型。动态是可以改变
@Argument 被标注到目标类型的参数上,表示被标注的参数会被value()代表的索引拿到
readOnly() 只读
typing() 转换这个类型使用的方式,默认是静态转换(类型不会被改动),动态转换是void.class可以被改成其他类
optional() 备选值,如果索引不存在,会使用这个提供的值。默认是关闭的
@AllArguments 使用一个数组来包含目标方法的参数,目标的参数必须是一个数组。
readOnly() 只读
typing() 类型开关
@Return 标注在参数上,来引用目标方法的返回值 readOnly() 只读
typing() 类型转化,默认是静态转换
@Thrown 获取抛出的异常
readOnly() 只读
typing() 默认是动态转化,可以改变类型
@FieldValue 被注解的参数,可以引用,目标method内的定义的局部变量 String value() 局部变量的名称
declaringType() 被声明的类型
readOnly() 只读的
typing() 默认是静态转化
@Origin 使用一个String代表目标方法的 String value() default DEFAULT 默认值是""
@Enter 标注在参数上,指向被标注@OnMethodEnter的advice方法的返回值,
readOnly() 只读标记
typing() 转换
@Exit 标注在参数上,指向被标注@OnMethodExit的advice方法的返回值,
readOnly() 只读标记
typing() 转换
@Local 声明被注解的参数当做一个本地变量,被Byte Buddy,织入目标方法中。本地变量可以被@OnMethodEnter@link OnMethodExit读写。然而如果本地变量被exit advice 引用了,它必须也在enter 的advice所声明。就是用来交换变量的。 String value() name
@StubValue mock值,总是返回一个设定的值
@Unused 让被标记的参数,总是返回默认值,比如int 返回0, 其他类型返回null

二、 demo 解析

byte budddy 单测
测试修改的类。
byte buddy 实用手册

2.1 测试工具介绍

2.1.1 目标的类得classloader

ChildFirst打破双亲委派,当前classLoader直接加载。
readToNames 返回Map返回类和类得字节码。自己读取也可以。
MANIFEST持久化策略,代表加载得会被保存下来

ClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
                ClassFileLocator.ForClassLoader.readToNames(Cooker.class),
                ByteArrayClassLoader.PersistenceHandler.MANIFEST);
              

2.1.2 获取当前jvm得 Instrumentation

ByteBuddyAgent.install(); 一旦调用就会在内存中维护一个实例,实例中得Instrumentation是当前jvm得应用。

2.2 @OnMethodEnter 和 @OnMethodExit的简单应用

代码得位置
bytebuddy之advice详解 & 注解详解_第1张图片
定义了两个一模一样得目标类CookerCooker2

package bytebuddys.advice.target;

public class Cooker {

    String name = "foo";

    public Cooker() {
        System.out.println("");
        System.out.println("Im Cooker");
        System.out.println("Constructor();");
    }

    public void hello() {
        System.out.println("public void hello();");
    }

    public String makeFood(String foodName, int deskId, Double[] materialsPrice) {
        System.out.println(materialsPrice.getClass().getName());

        System.out.println("public String makeFood(String foodName, int deskId, Double[] materialsPrice)! ");

        return foodName + ":" + deskId + ":" + materialsPrice.length;
    }

    public static void taste(String foodName) {

        System.out.println("public static void taste(String foodName)! ");

    }

}

我们给Cooker(除了构造器)所有方法的开始和结束织入额外的逻辑

package bytebuddys.advice;

import net.bytebuddy.asm.Advice;

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void onMethodEnter(){
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");

    }

    @Advice.OnMethodExit
    public static void onMethodExit(){
        System.out.println("exit!");
    }

}

测试一下
我们对Cooker改造,不动 Cooker2

package bytebuddys.advice;

import bytebuddys.advice.target.Cooker;
import bytebuddys.advice.target.Cooker2;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.lang.instrument.ClassFileTransformer;

public class TestAdvice {
    public static TestAdvice INSTANCE = new TestAdvice();

    ClassLoader classLoader;

    /**
     * 加载未被修改的类
     */
    public void initClassLoader() throws Exception {


        classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
                ClassFileLocator.ForClassLoader.readToNames(Cooker.class),
                ByteArrayClassLoader.PersistenceHandler.MANIFEST);


    }


    /**
     * modify
     */
    public void modifyTarget() throws Exception {
        ByteBuddyAgent.install();
        ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
                .with(AgentBuilder.PoolStrategy.Default.EXTENDED)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                //.ignore()
                // 设定匹配范围
                .type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                        // 对任何类都剩下
                        return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.not(ElementMatchers.isConstructor()).and(ElementMatchers.any())));
                    }
                })
                .installOnByteBuddyAgent();


    }

    public void print() throws Exception {

        Class<Cooker> cookerType = (Class<Cooker>) classLoader.loadClass(Cooker.class.getName());
        Object Cooker = cookerType.getDeclaredConstructor().newInstance();
        cookerType.getDeclaredMethod("hello").invoke(Cooker);
        cookerType.getDeclaredMethod("taste", String.class).invoke(null, "pototo");
        cookerType.getMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker, "pototo", 1, new Double[]{1.0, 2.0});

        Class<Cooker2> cooker2Type = (Class<Cooker2>) classLoader.loadClass(Cooker2.class.getName());
        Object Cooker2 = cooker2Type.getDeclaredConstructor().newInstance();
        cooker2Type.getDeclaredMethod("hello").invoke(Cooker2);
        cooker2Type.getDeclaredMethod("taste", String.class).invoke(Cooker2, "pototo");
        cooker2Type.getDeclaredMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker2, "pototo", 1, new Double[]{1.0, 2.0});

    }

    public static void main(String[] args) throws Exception {
        INSTANCE.initClassLoader();

        INSTANCE.modifyTarget();

        INSTANCE.print();

    }
}

print


Im Cooker
Constructor();
- - - - - - - - - - -
enter!
public void hello();
exit!
- - - - - - - - - - -
enter!
public static void taste(String foodName)! 
exit!
- - - - - - - - - - -
enter!
[Ljava.lang.Double;
public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 
exit!

Im Cooker2
Constructor();
public void hello();
public static void taste(String foodName)! 
[Ljava.lang.Double;
public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

2.2.1 @OnMethodEnter 进入方法时

  • skipOn() 跳过某个方法
  • inline(), 内联(默认)还是调用,区别是内联相当在目标类加入代码逻辑,会更快。调用就是调用第三方的方法。
  • suppress() 将方法抛出的异常交给一个异常handler处理,默认是NoExceptionHandler.class相当于就是,抑制异常,不处理。

2.2.1.1 skipOn() 跳过

这个跳过方法的判定比较奇葩,他是根据advice中返回值,来确定是否跳过。注意不能跳过构造器方案。

  • OnDefaultValue 当advice方法的返回值是{false for boolean , 0 for byte、short、char、int、long、float 、double , 或者null for 引用类型 } 那么就跳过目标方法。
  • OnNonDefaultValue 就是刚好相反
  • void默认 代表不跳过任何方法
  • 自定义类型,基本类型的坏处是,无法保留额外的信息,仅仅靠判断0或者非零。
    自定义类型,可以携带额外的信息。

目标类
我们要跳过除了构造器以外的所有方法。

public class Cooker {

    String name = "foo";

    public Cooker() {
        System.out.println("");
        System.out.println("Im Cooker`s Constructor()");

    }

    public void hello() {
        System.out.println("+ method name : public void hello();");
    }

    public String makeFood(String foodName, int deskId, Double[] materialsPrice) {

        System.out.println("+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! ");

        return foodName + ":" + deskId + ":" + materialsPrice.length;

    }


    public static void taste(String foodName) {

        System.out.println("+ method name : public static void taste(String foodName)! ");

    }

    public Cooker getInstance() {
        System.out.println("+ method name : public Cooker getInstance()");
        return this;
    }
}

测试

public class TestAdvice {
  
    public void defineNewClass() throws Exception {
        Class<?> cookerType = new ByteBuddy()
                .redefine(Cooker.class)
                .visit(Advice.to(AdviceLogic.class).on(ElementMatchers.not(ElementMatchers.isConstructor())))
                .make()
                .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();


        Object Cooker = cookerType.getDeclaredConstructor().newInstance();
        cookerType.getDeclaredMethod("hello").invoke(Cooker);
        cookerType.getDeclaredMethod("taste", String.class).invoke(null, "pototo");
        cookerType.getMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker, "pototo", 1, new Double[]{1.0, 2.0});

        cookerType.getDeclaredMethod("getInstance").invoke(Cooker);

    }
    public static void main(String[] args) throws Exception {

        INSTANCE.defineNewClass();


    }
}
1. void 默认不跳过的
public class AdviceLogic {
    
    @Advice.OnMethodEnter
    public static void enter() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");
        
    }

}

可以看到,enter 方法返回了一个 void 的方法。
打印
没有跳过,因此他执行了所有的方法

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!
+ method name : public void hello();

- - - - - - - - - - -
enter!
+ method name : public static void taste(String foodName)! 

- - - - - - - - - - -
enter!
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

- - - - - - - - - - -
enter!
+ method name : public Cooker getInstance()
2 OnDefaultValue 默认值

后面我们可以知道,enter是可以拿到被修改方式实例的引用和参数的。所有具体可以生成不同的值。如果 返回值是之前描述的各种基本类型的默认值,或者null。那么就跳过目标的方法。

如下示例中,我们返回了0 。结果就是目标所有的方法都不执行。

public class AdviceLogic {

    @Advice.OnMethodEnter( skipOn = Advice.OnDefaultValue.class)
    public static int enter() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");
        return 0;

    }

}

打印
什么也没有执行

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!

- - - - - - - - - - -
enter!

- - - - - - - - - - -
enter!

- - - - - - - - - - -
enter!
3 OnNonDefaultValue 非默认值

刚好相反,如果上面的例子中,return不为0,那么就跳过

4 自定义类型

除了用基本类型标识外还可以,使用自定义的类型。如果自定义类型返回会null,那么就不跳过。

advice-不跳过

public class AdviceLogic {

    @Advice.OnMethodEnter( skipOn = Cooker.class)
    public static Cooker enter() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");
        return null;

    }

}

打印
输出了所有

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!
+ method name : public void hello();

- - - - - - - - - - -
enter!
+ method name : public static void taste(String foodName)! 

- - - - - - - - - - -
enter!
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

- - - - - - - - - - -
enter!
+ method name : public Cooker getInstance()

advice-跳过

public class AdviceLogic {


    @Advice.OnMethodEnter( skipOn = Cooker.class)
    public static Cooker enter() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");
        return new Cooker();

    }

}

打印
仅仅打印了 new Cooker的构造器,跳过了所有。

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!

Im Cooker`s Constructor()

2.2.1.2 inline() 默认true ,内联编译

2.2.1.3 suppress 处理告警,默认是抑制告警

Class suppress() default NoExceptionHandler.class;把告警默认委托给一个handler,一个什么也不处理的handler。

比如抑制advice逻辑 抛出的IO异常


    @Advice.OnMethodEnter(skipOn = Cooker.class, suppress = IOException.class)
    public static Cooker enter() throws IOException{

 		throw new IOException();
        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");
        return null;

    }

2.2.2 @OnMethodExit

  • repeatOn() 和skipon()类似的判断方式也有OnDefaultValueOnNonDefaultValue。但是标注了方法意味只要再重复一次
  • onThrowable() 捕获抛出的异常,和@Thrown搭配使用
  • backupArguments() 备份参数,默认为true,会专门的copy原先的参数
  • inline()suppress()都是和@OnMethodExit都是类似的。

2.2.2.1 repeatOn() 重复

    @Advice.OnMethodExit(repeatOn = Advice.OnDefaultValue.class)
    public static int exit() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit!");
        return 0;

    }

打印
可以看到不断的重复hello();只重复一次,但是hello中不断命中advice。就是套娃,可以通过,在方法中,设置一个常量,来动态的改变返回值,来控制返回次数。

exit!
+ method name : public void hello();

- - - - - - - - - - -
exit!
+ method name : public void hello();

- - - - - - - - - - -
exit!
+ method name : public void hello();

- - - - - - - - - - -
exit!
+ method name : public void hello();
.... 

2.2.2.2 onThrowable() 异常的捕获和 @Thrown搭配使用

抛出异常

  public void hello() throws Exception {
        System.out.println("+ method name : public void hello();");
        throw new IOException();
    }

advice

public class AdviceLogic {

    @Advice.OnMethodExit(onThrowable = Throwable.class)
    public static int exit(@Advice.Thrown Throwable thrown) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit!"+ thrown.getClass().getName());
        return 0;

    }

}

打印
可以看到拿到了目标方法抛出的异常。

Im Cooker`s Constructor()
+ method name : public void hello();

- - - - - - - - - - -
exit!java.io.IOException
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at bytebuddys.advice.TestAdvice.defineNewClass(TestAdvice.java:76)
	at bytebuddys.advice.TestAdvice.main(TestAdvice.java:108)
Caused by: java.io.IOException
	at bytebuddys.advice.target.Cooker.hello(Cooker.java:19)
	... 6 more

2.3 @This注解

用来获取被修改的对象,

  • optional() = false 默认值。不能用在构造器静态方法,否则会报错 Exception in thread "main" java.lang.IllegalStateException: Cannot map this reference for static method or constructor start。如果optional() = true是,遇到构造器静态方法这种没有实例的对象是,This获取可以为null
  • readOnly()。 效果相当于final,能不能修改传入的对象。
  • typing()。 类型转化,Assigner.Typing.STATIC;不会进行强制转换,如果类型不符合直接报错,Assigner.Typing.DYNAMIC;会进行强制转换。

Cooker目标类

public class Cooker {

    String name = "foo";

    public Cooker() {
        System.out.println("");
        System.out.println("Im Cooker`s Constructor()");

    }

    public void hello() {
        System.out.println("+ method name : public void hello();");
    }

    public String makeFood(String foodName, int deskId, Double[] materialsPrice) {

        System.out.println("+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! ");

        return foodName + ":" + deskId + ":" + materialsPrice.length;
    }

    public static void taste(String foodName) {

        System.out.println("+ method name : public static void taste(String foodName)! ");

    }

}

2.3.1 default : optional() = false,typing()=Assigner.Typing.STATIC;

不能被用于 静态方法,如果被用于静态方法,bytebuddy里会报错,agentbuilder会会忽略掉,就是什么也不发生
ADVICE

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void enter(@Advice.This Cooker thiz) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");

        if (thiz == null) {
            System.out.println("- @This is null ! maybe used for  static or contructors! ");
        } else {
            System.out.println("- @This!" + thiz.getClass().getName());
        }
    }
}

测试1
ElementMatchers.isMethod().and(ElementMatchers.not(ElementMatchers.isStatic()))意味着只是匹配目标类的普通方法

public class TestAdvice {
    public static TestAdvice INSTANCE = new TestAdvice();

    ClassLoader classLoader;


    /**
     * 加载未被修改的类
     */
    public void initClassLoader() throws Exception {

        classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
                ClassFileLocator.ForClassLoader.readToNames(Cooker.class),
                ByteArrayClassLoader.PersistenceHandler.MANIFEST);


    }


    /**
     * modify
     */
    public void modifyTarget() throws Exception {
        ByteBuddyAgent.install();
        ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
                .with(AgentBuilder.PoolStrategy.Default.EXTENDED)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                //.ignore()
                // 设定匹配范围
                .type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                        // 对比 enter 设置为 @Advice.This(optional = false)
                        //      exit 设置为 @Advice.This(optional = true )

                       //return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.any()));
                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.isMethod().and(ElementMatchers.not(ElementMatchers.isStatic()))));

                    }
                })
                .installOnByteBuddyAgent();


    }



    public void print() throws Exception {

        Class<Cooker> cookerType = (Class<Cooker>) classLoader.loadClass(Cooker.class.getName());
        Object Cooker = cookerType.getDeclaredConstructor().newInstance();
        cookerType.getDeclaredMethod("hello").invoke(Cooker);
        cookerType.getDeclaredMethod("taste", String.class).invoke(null, "pototo");
        cookerType.getMethod("makeFood", String.class, int.class, Double[].class).invoke(Cooker, "pototo", 1, new Double[]{1.0, 2.0});


    }

    public static void main(String[] args) throws Exception {
        
        INSTANCE.initClassLoader();

        INSTANCE.modifyTarget();
        INSTANCE.print();


    }
}

打印

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!
- @This!bytebuddys.advice.target.Cooker
+ method name : public void hello();
+ method name : public static void taste(String foodName)! 

- - - - - - - - - - -
enter!
- @This!bytebuddys.advice.target.Cooker
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

测试2
ElementMatchers.any()表示任何方法,这意味着会出错,出错意味着原样输出

    public void modifyTarget() throws Exception {
        ByteBuddyAgent.install();
        ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
                .with(AgentBuilder.PoolStrategy.Default.EXTENDED)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                //.ignore()
                // 设定匹配范围
                .type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                        // 对比 enter 设置为 @Advice.This(optional = false)
                        //      exit 设置为 @Advice.This(optional = true )

                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.any()));
//                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.isMethod().and(ElementMatchers.not(ElementMatchers.isStatic()))));

                    }
                })
                .installOnByteBuddyAgent();


    }

打印

Im Cooker`s Constructor()
+ method name : public void hello();
+ method name : public static void taste(String foodName)! 
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

2.3.2 default : optional() = true,typing()=Assigner.Typing.STATIC;

即使ElementMatchers.any(),也会正常输出,但是This打印为null的输出
ADVICE

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void enter(@Advice.This(optional = true) Object thiz) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");

        if (thiz == null) {
            System.out.println("- @This is null ! maybe used for  static or contructors! ");
        } else {
            System.out.println("- @This!" + thiz.getClass().getName());
        }
        
    }
}
    public void modifyTarget() throws Exception {
        ByteBuddyAgent.install();
        ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
                .with(AgentBuilder.PoolStrategy.Default.EXTENDED)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                //.ignore()
                // 设定匹配范围
                .type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                        // 对比 enter 设置为 @Advice.This(optional = false)
                        //      exit 设置为 @Advice.This(optional = true )

                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.any()));
//                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.isMethod().and(ElementMatchers.not(ElementMatchers.isStatic()))));

                    }
                })
                .installOnByteBuddyAgent();


    }

打印

- - - - - - - - - - -
enter!
- @This is null ! maybe used for  static or contructors! 

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!
- @This!bytebuddys.advice.target.Cooker
+ method name : public void hello();

- - - - - - - - - - -
enter!
- @This is null ! maybe used for  static or contructors! 
+ method name : public static void taste(String foodName)! 

- - - - - - - - - - -
enter!
- @This!bytebuddys.advice.target.Cooker
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

2.3.3 default : optional() = true,typing()=Assigner.Typing.DYNAMIC;

advice

这里将一个Cooker对象转化为一个Cooker2
如果是Assigner.Typing.STATIC默认不会转换,直接原样输出。
Assigner.Typing.DYNAMIC,会尝试装换,发现是Cooker2,抛出异常。

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void enter(@Advice.This(optional = true , typing = Assigner.Typing.DYNAMIC)  Cooker2 thiz) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");

        if (thiz == null) {
            System.out.println("- @This is null ! maybe used for  static or contructors! ");
        } else {
            System.out.println("- @This!" + thiz.getClass().getName());
        }

    }
}

打印

- - - - - - - - - - -
enter!
- @This is null ! maybe used for  static or contructors! 

Im Cooker`s Constructor()

- - - - - - - - - - -
enter!
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at bytebuddys.advice.TestAdvice.print(TestAdvice.java:67)
	at bytebuddys.advice.TestAdvice.main(TestAdvice.java:79)
Caused by: java.lang.ClassCastException: bytebuddys.advice.target.Cooker cannot be cast to bytebuddys.advice.target.Cooker2
	at bytebuddys.advice.target.Cooker.hello(Cooker.java:16)
	... 6 more

2.4 @Argument 和 @AllArguments

2.4.1 @AllArguments

advice
打印所有的入参

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void enter(@Advice.AllArguments Object[] args) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");

        System.out.println("- @AllArguments !!");
        for (Object o : args) {
            System.out.println("- - - type : " + o.getClass().getName() + ", value : " + o.toString());
        }


    }
}

测试
builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.named("makeFood")));匹配cooker的makeFood方法

    public void modifyTarget() throws Exception {
        ByteBuddyAgent.install();
        ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
                .with(AgentBuilder.PoolStrategy.Default.EXTENDED)
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                //.ignore()
                // 设定匹配范围
                .type(ElementMatchers.is(Cooker.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                        // 对比 enter 设置为 @Advice.This(optional = false)
                        //      exit 设置为 @Advice.This(optional = true )

                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.named("makeFood")));
//                       return builder.visit(Advice.to(AdviceLogic.class).on(ElementMatchers.isMethod().and(ElementMatchers.not(ElementMatchers.isStatic()))));

                    }
                })
                .installOnByteBuddyAgent();


    }

打印

- - - - - - - - - - -
enter!
- @AllArguments !!
- - - type : java.lang.String, value : pototo
- - - type : java.lang.Integer, value : 1
- - - type : [Ljava.lang.Double;, value : [Ljava.lang.Double;@48fa0f47
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

2.4.2 @Argument

类似上一个注解,这个注解也是用来获取传入的参数。
但是需要指名参数的序号optional = true意味着如果目标方法没有该参数,依旧会返回,不过值是null

advice
这就是一个打乱了次序,和填了一个目标方法不存的参数。却是依旧能正确返回的例子。

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static void enter(@Advice.Argument(value = 1) int arg2,
                             @Advice.Argument(value = 2) Double[] arg3,
                             @Advice.Argument(value = 0) String arg1,
                             @Advice.Argument(value = 3, optional = true) String arg4
    ) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter!");


        Object[] args = new Object[4];
        args[0] = arg1;
        args[1] = arg2;
        args[2] = arg3;
        args[3] = arg4;
        for (Object o : args) {
            if (o != null) {
                System.out.println("- - - type : " + o.getClass().getName() + ", value : " + o.toString());
            } else {
                System.out.println("- - - type : null , value : null ");
            }

        }
        
    }
}

打印

enter!
- - - type : java.lang.String, value : pototo
- - - type : java.lang.Integer, value : 1
- - - type : [Ljava.lang.Double;, value : [Ljava.lang.Double;@5e316c74
- - - type : null , value : null 
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

2.5 @Return 获取返回值

advice
只能标注在@Advice.OnMethodExit上,用来承接返回值

public class AdviceLogic {

    @Advice.OnMethodExit
    public static void exit(@Advice.Return String result) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit!");

        System.out.println("- - - type : " + result.getClass().getName() + ", value : " + result.toString());


    }

}

打印

- - - - - - - - - - -
exit!
- - - type : java.lang.String, value : pototo:1:2

2.6 @Thrown 获取异常

捕获异常 2.2.2.2有讲

2.7 @FieldValue 获取目标类中的变量

  • value() filedValue
    @Advice.OnMethodEnter@Advice.OnMethodExit ,都可以用
  • declaringType filedValue 所在的类
    Cooker
public class Cooker {
    String name = "foo";
}

advice

public class AdviceLogic {

    @Advice.OnMethodExit
    public static int exit(@Advice.FieldValue(value = "name",declaringType = Cooker.class) String name) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit! name : "+ name);
        return 0;

    }

}

打印
通通拿到了

Im Cooker`s Constructor()

- - - - - - - - - - -
exit! name : foo
+ method name : public void hello();

- - - - - - - - - - -
exit! name : foo
+ method name : public static void taste(String foodName)! 
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

- - - - - - - - - - -
exit! name : foo

2.8 @Origin

利用反射,将目标字符串的签名,转化为方法和类格式,然后去调用。

advice

public class AdviceLogic {

    @Advice.OnMethodExit
    public static int exit(@AllArguments Object[] allArguments,
                          @Origin Method method
                          @Origin Class<?> method) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit! name : "+ origin);
        return 0;

    }

}

打印

- - - - - - - - - - -
exit! name : public bytebuddys.advice.target.Cooker()
+ method name : public void hello();

- - - - - - - - - - -
exit! name : public void bytebuddys.advice.target.Cooker.hello()
+ method name : public static void taste(String foodName)! 
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

- - - - - - - - - - -
exit! name : public java.lang.String bytebuddys.advice.target.Cooker.makeFood(java.lang.String,int,java.lang.Double[])

2.9 @Enter

标注在参数上,指向被标注@OnMethodEnter的advice方法的返回值,
readOnly() 只读标记
typing() 转换

advice

public class AdviceLogic {

    @Advice.OnMethodEnter
    public static int enter() {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter! name ");
        return 0;

    }

    @Advice.OnMethodExit
    public static int exit(@Advice.Enter int a) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("exit! the enter return   : "+ a);
        return 0;

    }

}

打印

- - - - - - - - - - -
enter! name 

Im Cooker`s Constructor()

- - - - - - - - - - -
exit! the enter return   : 0

- - - - - - - - - - -
enter! name 
+ method name : public void hello();

- - - - - - - - - - -
exit! the enter return   : 0
+ method name : public static void taste(String foodName)! 

- - - - - - - - - - -
enter! name 
+ method name : public String makeFood(String foodName, int deskId, Double[] materialsPrice)! 

- - - - - - - - - - -
exit! the enter return   : 0

2.10 @Exit

标注在参数上,指向被标注@OnMethodExit的advice方法的返回值,
@Enter类似。

2.11 @Local

创建本地变量中值,为方法创建一个局部变量。
常见的用途是

  • 在方法内创建一个局部变量,然后可以被@Advice.OnMethodEnter@Advice.OnMethodExit 同时获取到。

1 获取

比如一个本地方法foo,就会被存储在本地变量

public static class Sample {

        public String foo() {
            return "foo";
        }
    }

可以通过如下获取

public static class LocalValueAdvice {

        @Advice.OnMethodEnter
        private static void enter(@Advice.Local("foo") Object foo) {
            if (foo != null) {
                throw new AssertionError();
            }else{
            	//如果类没有的这个局部变量的变化,还可以创建一个
            	foo ="new foo"
            }
       		// 更改
            foo = "easy";
            if (!foo.equals(FOO)) {
                throw new AssertionError();
            }
            Sample.enter++;
        }
}
		// exit中就可以拿到
        @Advice.OnMethodExit
        private static void exit(@Advice.Local("foo") Object foo) {
            if (foo != null) {
                throw new AssertionError();
            }else{
            	//如果类没有的这个局部变量的变化,还可以创建一个
            	foo ="new foo"
            }
       		// 更改
            foo = "easy";
            if (!foo.equals(FOO)) {
                throw new AssertionError();
            }
            Sample.enter++;
        }
}

2.12 @StubValue

mock值,总是返回一个设定的值.

  • 必须使用 Object 迎接,
  • 返回默认值比如 null,0
    @Advice.OnMethodEnter
    public static int enter(@Advice.StubValue Object a) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter! StubValue "+ a);
        return 0;

    }

2.13 @Unused

Unused ,被标注了这个注解的参数总是返回默认值

    @Advice.OnMethodEnter
    public static int enter(@Advice.Unused  Object arg) {

        System.out.println("");
        System.out.println("- - - - - - - - - - -");
        System.out.println("enter! StubValue ");
        if(arg!=null){
            System.out.println("arg0 -"+arg.getClass().getName() +" - value : "+arg.toString());
        }
        return 0;

    }

你可能感兴趣的:(bytebuddy)