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 ...
随机推荐
- Comet OJ Contest #13 D
Comet OJ Contest #13 D \(\displaystyle \sum_{i=0}^{\left\lfloor\frac{n}{2}\right\rfloor} a^{i} b^{n- ...
- Python三元表达式,列表推导式,字典生成式
目录 1. 三元表达式 2. 列表推导式 3. 字典生成式 3.1 字典生成式 3.2 zip()方法 1. 三元表达式 """ 条件成立时的返回值 if 条件 else ...
- 21-Add Two Numbers-Leetcode
You are given two linked lists representing two non-negative numbers. The digits are stored in rever ...
- C语言中储存的大小端问题
一.大小端定义 研究变量的高低字节:从左往右看,字节序递增,也就是最右边是最低字节,最右边是最高字节.如 int i = 0x01020304, 01是高字节,04是低字节.如果是字符串如char a ...
- Kafka入门教程(二)
转自:https://blog.csdn.net/yuan_xw/article/details/79188061 Kafka集群环境安装 相关下载 JDK要求1.8版本以上. JDK安装教程:htt ...
- 从面试官的角度,聊聊java面试流程
在这篇回答里,就讲以我常规的面试流程为例,说下java方面大致会问什么问题,以及如何确认候选人达到招聘要求. 先说面试前准备,可能有些面试官是拿到简历直接问,而且是在候选人自我介绍时再草草浏览简历,但 ...
- CPU 是如何认识和执行代码的
CPU的介绍 CPU 也称为微处理器,是计算机的心脏和/或大脑. 深入研究计算机的核心,可以帮助我们有效地编写计算机程序. CPU 是计算机的心脏和大脑,它执行提供给他们的指令.它的主要工作是执行算术 ...
- [php安全]原生类的利用
php原生类的利用 查看原生类中具有魔法函数的类 $classes = get_declared_classes(); foreach ($classes as $class) { $methods ...
- Linux的小知识
1. top 命令可以在Linux下查看任务管理器和当前进程使用资源情况. 2. Ctrl+c 即可退出,然后使用 kill+进程号 命令可杀死指定进程 3.在Linux的 /etc/rc.local ...
- Spring Boot的异步任务、定时任务和邮件任务
一.异步任务 1.启动类添加注解@EnableAsync,开启异步任务注解功能: 2.需要异步执行的方法上添加@Async注解. 二.定时任务 1.启动类添加注解@EnableScheduling,开 ...