头图

简介

Byte Buddy是一个开源的Java字节码操作库,它允许程序在运行时生成和修改Java类的字节码,而无需使用编译器。Byte Buddy主要用于创建和操作Java字节码,但也可以用于创建任意类,并且不限于实现用于创建运行时代理的接口。

Byte Buddy提供了一种方便的API,使用户可以使用Java代理或在构建过程中手动更改类。Byte Buddy还支持检测和修改现有Java字节码的工具,例如自动编码、检测代码生成实用程序、转换现有代码等。

Byte Buddy的流式API允许用户从Byte Buddy实例出发,完成所有的操作和数据定义。此外,Byte Buddy不仅限于创建子类和操作类,还可以转换现有代码。Byte Buddy还提供了一个方便的API,用于定义所谓的 Java 代理,该代理允许在任何 Java 应用程序的运行期间进行代码转换。

总之,Byte Buddy是一个强大的工具,可以方便地操作和生成Java字节码,从而简化Java开发过程。

各类增强工具对比

性能对比

类的生成策略面临一个权衡,Byte Buddy的主要重点在于以最少的运行时间生成代码。

这是面对类生成,接口实现等行为的不同增强工具的性能对比,总体看起来bytebuddy的性能没有特殊的地方,没有恶化。

优缺点

  • java proxy6

    Java类库附带一个代理工具包,该工具包允许创建实现一组给定接口的类。这个内置的代理使用很方便,但功能非常有限。比如代理只能面对一个已经存在的接口,

    但是对类进行扩展的时候,proxy办不到

  • cglib

    太早了,没人维护了。

    该代码生成库是在最初几年的Java实现,它也不幸的是没有与Java平台的发展跟上。尽管如此,cglib仍然是一个功能非常强大的库,但是它的积极开发却变得相当模糊。因此,它的许多用户都离开了cglib。

  • javassist

    自带了一个相比javac弱小编译器,而且动态生成字节码时容易出错。

    该库带有一个编译器,该编译器采用包含Java源代码的字符串,这些字符串在应用程序运行时会转换为Java字节代码。因为Java源代码显然是描述Java类的一种很好的方法,所以这是非常雄心勃勃的,并且从原则上讲是个好主意。

    但是,Javassist编译器在功能上无法与javac编译器相提并论,并且在动态组成字符串以实现更复杂的逻辑时,容易出错。此外,Javassist附带了一个代理库,该代理库与JCL的代理实用程序相似,但是允许扩展类,并且不限于接口。

创造一个类

代码初体验

在进行Byte Buddy之前先创建一个项目,然后引入相关依赖:

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

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

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

测试类如下:

@Slf4j
public class CreateClassTest {
    private static String path;

    /**
     * 每个测试方法执行前获取CreateClassTest的路径
     */
    @BeforeAll
    private static void before(){
        path = CreateClassTest.class.getClassLoader().getResource("").getPath();
        System.out.println(path);

    }
    /**
     * 生成一个类
     */
    @Test
    public void create() throws IOException {
        //Unloaded代表生成字节码还未加载到jvm
        DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
            //指定父类
            .subclass(Object.class)
            .make();
        //获取生成类的字节码
        byte[] bytes = unloaded.getBytes();
    }
}
  • DynamicType:在运行时创建的动态类型,通常作为应用DynamicType.Builder或.AuxiliaryType的结果。
  • Unloaded:尚未被给定ClassLoader加载的动态类型。

    interface Unloaded<T> extends DynamicType

    类型参数: -由该动态类型实现的最具体的已知加载类型,通常是类型本身、接口或直接的超类。

  • ByteBuddy:在创建时,Byte Buddy会尝试发现当前JVM的版本。如果不可能,则创建与Java 6兼容的类文件。
  • subclass:用于创建所提供类型的子类,如果提供的类型是接口,则创建实现此接口类型的新类。

    当扩展一个类时,Byte Buddy模仿子类类型的所有可见构造函数。任何构造函数都被实现为只调用其具有相同签名的超类型构造函数。另一种行为可以通过subclass(Class, ConstructorStrategy)提供显式的ConstructorStrategy来指定。 注意:如果所提供的类型声明了类型变量或所有者类型,则此方法以泛型状态实现它们。

    /**
     * 通过构造器策略生成一个类
     */
    @Test
    public void createByConstructorStrategy() throws IOException {
        //Unloaded代表生成字节码还未加载到jvm
        DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
            //指定父类
            .subclass(CreateByConstructorStrategyEntity.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
            .make();
        //获取生成类的字节码
        byte[] bytes = unloaded.getBytes();
    }

实例化

@Test
public void create() throws Exception {
    //Unloaded代表生成字节码还未加载到jvm
    DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
        //指定父类
        .subclass(Object.class)
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method method = loaded.getMethod("toString");
    System.out.println(method.invoke(loaded.newInstance()));
}

构造器策略

  • NO_CONSTRUCTORS:此策略不添加构造函数
  • DEFAULT_CONSTRUCTOR:这个策略是添加一个默认构造函数来调用它的超类型默认构造函数。
  • IMITATE_SUPER_CLASS:这种策略是添加插装类型的超类的所有构造函数,其中每个构造函数都直接调用其签名等效的超类构造函数。
  • IMITATE_SUPER_CLASS_PUBLIC:这种策略是添加插装类型的超类的所有构造函数,其中每个构造函数都直接调用其签名等效的超类构造函数,只添加公共构造函数。
  • IMITATE_SUPER_CLASS_OPENING:这种策略是添加插装类型的超类的所有构造函数,其中每个构造函数都直接调用其签名等效的超类构造函数,为超类的任何可调用且声明为public的构造函数添加构造函数。

上面是Byte Buddy注释的直译,具体的一些解释会在下面测试中解释。

为了测试以上策略创建一个类作为生成类得超类,如下:

public class CreateByConstructorStrategyEntity {
    private Long id;
    private String name;
    private String content;

    public CreateByConstructorStrategyEntity() {
    }

    protected CreateByConstructorStrategyEntity(Long id) {
        this.id = id;
    }

    private CreateByConstructorStrategyEntity(String name) {
        this.name = name;
    }

    public CreateByConstructorStrategyEntity(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public CreateByConstructorStrategyEntity(Long id, String name, String content) {
        this.id = id;
        this.name = name;
        this.content = content;
    }
}

然后用用上面五种策略分别生成新类,并保存到文件中

@Test
public void createByConstructorStrategy() throws IOException {
    //Unloaded代表生成字节码还未加载到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //指定父类
        .subclass(CreateByConstructorStrategyEntity.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
        .make();
    unloaded.saveIn(new File(path));
}
  • NO_CONSTRUCTORS:不生成构造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$3c1yosow extends CreateByConstructorStrategyEntity {
    }
  • DEFAULT_CONSTRUCTOR:给一个默认构造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$sEqzBgYH extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$sEqzBgYH() {
        }
    }
  • IMITATE_SUPER_CLASS:添加超类所有可访问的构造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1, String var2) {
            super(var1, var2);
        }
    
        protected CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1) {
            super(var1);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k() {
        }
    }
  • IMITATE_SUPER_CLASS_PUBLIC:添加超类所有Public修饰的构造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS(Long var1, String var2) {
            super(var1, var2);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS() {
        }
    }
    
  • IMITATE_SUPER_CLASS_OPENING:添加超类所有可访问的构造器并转换为Public

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1, String var2) {
            super(var1, var2);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1) {
            super(var1);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn() {
        }
    }

文件保存

生成类字节码可以进行保存unloaded.saveIn(new File(path));,也就是将你生成的字节码以.class文件保存到相应的位置,但,继承jdk原生类和继承自定义类的保存位置不同。

  • 继承jdk原生类,文件会被保存到net.bytebuddy.renamed路径下然后加超类的路径

    net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i0ivrOhL
  • 继承用户自定义类,文件被保存和生成类同级路径下

    com.eacape.bytebuddy.CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS

命名策略

public void createWithNameStrategy() throws IOException {
    NamingStrategy.SuffixingRandom suffixingRandom = new NamingStrategy.SuffixingRandom("eacape");
    //Unloaded代表生成字节码还未加载到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(suffixingRandom)
        //指定父类
        .subclass(CreateByConstructorStrategyEntity.class)
        .make();
    unloaded.saveIn(new File(path));
}

生成的类名为CreateByConstructorStrategyEntity$eacape$OnHkVvgk,如果不指定生成策略则生成的类名如下

$$ 超类类名 + ByteBuddy + 8位随机字符 $$

增加SuffixingRandom则自定义的后缀将替换ByteBuddy,如果将命名策略改为PrefixingRandom则命名结果如下:

也可以直接指定类名

@Test
public void createWithName() throws IOException {
    //Unloaded代表生成字节码还未加载到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //指定父类
        .subclass(CreateByConstructorStrategyEntity.class)
        .name("cn.baidu.NewCreateByConstructorStrategyEntity")
        .make();
    unloaded.saveIn(new File(path));
}

自定义命名策略

@Test
public void createWithSelfDefineNameStrategy() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //使用自定义命名策略
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .subclass(CreateByConstructorStrategyEntity.class)
        .make();
    String canonicalName = unloaded.load(this.getClass().getClassLoader()).getLoaded().getCanonicalName();
    System.out.println(canonicalName);
}
//结果
top.eacape.CreateByConstructorStrategyEntity

字节码注入

也可以将字节码注入到已有的jar中,代码和操作结果如下

@Test
public void createWithInjectJar() throws IOException {
    //Unloaded代表生成字节码还未加载到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //是否检验生成字节码的正确性
        .with(TypeValidation.of(true))
        //指定父类
        .subclass(CreateByConstructorStrategyEntity.class)
        .name("cn.baidu.NewCreateByConstructorStrategyEntity")
        .make();
    unloaded.inject(new File("E:\\xxx\\arthas-demo.jar"));
}

类加载策略

DynamicType.Unloaded,代表一个尚未加载的类,顾名思义,这些类型不会加载到 Java 虚拟机中,它仅仅表示创建好了类的字节码,通过 DynamicType.Unloaded 中的 getBytes 方法你可以获取到该字节码。

在应用程序中,可能需要将该字节码保存到文件,或者注入的现在的 jar 文件中,因此该类型还提供了一个 saveIn(File) 方法,可以将类存储在给定的文件夹中; inject(File) 方法将类注入到现有的 Jar 文件中,另外你只需要将该字节码直接加载到虚拟机使用,你可以通过 ClassLoadingStrategy 来加载。

如果不指定ClassLoadingStrategy,Byte Buffer根据你提供的ClassLoader来推导出一个策略,内置的策略定义在枚举ClassLoadingStrategy.Default中

  • WRAPPER:创建一个新的Wrapping类加载器
  • CHILD_FIRST:类似上面,但是子加载器优先负责加载目标类
  • INJECTION:利用反射机制注入动态类型
 Class<?> dynamicClass = dynamicType
                .load(Object.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

我们使用 WRAPPER 策略来加载适合大多数情况的类,这样生产的动态类不会被ApplicationClassLoader加载到,不会影响到项目中已经存在的类getLoaded 方法返回一个 Java Class 的实例,它就表示现在加载的动态类

增强一个类

修改方法

@Test
public void methodFixedValue() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .subclass(CreateByConstructorStrategyEntity.class)
        //对toString方法进行拦截
        .method(named("toString"))
        //返回固定值
        .intercept(FixedValue.value("Hello ByteBuddy"))
        .make();
    unloaded.saveIn(new File(path));
}

插入方法

重新创建一个新的实体类用于测试

public class BaseEntity {
}
@Test
public void insertMethod() throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .defineMethod("init", String.class, Modifier.PUBLIC)
        .withParameters(Integer.class, String.class)
        .intercept(FixedValue.value("my return value"))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method init = loaded.getMethod("init", Integer.class, String.class);
    String result = (String) init.invoke(loaded.newInstance(), null, null);
    System.out.println(result);
}

// 输出结果 my return value

插入属性

@Test
public void insertField() throws IOException {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .rebase(BaseEntity.class)
        .defineField("age", Integer.class, Modifier.PRIVATE)
        .make();
    unloaded.saveIn(new File(path));
}

增强类

增强一个类有两种方式redefine&rebase,与subclass不同的是这两种方式是在原有类的基础上修改,而subclass是生成一个子类。

  • rebase:会保留所有被变基类的方法实现。Byte Buddy 会用兼容的签名复制所有方法的实现为一个私有的重命名过的方法, 而不像类重定义时丢弃覆写的方法。用这种方式的话,不存在方法实现的丢失,而且变基的方法可以通过调用这些重命名的方法, 继续调用原始的方法。
  • redifine:允许通过添加字段和方法或者替换已存在的方法实现来修改已存在的类。 但是,如果方法的实现被另一个实现所替换,之前的实现就会丢失。

使用rebase将之前测试的实体类增加一个getId方法并返回固定值0

public class CreateByConstructorStrategyEntity {
    .....
    public Long getId() {
        return 0L;
    }
    .....
}

然后以rebase的方式将其修改,使getId方法的返回值修改

@Test
public void dynamicEnhanceByRebase() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .rebase(CreateByConstructorStrategyEntity.class)
        .method(ElementMatchers.<MethodDescription>named("getId"))
        .intercept(FixedValue.value(50L))
        .make();
    unloaded.saveIn(new File(path));
}

修改结果如下,getId的返回值已经由0变为50

用idea直接查看没有什么问题,但是查看字节码发现这个文件中还有一个以getId开头的方法,它的返回值为0,代表getId的原始方法

反观使用redefine就没有这种效果

@Test
public void dynamicEnhanceByRedefine() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .redefine(CreateByConstructorStrategyEntity.class)
        .method(ElementMatchers.<MethodDescription>named("getId"))
        .intercept(FixedValue.value(50L))
        .make();
    unloaded.saveIn(new File(path));
}

匹配方法

如上Byte Buddy提供了各种匹配方法,可以让我们像sql中的where条件一样去灵活的匹配各种方法

@Test
public void dynamicEnhanceMatchMethod() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .redefine(CreateByConstructorStrategyEntity.class)
        //拦截方法名为getName的方法或者方法名含有Content并且返回值为String类型的方法
        .method(ElementMatchers.<MethodDescription>named("getName")
            .or(
               nameContains("Content")
                    .and(returns(String.class))
            )
        )
        .intercept(FixedValue.value("Hello"))
        .make();
    unloaded.saveIn(new File(path));
}

委托方法

在大多数情况下,方法返回一个固定值当然是不够的。为了更好的灵活性,Byte Buddy 提供了MethodDelegation(方法委托)实现, 它在对方法调用做出反应时提供最大程度的自由。一个方法委托定义了动态创建的类方法,到另外一个可能存在于动态类型之外的方法的任何调用。 这样,动态类的逻辑可以用简单的 Java 表示,仅通过代码生成就可以与另外的方法绑定。

实现委托方法有两种,一种是静态方法委托,一种是成员方法委托,具体代码如下

public class BaseEntity {
    public void a(Long id){
        System.out.println("hello");
    }

    public void b(Long id){
        System.out.println("hello");
    }
}
public class BaseEntityInterceptor {
    public static void ADelegation(Long id) {
        System.out.println(id + " name is " + UUID.randomUUID());
    }

    public void BDelegation(Long id) {
        System.out.println(id + " name is " + UUID.randomUUID());
    }
}
@Test
public void methodDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        //对a方法进行静态方法委托增强
        .method(named("a"))
        .intercept(MethodDelegation.to(BaseEntityInterceptor.class))
        //对b方法进行成员方法委托增强
        .method(named("b"))
        .intercept(MethodDelegation.to(new BaseEntityInterceptor()))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("a", Long.class);
    a.invoke(loaded.newInstance(),2L);
    Method b = loaded.getMethod("b", Long.class);
    b.invoke(loaded.newInstance(),4L);
}

//结果如下
2 name is b075a124-072a-4163-bf2d-130eb299f96c
4 name is 08e08e11-9698-4964-a3b0-2720b0162a4b

上面的委托方式看起来还是不够灵活,日常最常使用的委托方式是通过注解进行参数绑定。

注解说明
@Argument绑定单个参数
@AllArguments绑定所有参数的数组
@This当前被拦截的、动态生成的那个对象
@Super当前被拦截的、动态生成的那个对象,不会继承原有的类
@Origin可以绑定到以下类型的参数: - Method 被调用的原始方法 - Constructor 被调用的原始构造器 - Class 当前动态创建的类 - MethodHandleMethodTypeString 动态类的toString()的返回值 - int 动态方法的修饰符
@DefaultCall调用默认方法而非super的方法
@SuperCall用于调用父类版本的方法
@RuntimeType可以用在返回值、参数上,提示ByteBuddy禁用严格的类型检查
@Empty注入参数的类型的默认值
@StubValue注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0
@FieldValue注入被拦截对象的一个字段的值
@Morph类似于@SuperCall,但是允许指定调用参数

下面使用以上注解对BaseEntity.a进行简单测试,另外当注解这种方式和之前的委托方法同时存在时,会优先选择前者。

public class BaseEntityInterceptor {
//    public static void ADelegation(Long id) {
//        System.out.println(id + " name is " + UUID.randomUUID());
//    }
//
//    public void BDelegation(Long id) {
//        System.out.println(id + " name is " + UUID.randomUUID());
//    }

    @RuntimeType
    public void runtimeAspect(
        @This Object targetObject, 
        @Super Object superObject,
        @Origin Method originMethod,
        @AllArguments Object[] args,
        @SuperCall Callable<?> call
    ) {
        System.out.println(targetObject);
        System.out.println(superObject);
        System.out.println(originMethod);
        System.out.println(Arrays.toString(args));
        try {
            long start = System.currentTimeMillis();
            call.call();
            System.out.println(System.currentTimeMillis() - start + "ms");
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}
/**
 *方法委托
 */
@Test
public void methodDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        //对a方法进行静态方法委托增强
        .method(named("a"))
        .intercept(MethodDelegation.to(new BaseEntityInterceptor()))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("a", Long.class);
    a.invoke(loaded.newInstance(),4L);
}
// 结果
com.eacape.bytebuddy.BaseEntity$ByteBuddy$r0NYPVQb@2fc0cc3
com.eacape.bytebuddy.BaseEntity$ByteBuddy$r0NYPVQb@2fc0cc3
public void com.eacape.bytebuddy.BaseEntity.a(java.lang.Long)
[4]
hello
0ms

前面示例中,使用 @SuperCall 注解注入的 Callable 参数来调用目标方法时,是无法动态修改参数的,如果想要动态修改参数,则需要用到 @Morph 注解以及一些绑定操作,示例如下:

@Test
public void methodDelegationByMorph() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .method(named("getId"))
        .intercept(MethodDelegation
            .withDefaultConfiguration()
            // 要用@Morph注解之前,需要通过 Morph.Binder 告诉 Byte Buddy
            // 要注入的参数是什么类型
            .withBinders(Morph.Binder.install(MorphCallable.class))
            .to(new BaseEntityInterceptor())
        )
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("getId", Long.class);
    a.invoke(loaded.newInstance(), 100L);
}

这里的 Interceptor 会使用 @Morph 注解注入一个 MorphCallable 对象作为参数,然后通过该 MorphCallable对象调用目标方法,如下所示:

@RuntimeType
public void morphAspect(
    @This Object target,
    @Super Object superO,
    @Origin Method method,
    @AllArguments Object[] args,
    @Morph MorphCallable call
) {
    try {
        long start = System.currentTimeMillis();
        if (superO instanceof BaseEntity && "getId".equals(method.getName())){
            args[0] = (Long)args[0] + 1;
        }
        Object res = call.call(args);
        System.out.println(res);
        System.out.println(System.currentTimeMillis() - start + "ms");
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}
public class BaseEntity {
    public Long getId(Long id){
        System.out.println(id);
        return 308843 + id;
    }
}
public interface MorphCallable {

    /**
     * Callable.
     * @Description 调用方法
     * @param args the args
     * @return object
     */
    Object call(Object[] args);
}

输出结果为

100
308943
0ms

构造器方法

public class BaseEntity {

    public BaseEntity() {
        System.out.println("BaseEntity实例化成功");
    }
}
@RuntimeType
public void constructorAspect(
    @This Object target
) {
    System.out.println(target + ":实例化后置操作");
}
@Test
public void constructorDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .constructor(any())
        .intercept(
            //在构造器方法执行完成之后进行拦截
            SuperMethodCall.INSTANCE.andThen(
                MethodDelegation.to(new BaseEntityInterceptor())
            )
        )
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    loaded.newInstance();
}
//结果
BaseEntity实例化成功
com.eacape.bytebuddy.BaseEntity$ByteBuddy$8116eKW8@1613674b:实例化后置操作

参考

https://bytebuddy.net/#/tutorial

https://blog.csdn.net/wanxiaoderen/article/details/106544773

https://www.bilibili.com/video/BV1G24y1a7bd


eacape
205 声望8 粉丝

JAVA 攻城狮