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 ...
随机推荐
- SAM 感性瞎扯
SAM 做题笔记. 这里是 SAM 感性瞎扯. 最近学了后缀自动机(Suffix_Automaton,SAM),深感其巧妙之处,故写文以记之. 部分文字与图片来源于 OI-Wiki,hihoCoder ...
- zabbix_get
zabbix_get是一个命令行工具,可用于从一个远程的Zabbix探针获取监控数据. 1.2 用法 zabbix_get [-hV] [-s <host name or IP> ] [- ...
- ubuntu20.04安装EasyConnect兼容性问题解决
目录 1. 命令行启动EasyConnect 2. 降级pango 3. 重新启动EasyConnect,即可成功启动 Ubuntu20.04安装EasyConnect后无法启动的解决方案 工作使用操 ...
- Deep Learning(深度学习)整理,RNN,CNN,BP
申明:本文非笔者原创,原文转载自:http://www.sigvc.org/bbs/thread-2187-1-3.html 4.2.初级(浅层)特征表示 既然像素级的特征表示方法没有作用,那怎 ...
- 漏洞分析:CVE-2017-17215
漏洞分析:CVE-2017-17215 华为HG532路由器的命令注入漏洞,存在于UPnP模块中. 漏洞分析 什么是UPnP? 搭建好环境(使用IoT-vulhub的docker环境),启动环境,查看 ...
- 表格table的宽度问题
首先注意table的一个样式 table { table-layout:fixed; } table-layout有以下取值: automatic 默认.列宽度由单元格内容设定 fixed 列宽由表格 ...
- 重新整理 .net core 实践篇——— endpoint[四十七]
前言 简单整理一些endpoint的一些东西,主要是介绍一个这个endpoint是什么. 正文 endpoint 从表面意思是端点的意思,也就是说比如客户端的某一个action 是一个点,那么服务端的 ...
- 一文搞懂指标采集利器 Telegraf
作者| 姜闻名 来源|尔达 Erda 公众号 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...
- 在JTable单元格上 加入组件,并赋予可编辑能力 [转]
表格(单元格放置组件) 对于JTable单元格的渲染主要是通过两个接口来实现的,一个是TableCellRenderer另一个是TableCellEditor,JTable默认是用的是DefaultC ...
- Linux学习 - 正则表达式
一.正则表达式与通配符 正则表达式:在文件中匹配符合条件的字符串,正则是包含匹配 通配符:用来匹配符合条件的文件名,通配符是完全匹配 二.基础正则表达式 元字符 作用 a* a有0个或任意多个 . 除 ...