一、前言
相对于小傅哥之前编写的字节码编程; ASM、Javassist 系列,Byte Buddy 玩法上更加高级,你可以完全不需要了解一个类和方法块是如何通过 指令码 LDC、LOAD、STORE、IRETURN... 生成出来的。就像它的官网介绍;
Byte Buddy 是一个代码生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy 提供了一种方便的 API,可以使用 Java 代理或在构建过程中手动更改类。
- 无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。
- 已支持Java 11,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
- 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。
2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大奖。该奖项对Byte Buddy的“ Java技术方面的巨大创新 ”表示赞赏。我们为获得此奖项感到非常荣幸,并感谢所有帮助Byte Buddy取得成功的用户以及其他所有人。我们真的很感激!
除了这些简单的介绍外,还可以通过官网:https://bytebuddy.net,去了解更多关于 Byte Buddy 的内容。
好!那么接下来,我们开始从 HelloWorld 开始。深入了解一个技能前,先多多运行,这样总归能让找到学习的快乐。
二、开发环境
- JDK 1.8.0
- byte-buddy 1.10.9
- byte-buddy-agent 1.10.9
- 本章涉及源码在:itstack-demo-bytecode-2-01,可以关注公众号:bugstack虫洞栈,回复源码下载获取。你会获得一个下载链接列表,打开后里面的第17个「因为我有好多开源代码」,记得给个Star!
三、案例目标
每一个程序员,都运行过 N 多个 HelloWorld,就像很熟悉的 Java;
那么我们接下来就通过使用动态字节码生成的方式,来创建出可以输出 HelloWorld 的程序。
新知识点的学习不要慌,最主要是找到一个可以入手的点,通过这样的一个点去慢慢解开整个程序的面纱。
四、技术实现
1. 官网经典例子
在我们看官网文档中,从它的介绍了就已经提供了一个非常简单的例子,用于输出 HelloWorld,我们在这展示并讲解下。
案例代码:
他的运行结果就是一行,Hello World!,整个代码块核心功能就是通过 method(named("toString")),找到 toString 方法,再通过拦截 intercept,设定此方法的返回值。FixedValue.value("Hello World!")。到这里其实一个基本的方法就通过 Byte-buddy ,改造完成。
接下来的这一段主要是用于加载生成后的 Class 和执行,以及调用方法 toString()。也就是最终我们输出了想要的结果。那么,如果你不能看到这样一段方法块,把我们的代码改造后的样子,心里还是有点虚。那么,我们通过字节码输出到文件,看下具体被改造后的样子,如下;
编译后的Class文件,ByteBuddyHelloWorld.class
在官网来看,这是一个非常简单并且能体现 Byte buddy 的例子。但是与我们平时想创建出来的 main 方法相比,还是有些差异。那么接下来,我们尝试使用字节码编程技术创建出这样一个方法。
2. 字节码创建类和方法
接下来的例子会通过一点点的增加代码梳理,不断的把一个方法完整的创建出来。
2.1 定义输出字节码方法
为了可以更加清晰的看到每一步对字节码编程后,所创建出来的方法样子(clazz),我们需要输出字节码生成 clazz。在Byte buddy中默认提供了一个 dynamicType.saveIn() 方法,我们暂时先不使用,而是通过字节码进行保存。
- 这个方法我们在之前也用到过,主要就是一个 Java 基础的内容,输出字节码到文件中。
2.2 创建类信息
- 创建类和定义类名,如果不写类名会自动生成要给类名。
此时class文件:
2.3 创建main方法
与上面相比新增的代码片段;
- defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC),定义方法;名称、返回类型、属性public static
- withParameter(String[].class, "args"),定义参数;参数类型、参数名称
- intercept(FixedValue.value("Hello World!")),拦截设置返回值,但此时还能满足我们的要求。
这里有一个知识点,Modifier.PUBLIC + Modifier.STATIC,这是一个是二进制相加,每一个类型都在二进制中占有一位。例如 1 2 4 8 ... 对应的二进制占位 1111。所以可以执行相加运算,并又能保留原有单元的属性。
此时class文件:
此时基本已经可以看到我们平常编写的 Hello World 影子了,但还能输出结果。
2.4 委托函数使用
为了能让我们使用字节码编程创建的方法去输出一段 Hello World ,那么这里需要使用到委托。
- 整体来看变化并不大,只有 intercept(MethodDelegation.to(Hi.class)),使用了一段委托函数,真正去执行输出的是另外的函数方法。 MethodDelegation,需要是 public 类被委托的方法与需要与原方法有着一样的入参、出参、方法名,否则不能映射上
此时class文件:
- 那么此时就可以输出我们需要的内容了,Hi.main 是定义出来的委托函数。也就是一个 HelloWorld
五、测试结果
为了可以让整个方法运行起来,我们需要添加字节码加载和反射调用的代码块,如下;
运行结果
效果图
Byte buddy HelloWorld 效果图