-
Notifications
You must be signed in to change notification settings - Fork 3.7k
2020 05 08 字节码编程,Byte buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》
作者:小傅哥
博客:https://bugstack.cn
沉淀、分享、成长,让自己和他人都能有所收获!
相对于小傅哥
之前编写的字节码编程; 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
;
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))
,使用了一段委托函数,真正去执行输出的是另外的函数方法。- MethodDelegation,需要是
public
类 - 被委托的方法与需要与原方法有着一样的入参、出参、方法名,否则不能映射上
- MethodDelegation,需要是
此时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。 - 在学习整理的过程中发现,关于字节码编程方面的资料并不是很全,主要源于大家平时的开发中基本是用不到的,谁也不可能总去修改字节码。但对于补全这样的成体系完善技术栈资料,却可以帮助很多需要的人。因此我也会持续输出类似这样空白的技术文章。
小傅哥(微信:fustack),公众号:bugstack虫洞栈
| bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
🌏 知识星球:码农会锁
实战项目:「DDD+RPC分布式抽奖系统
」、专属小册、问题解答、简历指导、架构图稿、视频课程
🐲 头条
-
💥
🎁 Lottery 抽奖系统
- 基于领域驱动设计的四层架构的互联网分布式开发实践 -
小傅哥的《重学 Java 设计模式》
- 全书彩印、重绘类图、添加内容 -
⭐小傅哥的《Java 面经手册》
- 全书5章29节,417页11.5万字,完稿&发版 -
小傅哥的《手撸 Spring》
- 通过带着读者手写简化版 Spring 框架,了解 Spring 核心原理 -
🌈小傅哥的《SpringBoot 中间件设计和开发》
- 小册16个中间件开发30个代码库
⛳ 目录
💋 精选
🐾 友链
建立本开源项目的初衷是基于个人学习与工作中对 Java 相关技术栈的总结记录,在这里也希望能帮助一些在学习 Java 过程中遇到问题的小伙伴,如果您需要转载本仓库的一些文章到自己的博客,请按照以下格式注明出处,谢谢合作。
作者:小傅哥
链接:https://bugstack.cn
来源:bugstack虫洞栈
2021年10月24日,小傅哥
的文章全部开源到代码库 CodeGuide
中,与同好同行,一起进步,共同维护。
这里我提供 3 种方式:
-
提出
Issue
:在 Issue 中指出你觉得需要改进/完善的地方(能够独立解决的话,可以在提出 Issue 后再提交PR
)。 -
处理
Issue
: 帮忙处理一些待处理的Issue
。 -
提交
PR
: 对于错别字/笔误这类问题可以直接提交PR
,无需提交Issue
确认。
详细参考:CodeGuide 贡献指南 - 非常感谢你的支持,这里会留下你的足迹
- 加群交流 本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “小傅哥” 微信(fustack),备注:加群。
- 公众号(bugstack虫洞栈) - 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。
感谢以下人员对本仓库做出的贡献或者对小傅哥的赞赏,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与我联系。