做者:小傅哥
博客:https://bugstack.cn - 汇总系列专题文章
java
沉淀、分享、成长,让本身和他人都能有所收获!
相对于小傅哥
以前编写的字节码编程; ASM
、Javassist
系列,Byte Buddy
玩法上更加高级,你能够彻底不须要了解一个类和方法块是如何经过 指令码
LDC、LOAD、STORE、IRETURN... 生成出来的。就像它的官网介绍; git
Byte Buddy
是一个代码生成和操做库,用于在 Java
应用程序运行时建立和修改 Java
类,而无需编译器的帮助。除了 Java
类库附带的代码生成实用程序外,Byte Buddy
还容许建立任意类,而且不限于实现用于建立运行时代理的接口。此外,Byte Buddy
提供了一种方便的 API,可使用 Java
代理或在构建过程当中手动更改类。程序员
2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大奖。该奖项对Byte Buddy的“ Java技术方面的巨大创新 ”表示赞扬。咱们为得到此奖项感到很是荣幸,并感谢全部帮助Byte Buddy取得成功的用户以及其余全部人。咱们真的很感激!
除了这些简单的介绍外,还能够经过官网:https://bytebuddy.net
,去了解更多关于 Byte Buddy
的内容。github
好! 那么接下来,咱们开始从 HelloWorld
开始。深刻了解一个技能前,先多多运行,这样总归能让找到学习的快乐。编程
itstack-demo-bytecode-2-01
,能够关注公众号:bugstack虫洞栈
,回复源码下载获取。你会得到一个下载连接列表,打开后里面的第17个「由于我有好多开源代码」
,记得给个Star
!每个程序员,都运行过 N
多个 HelloWorld
,就像很熟悉的 Java
;ide
public class Hi { public static void main(String[] args) { System.out.println("Byte-buddy Hi HelloWorld By 小傅哥(bugstack.cn)"); } }
那么咱们接下来就经过使用动态字节码生成的方式,来建立出能够输出 HelloWorld
的程序。函数
新知识点的学习不要慌,最主要是找到一个能够入手的点,经过这样的一个点去慢慢解开整个程序的面纱。性能
在咱们看官网文档中,从它的介绍了就已经提供了一个很是简单的例子,用于输出 HelloWorld
,咱们在这展现并讲解下。学习
案例代码:测试
String helloWorld = new ByteBuddy() .subclass(Object.class) .method(named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader()) .getLoaded() .newInstance() .toString(); System.out.println(helloWorld); // Hello World!
他的运行结果就是一行,Hello World!
,整个代码块核心功能就是经过 method(named("toString"))
,找到 toString 方法,再经过拦截 intercept
,设定此方法的返回值。FixedValue.value("Hello World!")
。到这里其实一个基本的方法就经过 Byte-buddy
,改造完成。
接下来的这一段主要是用于加载生成后的 Class
和执行,以及调用方法 toString()
。也就是最终咱们输出了想要的结果。那么,若是你不能看到这样一段方法块,把咱们的代码改造后的样子,内心仍是有点虚。那么,咱们经过字节码输出到文件,看下具体被改造后的样子,以下;
编译后的Class文件,ByteBuddyHelloWorld.class
public class HelloWorld { public String toString() { return "Hello World!"; } public HelloWorld() { } }
在官网来看,这是一个很是简单而且能体现 Byte buddy
的例子。可是与咱们平时想建立出来的 main
方法相比,仍是有些差别。那么接下来,咱们尝试使用字节码编程技术建立出这样一个方法。
接下来的例子会经过一点点的增长代码梳理,不断的把一个方法完整的建立出来。
为了能够更加清晰的看到每一步对字节码编程后,所建立出来的方法样子(clazz),咱们须要输出字节码生成 clazz
。在Byte buddy中默认提供了一个 dynamicType.saveIn()
方法,咱们暂时先不使用,而是经过字节码进行保存。
private static void outputClazz(byte[] bytes) { FileOutputStream out = null; try { String pathName = ApiTest.class.getResource("/").getPath() + "ByteBuddyHelloWorld.class"; out = new FileOutputStream(new File(pathName)); System.out.println("类输出路径:" + pathName); out.write(bytes); } catch (Exception e) { e.printStackTrace(); } finally { if (null != out) try { out.close(); } catch (IOException e) { e.printStackTrace(); } } }
Java
基础的内容,输出字节码到文件中。DynamicType.Unloaded<?> dynamicType = new ByteBuddy() .subclass(Object.class) .name("org.itstack.demo.bytebuddy.HelloWorld") .make(); // 输出类字节码 outputClazz(dynamicType.getBytes());
此时class文件:
public class HelloWorld { public HelloWorld() { } }
DynamicType.Unloaded<?> dynamicType = new ByteBuddy() .subclass(Object.class) .name("org.itstack.demo.bytebuddy.HelloWorld") .defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC) .withParameter(String[].class, "args") .intercept(FixedValue.value("Hello World!")) .make();
与上面相比新增的代码片断;
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文件:
public class HelloWorld { public static void main(String[] args) { String var10000 = "Hello World!"; } public HelloWorld() { } }
此时基本已经能够看到咱们日常编写的 Hello World
影子了,但还能输出结果。
为了能让咱们使用字节码编程建立的方法去输出一段 Hello World
,那么这里须要使用到委托
。
DynamicType.Unloaded<?> dynamicType = new ByteBuddy() .subclass(Object.class) .name("org.itstack.demo.bytebuddy.HelloWorld") .defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC) .withParameter(String[].class, "args") .intercept(MethodDelegation.to(Hi.class)) .make();
总体来看变化并不大,只有 intercept(MethodDelegation.to(Hi.class))
,使用了一段委托函数,真正去执行输出的是另外的函数方法。
public
类此时class文件:
public class HelloWorld { public static void main(String[] args) { Hi.main(var0); } public HelloWorld() { } }
Hi.main
是定义出来的委托函数。也就是一个 HelloWorld
为了可让整个方法运行起来,咱们须要添加字节码加载和反射调用的代码块,以下;
// 加载类 Class<?> clazz = dynamicType.load(GenerateClazzMethod.class.getClassLoader()) .getLoaded(); // 反射调用 clazz.getMethod("main", String[].class).invoke(clazz.newInstance(), (Object) new String[1]);
运行结果
类输出路径:/User/xiaofuge/itstack/git/github.com/itstack-demo-bytecode/itstack-demo-bytecode-2-01/target/test-classes/ByteBuddyHelloWorld.class helloWorld Process finished with exit code 0
效果图
Byte buddy
中,须要掌握几个关键信息;建立方法、定义属性、拦截委托、输出字节码,以及最终的运行。这样的一个简单过程,能够很快的了解到如何使用 Byte buddy
。Byte buddy
方法经过实际的案例去模拟建设,在这个过程当中增强学习使用。一些基础知识也能够经过官方文档进行学习;https://bytebuddy.net。CodeGuide | 程序员编码指南 Go!
本代码库是做者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为你们提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。若是本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!