简介

如官网所说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代码生成技术的更多相关文章

  1. 基于 Eclipse 平台的代码生成技术

    ------------------------------------------------------------------ 转自http://www.ibm.com/developerwor ...

  2. Impala中的代码生成技术

    Cloudera Impala是一种为Hadoop生态系统打造的开源MPP(massive parallel processing)数据库,它主要为分析型查询负载而设计,而非OLTP.Impala能最 ...

  3. Simulink仿真入门到精通(十七) Simulink代码生成技术详解

    17.1 基于模型的设计 基于模型设计是一种流程,较之传统软件开发流程而言,使开发者能够更快捷.更高效地进行开发.适用范围包括汽车电子信号处理.控制系统.通信行业和半导体行业. V字模型开发流程整体描 ...

  4. 探究Presto SQL引擎(3)-代码生成

    ​ vivo 互联网服务器团队- Shuai Guangying 探究Presto SQL引擎 系列:第1篇<探究Presto SQL引擎(1)-巧用Antlr>介绍了Antlr的基本用法 ...

  5. Visual Studio动态代码生成的实现基础

    这篇文章讨论以下3个问题: 1.代码生成器应该做什么 2.大多数代码生成器的缺点 3.动态代码生成实现的基础 代码生成器应该做什么? 我认为,目标是加快项目开发,方式是减少重复代码手工操作,实现是用过 ...

  6. 阿里如何实现海量数据实时分析技术-AnalyticDB

    导读:随着数据量的快速增长,越来越多的企业迎来业务数据化时代,数据成为了最重要的生产资料和业务升级依据.本文由阿里AnalyticDB团队出品,近万字长文,首次深度解读阿里在海量数据实时分析领域的多项 ...

  7. 编程学习笔记(第四篇)面向对象技术高级课程:绪论-软件开发方法的演化与最新趋势(4)meta、元与元模型、软件方法的未来发展

    一.meta.元与元模型 1.元. ​ "元" 英语是 Meta,meta在不同的行业领域有不同的翻译,在 IT 领域一般来说 Meta 是翻译成元,主要因为在 IT 中Meta ...

  8. SkyWalking分布式系统应用程序性能监控工具-中

    其他功能 性能剖析 在系统性能监控方法上,Skywalking 提出了代码级性能剖析这种在线诊断方法.这种方法基于一个高级语言编程模型共性,即使再复杂的系统,再复杂的业务逻辑,都是基于线程去进行执行的 ...

  9. SOA 实现:服务设计原则

    http://www.ibm.com/developerworks/cn/webservices/ws-soa-design/ 引言 面向服务的体系结构(Service-Oriented Archit ...

随机推荐

  1. webpack--css、html 和 js 代码的常用处理

    前言 本文来总结下webpack中 css.js.html 代码常见的处理方式,学习笔记仅供参考. 正文 1.css样式文件处理 (1)提取css为一个单独的文件 在我们前面学习了webpack的基础 ...

  2. 【数据库】本地KEGG数据库如何拆分子库?

    目录 KEGG本地库文件 按物种拆分KEGG数据库 1.获得物种分类信息 2.获得物种分类的序列信息并建库 3.获得物种分类的K-ko对应文件 根据相似性原理,序列相似,功能相似,所有功能注释无非是用 ...

  3. 在R语言中使用Stringr进行字符串操作

    今天来学习下R中字符串处理操作,主要是stringr包中的字符串处理函数的用法. 先导入stringr包,library(stringr),require(stringr),或者stringr::函数 ...

  4. mysql-计算排名

    mysql计算排名,获取行号rowno 学生成绩表数据 SELECT * FROM table_score ORDER BY score DESC; 获取某个学生成绩排名并计算该学生和上一名学生成绩差 ...

  5. 初学者如何吃透一个Java项目

    不少初学者朋友在学习Java过程中,会对着视频敲Java项目,其中遇到的BUG还能解决,但就是每次敲完一个项目,就感觉很空虚,项目里面的知识点感觉懂了但又好像没懂 这些朋友应该怎样才能掌握一个项目所用 ...

  6. [Emlog主题] Monkey V3.0 优化修改

    原作者博客:https://blog.dyboy.cn/ Monkey V3.0 优化修改版 修改说明: 背景颜色修改(按个人喜好可自行修改,仿PCQQ午夜巴黎皮肤) 搜索框按钮样式优化,不那么突兀了 ...

  7. android TabLayout设置选项卡之间的距离无效已解决

    根据下面的链接设置完距离后无法生效 https://www.jb51.net/article/131304.htm layout <com.google.android.material.tab ...

  8. 【编程思想】【设计模式】【创建模式creational】Borg/Monostate

    Python版 https://github.com/faif/python-patterns/blob/master/creational/borg.py #!/usr/bin/env python ...

  9. 【Spring Framework】Spring入门教程(六)Spring AOP使用

    Spring的AOP 动态代理模式的缺陷是: 实现类必须要实现接口 -JDK动态代理 无法通过规则制定拦截无需功能增强的方法. Spring-AOP主要弥补了第二个不足,通过规则设置来拦截方法,并对方 ...

  10. 【VSCode】检测到 #include 错误。请更新 includePath。已为此翻译单元(C:\mingw-w64\i686-8.1.0-posix-dwarf-rt_v6-rev0\mingw32\i686-

    win+r 运行cmd 输入"gcc -v -E -x c -"获取mingw路径: 我的: #include "..." search starts here ...