ByteBuddy简介与使用

1、ByteBuddy简介

Byte Buddy 是一个代码(字节码)生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还允许创建任意类,并且不限于实现用于创建运行时代理的接口。

此外,Byte Buddy 提供了一种方便的 API,可以使用 Java 代理或在构建过程中手动更改类。

无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。

比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。
2015年10月,Byte Buddy被 Oracle 授予了 Duke’s Choice大奖。

该奖项对Byte Buddy的“ Java技术方面的巨大创新 ”表示赞赏。我们为获得此奖项感到非常荣幸,并感谢所有帮助Byte Buddy取得成功的用户以及其他所有人。我们真的很感激!

除了这些简单的介绍外,还可以通过官网:https://bytebuddy.net,去了解更多关于 Byte Buddy 的内容。

2、入门案列

1、导入依赖

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>

2、编写测试示例

@Test
	public void HelloWorld() throws IllegalAccessException, InstantiationException {
		String s = new ByteBuddy()
				// 指定父类
				.subclass(Object.class)
				// 指定生成类的名称
				.name("User")
				// 按名称 拦截该类的 toString()
				.method(ElementMatchers.named("toString"))
				// 拦截方法调用 返回固定值
				.intercept(FixedValue.value("Hello World!!"))
				// 产生字节码
				.make()
				// classLoader 加载字节码到内存
				.load(ByteBuddy.class.getClassLoader())
				// 获得class对象
				.getLoaded()
				.newInstance()
				.toString();

		System.out.println(s);
	}

在这里插入图片描述
说明

  • subclass(Object.class) :创建一个Object的子类
  • name(“ExampleClass”) : 新建的类名叫做“ExampleClass” ,暂时没有用到
  • method() :要拦截“ExampleClass”中的方法
  • ElementMatchers.named(“toString”) :拦截条件,拦截toString()这个方法, 没有条件,表示所有的方法
  • intercept() :指定了拦截到的方法要修改成什么样子,是不是和 Spring AOP有点像了
  • make() :创建上面生成的这个类型
  • load() :加载这个生成的类
  • newInstance() :Java 反射的API,创建实例

来看下编译后的代码

在Byte buddy中默认提供了一个 dynamicType.saveIn() 方法,可以保存编译后的Class文件

@Test
	public void LookUpClassFile() throws IllegalAccessException, InstantiationException, IOException {
		DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
				// 指定父类
				.subclass(Object.class)
				// 指定生成类的名称
				.name("User")
				// 按名称 拦截该类的 toString()
				.method(ElementMatchers.named("toString"))
				// 拦截方法调用 返回固定值
				.intercept(FixedValue.value("Hello World!!"))
				// 产生字节码
				.make();

		DynamicType.Loaded<Object> loaded = dynamicType.load(ByteBuddy.class.getClassLoader());

		// 找到ByteBuddyTest 字节码所在的目录
		File file = new File(ByteBuddyTest.class.getResource("./").getPath());
		// 通过dynamicType 将字节码保存到文件
		dynamicType.saveIn(file);

		String s = loaded.getLoaded().newInstance().toString();
		System.out.println(s);

	}

在这里插入图片描述

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

三种字节码动态增强方式

  • subclass
    对应 ByteBuddy.subclass() 方法。这种方式比较好理解,就是为目标类(即被增强的类)生成一个子类,在子类方法中插入动态代码。
@Test
	public void HelloWorld() throws IllegalAccessException, InstantiationException {
		String s = new ByteBuddy()
				// 指定父类
				.subclass(Object.class)
				// 指定生成类的名称
				.name("User")
				// 按名称 拦截该类的 toString()
				.method(ElementMatchers.named("toString"))
				// 拦截方法调用 返回固定值
				.intercept(FixedValue.value("Hello World!!"))
				// 产生字节码
				.make()
				// classLoader 加载字节码到内存
				.load(ByteBuddy.class.getClassLoader())
				// 获得class对象
				.getLoaded()
				.newInstance()
				.toString();

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

class Foo { // 增强后的Foo定义
  String bar() { return "foo" + bar$original(); }
  // 目标类原有方法
  private String bar$original() { return "bar"; }
}  
  • redefinition
    对应 ByteBuddy.redefine() 方法。
    当重定义一个类时,Byte Buddy 可以对一个已有的类添加属性和方法,删除已经存在的方法实现。如果使用其他的方法实现, 去替换已经存在的方法实现,则原来存在的方法实现就会消失。例如,这里依然是增强 Foo 类的 bar() 方法使其直接返回 “unknow” 字符串,增强结果如下
class Foo { // 增强后的Foo定义
  String bar() { return "unknow"; }
}

4、类加载策略

DynamicType.Unloaded 对象,表示的是一个未加载的类型,通过在 ClassLoadingStrategy.Default中定义的加载策略,加载此类型。

Class<?> loaded = new ByteBuddy()
        .subclass(Object.class)
        .name("com.xxx.Type")
        .make()
        // 使用 WRAPPER 策略加载生成的动态类型
        .load(Main2.class.getClassLoader(), 
              ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();
  • WRAPPER 策略:创建一个新的 ClassLoader 来加载动态生成的类型。
  • CHILD_FIRST 策略:创建一个子类优先加载的 ClassLoader,即打破了双亲委派模型。
  • INJECTION 策略:使用反射, 将动态生成的类型直接注入到当前 ClassLoader 中。

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

@Test
	public void test4() throws IllegalAccessException, InstantiationException, IOException {
		DynamicType.Unloaded<Foo> dynamicType = new ByteBuddy()
				// 指定父类
				.subclass(Foo.class)
				.defineMethod("say", String.class, Modifier.PUBLIC)
				.withParameter(String.class,"s")
				.intercept(FixedValue.value("say lala"))
				// 新增一个字段,该字段名称成为"name",类型是 String,且public修饰
				.defineField("name", String.class, Modifier.PUBLIC)
				// 产生字节码
				.make();

		dynamicType.saveIn(new File(ByteBuddyTest.class.getResource("./").getPath()));

		// classLoader 加载字节码到内存
		String s = dynamicType.load(ByteBuddy.class.getClassLoader())
				// 获得class对象
				.getLoaded()
				.newInstance()
				.toString();

		System.out.println(s);
	}

在这里插入图片描述

  • defineMethod(“main”, String.class, Modifier.PUBLIC + Modifier.STATIC),
    定义方法;名称、返回类型、属性public static void

  • Modifier.PUBLIC + Modifier.STATIC,这是一个是二进制相加,每一个类型都在二进制中占有一位。例如 1 2 4 8 … 对应的二进制占位 1111。既可以执行相加运算,并又能保留原有单元的属性。

  • withParameter(String[].class, “args”),
    定义参数;参数类型、参数名称

  • intercept(FixedValue.value(“Hello World!”)),
    拦截设置返回值,但此时还能满足我们的要求。

6、委托函数调用

为了能让我们使用字节码编程创建的方法,去调用另外一个同名方法,那么这里需要使用到委托。

委托函数调用实例
通过 MethodDelegation 去完成

  • 在intercept方法中,使用MethodDelegation.to委托到静态方法
    intercept(MethodDelegation.to(DelegateClazz.class)) // 委托到 DelegateClazz 的静态方法

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

1、委托到静态方法

@Test
	public void delegateMethod() throws IOException, IllegalAccessException, InstantiationException {
		DynamicType.Unloaded<User> dynamicType = new ByteBuddy()
				.subclass(User.class)
				.name("com.xiaoxu.User")
				.defineMethod("getClassName", String.class, Modifier.PUBLIC + Modifier.STATIC)
				.intercept(MethodDelegation.to(User.class))
				.make();

		dynamicType.saveIn(new File(ByteBuddyTest.class.getResource("./").getPath()));

		System.out.println(User.class);
		System.out.println(User.getClassName());
		// 动态增强生成的字节码类
		System.out.println(dynamicType.load(ByteBuddy.class.getClassLoader()).getLoaded().getName());
	}

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

在这里插入图片描述

注意:

  • 被委托的方法,需要是 public 类
  • 被委托的方法与需要与原方法有着一样的入参、出参、方法名,否则不能映射上

2、动态委托调用

@Test
	public void dynamicDelegateMethod() throws IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		People people = new People();

		DynamicType.Unloaded<People> dynamicType = new ByteBuddy()
				.subclass(People.class)
				.name("com.xiaoxu.people")
				.make();

		dynamicType.saveIn(new File(ByteBuddyTest.class.getResource("./").getPath()));

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

		peopleClass.getMethod("say").invoke(peopleClass.newInstance());
	}

class 都拿到了,反射调用即可

7、ByteBuddy常用注解

通过上述 API 拦截方法并将方法实现委托给 Interceptor 增强之外,Byte Buddy 还提供了一些预定义的注解,

通过这些注解我们可以告诉 Byte Buddy 将哪些需要的数据注入到 Interceptor 中

  • @RuntimeType 注解:
    告诉 Byte Buddy 不要进行严格的参数类型检测,在参数匹配失败时,尝试使用类型转换方式(runtime type casting)进行类型转换,匹配相应方法。

  • @This 注解:
    注入被拦截的目标对象。

  • @AllArguments 注解:
    注入目标方法的全部参数,是不是感觉与 Java 反射的那套 API 有点类似了?

  • @Origin 注解:
    注入目标方法对应的 Method 对象。如果拦截的是字段的话,该注解应该标注到 Field 类型参数。

  • @Super 注解:
    注入目标对象。通过该对象可以调用目标对象的所有方法。

  • @SuperCall:
    这个注解比较特殊,我们要在 intercept() 方法中调用目标方法的话,需要通过这种方式注入,

  • @SuperCall与 Spring AOP 中的 ProceedingJoinPoint.proceed() 方法有点类似,需要注意的是,这里不能修改调用参数,从上面的示例的调用也能看出来,参数不用单独传递,都包含在其中了。

另外,@SuperCall 注解还可以修饰 Runnable 类型的参数,只不过目标方法的返回值就拿不到了。


  public   static  class DelegeteFoo {
        public String hello(String name) {
            System.out.println("DelegeteFoo:" + name);
            return null;
        }
    }


    public  static class Interceptor {
        @RuntimeType
        public Object intercept(
                @This Object obj, // 目标对象
                @AllArguments Object[] allArguments, // 注入目标方法的全部参数
                @SuperCall Callable<?> zuper, // 调用目标方法,必不可少哦
                @Origin Method method, // 目标方法
                @Super DelegeteFoo delegeteFoo // 目标对象
        ) throws Exception {
            System.out.println("obj="+obj);
            System.out.println("delegeteFoo ="+ delegeteFoo);
            // 从上面两行输出可以看出,obj和db是一个对象
            try {
                return zuper.call(); // 调用目标方法
            } finally {
            }
        }

    }


@Test
    public void annotateDelegateTest() throws IllegalAccessException, InstantiationException {

        DynamicType.Unloaded<DelegeteFoo> dynamicType = new ByteBuddy()
                .subclass(DelegeteFoo.class)
                .name("com.xiaoxu.Foo")
                .method(named("hello"))
                .intercept(MethodDelegation.to(new Interceptor()))
                .make();

        // 加载字节码
        DynamicType.Loaded<DelegeteFoo> type = dynamicType.load(getClass().getClassLoader());

        // 输出类字节码
        outputClazz(dynamicType.getBytes(), "com.crazymaker.circle.bytecode.enhancement.Foo");


        //加载类
        Class<?> clazz = type.getLoaded();

        // 反射调用
        try {
            String bar = (String) clazz.getMethod("hello",String.class).invoke(clazz.newInstance(),"bar");
            System.out.println(bar);

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

8、字节码增强技术性能比较

在选择字节码操作库时,往往需要考虑库本身的性能。对于许多应用程序,生成代码的运行时特性更有可能确定最佳选择。而在生成的代码本身的运行时间之外,用于创建动态类的运行时也是一个问题。官网对库进行了性能测试,给出以下结果图:

在这里插入图片描述

参考文章 https://blog.csdn.net/crazymakercircle/article/details/126579528

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鸽呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值