ByteBuddy代码生成技术
简介
如官网所说Byte Buddy 是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类。Byte Buddy 相比其他字节码操作库有如下优势:
- 无需理解字节码格式,即可操作,简单易行的 API 能很容易操作字节码。
- 支持 Java 任何版本,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
- 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有优势,具体的性能测试数据可以查看官网
创建类
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World"))
.make()
.load(HelloByteBuddy.class.getClassLoader())
.getLoaded();
Object instance = dynamicType.newInstance();
String toString = instance.toString();
System.out.println(toString);
System.out.println(instance.getClass().getCanonicalName());
}
Hello World
net.bytebuddy.renamed.java.lang.Object$ByteBuddy$4oGQtGr3
上面的例子中创建了一个新的类型(在输出中可以看到相应的类名),继承自Object类型,并覆写了它的toString方法,返回一个固定值,api的可读性很高
- subclass指定了新创建的类的父类
- method 指定了需要拦截的方法
- intercept拦截了toString方法并返回固定的value,最后make方法产生字节码,由类加载器加载到java虚拟机中
方法拦截
上面的例子是拦截了toString方法到一个FixedValue实现,实际使用中可能会实现一些更复杂的场景。Byte Buddy提供了MethodDelegation方法,可以将源方法的调用委托给任意一个POJO对象
假设target对象的实现
public class GreetingInterceptor {
public Object greet(Object argument) {
return "Hello from " + argument;
}
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<? extends java.util.function.Function> dynamicType = new ByteBuddy()
.subclass(java.util.function.Function.class)
.method(ElementMatchers.named("apply"))
.intercept(MethodDelegation.to(new GreetingInterceptor()))
.make()
.load(MethodDelegationTest.class.getClassLoader())
.getLoaded();
System.out.println((String) dynamicType.newInstance().apply("Byte Buddy"));
}
public static class GreetingInterceptor {
public Object greet(Object argument) {
return "Hello from " + argument;
}
}
Hello from Byte Buddy
将java.util.function.Function的apply方法代理到了GreetingInterceptor的greet方法上,这里代理的时候查找的greet方法是通过返回值和参数来确认的,并不依赖方法名一致,如果有两个返回值和参数一致的方法就会产生歧义,无法正确的代理。
拦截器还可以通过注解定义接收更多的参数,以下拦截方法会在拦截到一个Funcition:apply方法后,将原方法的参数以及原方法的Method对象传入intercept方法,在intercept中实现一些自定义的逻辑。在方法上@RuntimeType注解的作用是会通知ByteBuddy在最终会将返回值cast成被拦截的方法的返回值类型。
public class GeneralInterceptor {
@RuntimeType
public Object intercept(@AllArguments Object[] allArguments,
@Origin Method method) {
// intercept any method of any signature
}
}
其他注解:
@SuperCall 传入的是一个Callable类型,可以在被代理类之外调用原方法
@Argument(0) 方法调用的第一个参数,可以使用0-n标记
@This 表示调用方法的原始对象
@AllArguments 被AllArguments标注的参数需要是一个数组类型,并且原参数的类型都要能和数组的类型兼容,
原生支持的注解还有很多,ByteBuddy会根据注解给我们注入相应的参数,可以参阅官方文档了解更多可以使用的注解,同时还能支持自定义的注解形式。
并且这里值得注意的是,虽然在GeneralInterceptor类中使用了bytebuddy中的注解,但是在生成新的子类的时候这些注解都会被忽略,保持生成的代码并不依赖bytebuddy框架。
关于拦截方法的选择上,ByteBuddy不要求Source(被委托的类)和target类的方法名一致,而是通过最接近原则去选取最合适的方法,主要是针对方法参数类型,方法返回值类型,如果存在歧义会报错,也可以通过注解定义优先级。
Java agent
Bytebuddy不仅能通过api创建新的类,还能够修改现有类,在不修改源代码的情况下,做一些侵入,实现一些特定功能。通过java agent可以在main函数之前修改已经存在的类定义,以下的例子是对所有的以Timed结尾的方法实现打印方法执行耗时
代理方法
public class TimingInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) {
long start = System.currentTimeMillis();
try {
return callable.call();
} finally {
System.out.println(method + " took " + (System.currentTimeMillis() - start));
}
}
}
定义premain方法
public class TimerAgent {
public static void premain(String arguments,
Instrumentation instrumentation) {
new AgentBuilder.Default()
.type(ElementMatchers.nameEndsWith("Timed"))
.transform((builder, type, classLoader, module) ->
builder.method(ElementMatchers.any())
.intercept(MethodDelegation.to(TimingInterceptor.class))
).installOn(instrumentation);
}
}
通过maven插件,指定premain的mainfest属性
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>com.aitozi.bytebuddy.TimerAgent</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
在启动java进程时通过加上以下参数:-javaagent:timingagent.jar,这样在启动后所有的以Timed结尾的方法都被注入会打印相应的执行耗时。
重新加载类
除了通过agent实现启动前redefine class。利用jvm hotswap的特性,已经加载的类也可以被重新定义,通常这样可以很方便的编写测试,直接修改类的行为来模拟拦截情况
class Foo {
String m() { return "foo"; }
}
class Bar {
String m() { return "bar"; }
}
ByteBuddyAgent.install();
Foo foo = new Foo();
new ByteBuddy()
.redefine(Bar.class)
.name(Foo.class.getName())
.make()
.load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
assertThat(foo.m(), is("bar"));
使用场景
通过这种非常方便的字节码生成技术,可以做一些有意思的功能,比如以上例子中,不修改源码计算某些方法的耗时。我注意到这个框架主要是因为在blink中也使用了这个lib。
在blink中目前支持sql,datastream,和tableapi作业,作业的资源都是在平台上在执行计划上设置每个节点的资源。
对于sql作业的执行计划的生成其实是引擎代码的逻辑,可以直接拿到用户在平台设置的内存和cpu参数设置到每一个sql节点上,但是对于datastream作业由于streamgraph的生成过程是在用户代码的main函数中,需要侵入用户代码,这就有了byte buddy的用武之地。通过字节码修改技术可以在用户的main函数执行之前,拦截transformation以及StreamNode构造方法,在创建这些方法的地方注入用户在平台上设置的每个计算节点的资源值,达到通过平台设置用户作业资源的目的
参考
官方文档
官网的翻译
深入理解instrument
JVM源码分析之javaagent原理完全解读
https://juejin.im/post/5da2fd6a6fb9a04e23576dd4
ByteBuddy代码生成技术的更多相关文章
- 基于 Eclipse 平台的代码生成技术
------------------------------------------------------------------ 转自http://www.ibm.com/developerwor ...
- Impala中的代码生成技术
Cloudera Impala是一种为Hadoop生态系统打造的开源MPP(massive parallel processing)数据库,它主要为分析型查询负载而设计,而非OLTP.Impala能最 ...
- Simulink仿真入门到精通(十七) Simulink代码生成技术详解
17.1 基于模型的设计 基于模型设计是一种流程,较之传统软件开发流程而言,使开发者能够更快捷.更高效地进行开发.适用范围包括汽车电子信号处理.控制系统.通信行业和半导体行业. V字模型开发流程整体描 ...
- 探究Presto SQL引擎(3)-代码生成
vivo 互联网服务器团队- Shuai Guangying 探究Presto SQL引擎 系列:第1篇<探究Presto SQL引擎(1)-巧用Antlr>介绍了Antlr的基本用法 ...
- Visual Studio动态代码生成的实现基础
这篇文章讨论以下3个问题: 1.代码生成器应该做什么 2.大多数代码生成器的缺点 3.动态代码生成实现的基础 代码生成器应该做什么? 我认为,目标是加快项目开发,方式是减少重复代码手工操作,实现是用过 ...
- 阿里如何实现海量数据实时分析技术-AnalyticDB
导读:随着数据量的快速增长,越来越多的企业迎来业务数据化时代,数据成为了最重要的生产资料和业务升级依据.本文由阿里AnalyticDB团队出品,近万字长文,首次深度解读阿里在海量数据实时分析领域的多项 ...
- 编程学习笔记(第四篇)面向对象技术高级课程:绪论-软件开发方法的演化与最新趋势(4)meta、元与元模型、软件方法的未来发展
一.meta.元与元模型 1.元. "元" 英语是 Meta,meta在不同的行业领域有不同的翻译,在 IT 领域一般来说 Meta 是翻译成元,主要因为在 IT 中Meta ...
- SkyWalking分布式系统应用程序性能监控工具-中
其他功能 性能剖析 在系统性能监控方法上,Skywalking 提出了代码级性能剖析这种在线诊断方法.这种方法基于一个高级语言编程模型共性,即使再复杂的系统,再复杂的业务逻辑,都是基于线程去进行执行的 ...
- SOA 实现:服务设计原则
http://www.ibm.com/developerworks/cn/webservices/ws-soa-design/ 引言 面向服务的体系结构(Service-Oriented Archit ...
随机推荐
- Codeforces 1361C - Johnny and Megan's Necklace(欧拉回路)
Codeforces 题目传送门 & 洛谷题目传送门 u1s1 感觉这个题作为 D1C 还是蛮合适的-- 首先不难发现答案不超过 \(20\),所以可以直接暴力枚举答案并 check 答案是否 ...
- Admixture的监督分群(Supervised analysis)
目录 说明 实战 说明 Admixture通过EM算法一般用于指定亚群分类:或者在不知材料群体结构背景下,通过迭代交叉验证获得error值,取最小error对应的K值为推荐亚群数目.如果我们预先已知群 ...
- window修改dns本地文件
文件地址: C:\Windows\System32\drivers\etc 先修改权限: 最后用记事本打开编辑保存即可
- 半主机模式和_MICROLIB 库
半主机是这么一种机制,它使得在ARM目标上跑的代码,如果主机电脑运行了调试器,那么该代码可以使用该主机电脑的输入输出设备. 这点非常重要,因为开发初期,可能开发者根本不知道该 ARM 器件上有什么 ...
- mysql—Linux系统直接进入mysql服务器,并实现一些基础操作
首先,我们需要通过以下命令来检查MySQL服务器是否启动: ps -ef | grep mysqld 如果MySql已经启动,以上命令将输出mysql进程列表 如果mysql未启动,你可以使用以下命令 ...
- Linux-centos7设置静态IP地址
参考:https://blog.csdn.net/sjhuangx/article/details/79618865
- 【玩具】获取B站视频的音频片段
事情是这样的,我有个和社畜的社会地位不太相符的小爱好--听音乐剧. 基本上是在B站上点开视频听,不是不想在网易云或者QQ音乐听,只是在这些音乐软件上面,我想听的片段要不就收费,要不版本不是我喜欢的,要 ...
- jsp的动态包含和静态包含
jsp的动态包含和静态包含 例如:提取一个公共的页面(top.jsp)到/WEB-INF/jsp/common/目录下 动态包含: 被包含的页面也会独立编译,生成字节码文件,一般包含页面信息频繁变化的 ...
- 巩固javaweb的第二十八天
巩固内容: 设置页面的编码方式 实现代码: 每个 JSP 页面都需要设置编码方式,设置 JSP 页面的编码方式可以是下面两种方式 之一. 方式一: <%@ page contentType=&q ...
- canal从mysql拉取数据,并以protobuf的格式往kafka中写数据
大致思路: canal去mysql拉取数据,放在canal所在的节点上,并且自身对外提供一个tcp服务,我们只要写一个连接该服务的客户端,去拉取数据并且指定往kafka写数据的格式就能达到以proto ...