java–字节码增强–1.2–ByteBuddy–使用


1、快速开始

1.1、依赖

使用bytebuddy 需要引入依赖

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

1.2、代码

package fei.zhou.demo1.business.ByteBuddy2.demo1;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

public class Test01 {


    public static void main(String[] args) throws Exception {
        //创建 class对象
        Class<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                // 指定生成类的名称
                .name("fei.zhou.demo1.business.ByteBuddy2.User")
                // 按名称 拦截该类的 toString()
                // ElementMatchers.named("toString") : 拦截条件,拦截toString()这个方法, 没有条件,表示所有的方法
                .method(ElementMatchers.named("toString"))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("Hello World"))
                //生成字节码,也就是 动态类型
                .make()
                //设置类加载器,加载这个生成的类
                .load(Test01.class.getClassLoader())
                //  获得class对象
                .getLoaded();
        //Java 反射的API,创建实例
        Object instance = dynamicType.newInstance();
        //执行toString 方法
        String toString = instance.toString();
        //打印 toString 结果
        System.out.println(toString);
        System.out.println(instance.getClass().getCanonicalName());
    }
}

在这里插入图片描述

2、DynamicType.Unloaded

2.1、案例

package fei.zhou.demo1.business.ByteBuddy2.demo2;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

public class Test01 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                //定义类名称
                .name("fei.zhou.demo1.business.ByteBuddy2.demo2.DynamicType")
                //增加了两个字段name和age
                .defineField("name", String.class, 1)
                .defineField("age", Integer.class, 1)
                // 按名称 拦截该类的 toString()
                .method(ElementMatchers.named("toString"))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("Hello World!"))
                //生成字节码,也就是 动态类型
                .make();
    }
}

上面的示例代码的作用

  1. 会创建一个继承至 Object类型的类。
  2. 类名称:fei.zhou.demo1.business.ByteBuddy2.demo2.DynamicType
  3. 定义2个字段:
    1. name:String类型,默认值1
    2. age:Integer类型,默认值1

和下面这个类等效

package fei.zhou.demo1.business.ByteBuddy2.demo2;

public class DynamicType {
    String name = "1";
    Integer age = 1;

    @Override
    public String toString() {
        return "Hello World!";
    }
}

2.2、DynamicType.Unloaded<?> 介绍

  1. 上节创建的 DynamicType.Unloaded,代表一个尚未加载的类,顾名思义,改类型不会加载到 Java 虚拟机中,它仅仅表示 类的字节码。
  2. 如果需要将该字节码直接加载到虚拟机使用,你可以通过 ClassLoadingStrategy 来加载。

2.3、DynamicType.Unloaded<?> 常用方法

  1. getBytes 方法:可以获取到 class文件的字节码。
  2. saveIn(File) 方法:在File目录,生成一个class文件
  3. inject(File) 方法:将类注入到现有的 Jar 文件中

2.3.1、getBytes 方法


package fei.zhou.demo1.business.ByteBuddy2.demo2;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

import java.io.File;

public class Test03 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                //定义类名称
                .name("fei.zhou.DynamicType2")
                //增加了两个字段name和age
                .defineField("name", String.class, 1)
                .defineField("age", Integer.class, 1)
                // 按名称 拦截该类的 toString()
                .method(ElementMatchers.named("toString"))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("Hello World!"))
                //生成字节码,也就是 动态类型
                .make();


        // 可以获取到字节码,class文件的字节码
        byte[] bytes = dynamicType.getBytes();


    }
}

2.3.2、saveIn(File) 方法

package fei.zhou.demo1.business.ByteBuddy2.demo2;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

import java.io.File;

public class Test02 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                //定义类名称
                .name("fei.zhou.DynamicType2")
                //增加了两个字段name和age
                .defineField("name", String.class, 1)
                .defineField("age", Integer.class, 1)
                // 按名称 拦截该类的 toString()
                .method(ElementMatchers.named("toString"))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("Hello World!"))
                //生成字节码,也就是 动态类型
                .make();


        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);


    }
}

在这里插入图片描述
在这里插入图片描述

2.3.3、inject(File) 方法


package fei.zhou.demo1.business.ByteBuddy2.demo2;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

import java.io.File;

public class Test04 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                //定义类名称
                .name("fei.zhou.DynamicType2")
                //增加了两个字段name和age
                .defineField("name", String.class, 1)
                .defineField("age", Integer.class, 1)
                // 按名称 拦截该类的 toString()
                .method(ElementMatchers.named("toString"))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("Hello World!"))
                //生成字节码,也就是 动态类型
                .make();

        // 保存到当前目录
        File file = new File("D:\\temp\\demo1-1.1.jar");
        // 将类注入到现有的 Jar 文件中
        dynamicType.inject(file);


    }
}

在这里插入图片描述

3、ByteBuddy 三种字节码增强方式

  1. subclass
  2. rebasing
  3. redefinition

3.1、subclass

  1. 对应 ByteBuddy.subclass() 方法。
  2. 为目标类(即被增强的类)生成一个子类,在子类方法中插入动态代码。

在这里插入图片描述

3.2、rebasing

  1. 对应 ByteBuddy.rebasing() 方法。
  2. 当使用 rebasing 方式增强一个类时,Byte Buddy 保存目标类中所有方法的实现,也就是说,当 Byte Buddy 遇到冲突的字段或方法时,会将原来的字段或方法实现复制到 具有兼容签名的重新命名的私有方法中,而不会抛弃这些字段和方法实现。从而达到不丢失实现的目的。
  3. 这些重命名的方法 可以继续通过重命名后的名称进行调用。
class Foo { // Foo的原始定义
  String bar() { return "bar"; }
}

class Foo { // 增强后的Foo定义
  String bar() { return "foo" + bar$original(); }
  // 目标类原有方法
  private String bar$original() { return "bar"; }
}  

3.3、redefinition

  1. 对应 ByteBuddy.redefine() 方法。
  2. 当重定义一个类时,Byte Buddy 可以对一个已有的类添加属性和方法,删除已经存在的方法实现。如果使用其他的方法实现, 去替换已经存在的方法实现,则原来存在的方法实现就会消失。
举例

这里依然是增强 Foo 类的 bar() 方法使其直接返回 “unknow” 字符串,增强结果如下

class Foo { // 增强后的Foo定义
  String bar() { return "unknow"; }
}


4、类加载策略

4.1、ClassLoadingStrategy.Default

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

4.1.1、内置策略如下

WRAPPER 策略:
	1. 创建一个新的 ClassLoader 来加载动态生成的类型。
	2. 适合大多数情况,这样生产的动态类不会被ApplicationClassLoader加载到,不会影响到项目中已经存在的类。
CHILD_FIRST 策略:创建一个子类优先加载的 ClassLoader,即打破了双亲委派模型。
INJECTION 策略:使用反射,将动态生成的类型直接注入到当前 ClassLoader 中。

4.2、案例

package fei.zhou.demo1.business.ByteBuddy2.demo3;

import fei.zhou.demo1.business.ByteBuddy2.demo2.Test02;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;

public class Test01 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Object.class)
                //定义类名称
                .name("User")
                //生成字节码,也就是 动态类型
                .make();


        Class<?> classObject = dynamicType.load(Test02.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                //  获得class对象
                .getLoaded();


    }
}

5、动态定义字节码字段,方法和参数

package fei.zhou.demo1.business.ByteBuddy2.demo4;
 
public class Foo {
}

package fei.zhou.demo1.business.ByteBuddy2.demo4;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;

import java.io.File;
import java.lang.reflect.Modifier;

public class Test01 {


    public static void main(String[] args) throws Exception {
        DynamicType.Unloaded<Foo> dynamicType = new ByteBuddy()
                // 指定父类
                .subclass(Foo.class)
                //定义类名称
                .name("FooSon")

                //定义方法;名称、返回类型、属性public static void
                .defineMethod("say", String.class, Modifier.PUBLIC)
                //定义参数;参数类型、参数名称
                .withParameter(String.class, "sayStr")
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("hello world"))
                // 新增一个字段,该字段名称成为"name",类型是 String,且public修饰
                .defineField("name", String.class, Modifier.PUBLIC)
                .defineField("age", Integer.class, Modifier.PRIVATE)
                // 产生字节码
                .make();


        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);


    }
}

6、委托函数调用

  1. 让我们使用字节码编程创建的方法,去调用另外一个方法
  2. 使用MethodDelegation可以将方法调用委托给任意POJO。
  3. Byte Buddy不要求Source(被委托类)、Target类的方法名一致
# 在intercept方法中,使用 MethodDelegation.to 委托到 DelegateClazz的静态方法
intercept(MethodDelegation.to(DelegateClazz.class))  

# 在intercept方法中,使用 MethodDelegation.to 委托到 DelegateClazz的 成员方法
intercept(MethodDelegation.to(new DelegateClazz())  

6.1、静态方法

 public class Foo {
    public static String getInfo() {
        return "Foo";
    }
}

package fei.zhou.demo1.business.ByteBuddy2.demo5;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;

import java.io.File;
import java.lang.reflect.Modifier;

public class Test01 {


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

        DynamicType.Unloaded<Foo> dynamicType = new ByteBuddy()
                .subclass(Foo.class)
                .name("User4")
                //定义方法getInfo2
                .defineMethod("getInfo2", String.class, Modifier.PUBLIC + Modifier.STATIC)
                //调用的方法,使用MethodDelegation进行委托
                .intercept(MethodDelegation.to(Foo.class))
                .make();


        // 动态增强生成的字节码类
        System.out.println("生成的class名称:" + dynamicType.load(ByteBuddy.class.getClassLoader()).getLoaded().getName());


        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);


    }
}

在这里插入图片描述

6.2、动态方法

package fei.zhou.demo1.business.ByteBuddy2.demo5;

public class Foo2 {
    public String getInfo() {
        return "Foo2";
    }
}
package fei.zhou.demo1.business.ByteBuddy2.demo5;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;

import java.io.File;
import java.lang.reflect.Modifier;

public class Test02 {


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


        Foo2 foo2 = new Foo2();

        DynamicType.Unloaded<Foo2> dynamicType = new ByteBuddy()
                .subclass(Foo2.class)
                .name("User5")
                //定义方法getInfo2
                .defineMethod("getInfo2", String.class, Modifier.PUBLIC + Modifier.STATIC)
                //调用的方法,使用MethodDelegation进行委托
                .intercept(MethodDelegation.to(new Foo2()))
                .make();

        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);


        Class<? extends Foo2> peopleClass = dynamicType.load(ByteBuddy.class.getClassLoader()).getLoaded();

        Object result = peopleClass.getMethod("getInfo").invoke(peopleClass.newInstance());
        System.out.println("结果:" + result);
    }

}

输出:

结果:Foo2

在这里插入图片描述

6.3、委托原则

遵循一个最接近原则

6.3.1、案例

public class Source {
    public String hello(String name) {
        return "Source " + name;
    }
}

package fei.zhou.demo1.business.ByteBuddy2.demo6;
 
public class Target {

    public static String say(String name) {
        return "Target " + name + "!";
    }

    public static String say(int i) {
        return Integer.toString(i);
    }

    public static String say(Object o) {
        return o.toString();
    }

}
package fei.zhou.demo1.business.ByteBuddy2.demo6;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;

import java.io.File;
import java.lang.reflect.Method;

import static net.bytebuddy.matcher.ElementMatchers.named;

public class Test05 {


    public static void main(String[] args) throws Exception {
        //创建了一个动态类型
        DynamicType.Unloaded<Source> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Source.class)
                .name("Source2")
                // 按名称 拦截该类的 hello()
                .method(named("hello"))
                //调用的方法,使用MethodDelegation进行委托
                .intercept(MethodDelegation.to(Target.class))
                //生成字节码,由类加载器加载到虚拟机中
                .make();

        Class<? extends Source> source2Class = dynamicType.load(ByteBuddy.class.getClassLoader()).getLoaded();
        Method helloMethod = source2Class.getMethod("hello", String.class);
        Object result = helloMethod.invoke(source2Class.newInstance(), "我是");
        System.out.println("结果:" + result);

        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);


    }
}

输出:
结果:Target 我是!

在这里插入图片描述

6.3.2、说明

Byte Buddy遵循一个最接近原则:

  • say(int)因为参数类型不匹配,直接Pass
  • 另外两个方法参数都匹配,但是 say(String)类型更加接近,因此会委托给它

java–字节码增强–1.2–ByteBuddy–使用


7、拦截方法

package fei.zhou.demo1.business.ByteBuddy2.demo7;

 
public class Foo {
    public String foo1() {
        return "foo1";
    }

    public String foo2(int i) {
        return "foo2";
    }

    public String foo3(int i) {
        return "foo3";
    }

    public String foo3(Object i,String s) {
        return "foo4";
    }
}
package fei.zhou.demo1.business.ByteBuddy2.demo7;

import fei.zhou.demo1.business.ByteBuddy2.demo6.Source;
import fei.zhou.demo1.business.ByteBuddy2.demo6.Target;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;

import java.io.File;

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class Test01 {

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

        //创建了一个动态类型
        DynamicType.Unloaded<Foo> dynamicType = new ByteBuddy()
                //指定了新创建类的父类
                .subclass(Foo.class)
                .name("Foo2")
                // 拦截  由Foo.class声明的 所有方法
                .method(isDeclaredBy(Foo.class))
                // 指定了拦截到的方法要修改成什么样子
                .intercept(FixedValue.value("1111"))
                // 拦截  为foo3,入参数量为1的方法
                .method(named("foo3").and(takesArguments(1))).intercept(FixedValue.value("2222"))
                //生成字节码
                .make();


        // 保存到当前目录
        File file = new File("D:\\temp");
        // 通过dynamicType 将字节码保存到文件
        dynamicType.saveIn(file);
    }
}

在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐