byte-buddy官网:bytebuddy.net
中文翻译文档[部分文字内容不全]:notes.diguage.com/byte-buddy-…
官方英文文档:bytebuddy.net/#/tutorial
简介:
Byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类。
0. Instrumentation简介
内容摘抄自:www.ibm.com/developerwo…
Instrumentation 是 Java SE 5 的新特性,使用 Instrumentation可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。
在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。
对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能,这使以前无法完成的功能 —— 对 native 接口的 instrumentation 可以在 Java SE 6 中,通过一个或者一系列的 prefix 添加而得以完成。Java SE 6 里的 Instrumentation 也增加了动态添加 class path 的功能。所有这些新的功能,都使得 instrument 包的功能更加丰富,从而使 Java 语言本身更加强大。
java.lang.instrument包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入,整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI),而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。事实上,java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。除开 Instrumentation 功能外,JVMTI 还在虚拟机内存管理,线程控制,方法和变量操作等等方面提供了大量有价值的函数。关于 JVMTI 的详细信息,请参考 Java SE 6 文档(docs.oracle.com/javase/6/do…)当中的介绍。
1. JavaAgent中使用
pom.xml引入byte-buddy依赖和Agent打包插件:
// pom.xml
...
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<!-- 修改为正确的Premain地址 -->
<Premain-Class>xxx.AgentPremain</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
...
共用代码:
public interface Operation<K,V> {
void put(K key,V value);
V get(K key);
void dump();
}
public class DefaultOperation implements Operation<String, String> {
private Map<String, String> map = new HashMap<>();
public DefaultOperation() {
//
}
@Override
public void put(String key, String value) {
map.put(key, value);
}
@Override
public String get(String key) {
return map.get(key);
}
@Override
public void dump() {
if (map.isEmpty()) {
System.out.println("{}");
}
StringBuilder sb = new StringBuilder("{");
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
}
if (sb.lastIndexOf(", ") == sb.length() - 2) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append("}");
System.out.println(sb.toString());
}
}
Agent模块通用代码:
// Agent增强结果回调
public class TransformListener implements AgentBuilder.Listener {
@Override
public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
System.out.printf("Agent throw error before running, typeName=%s, message=%s%n", typeName, throwable.getMessage());
throwable.printStackTrace();
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
}
}
1.1 添加成员变量并在构造函数中赋值
// 定义构造函数通知
public class ConstructorAdvice {
@Advice.OnMethodExit
public static void onMethodExit(@Advice.FieldValue(value = "counter", readOnly = false) AtomicInteger counter) {
counter = new AtomicInteger();
}
}
public class AgentPremain {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Agent premain!");
new AgentBuilder.Default()
// 限定作用范围,为接口Operation的实现类
.type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
.and(ElementMatchers.not(ElementMatchers.isInterface()))
)
.transform((builder, typeDescription, classLoader, module) -> builder
// 定义类成员变量,命名counter,类型AtomicInteger,作用域public
.defineField("counter", AtomicInteger.class, Visibility.PUBLIC)
// 确定切点为所有构造函数,紧跟这使用.intercept(...)进行通知织入
.constructor(ElementMatchers.any())
// 通知织入上面的切点
.intercept(Advice.to(ConstructorAdvice.class)))
.with(new TransformListener())
.installOn(instrumentation);
}
}
maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar
运行测试类:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Operation<String, String> operation = new DefaultOperation();
Field field = operation.getClass().getDeclaredField("counter");
Object object = field.get(operation);
int counterValue = ((AtomicInteger) object).incrementAndGet();
System.out.println(counterValue);
}
输出结果:
Agent premain!
1
使用Arthas的jad命令反编译出DefaultOperation类:
public class DefaultOperation implements Operation<String, String> {
private Map<String, String> map;
public AtomicInteger counter;
@Override
public void dump() {
if (this.map.isEmpty()) {
System.out.println("{}");
}
StringBuilder sb = new StringBuilder("{");
for (Map.Entry<String, String> entry : this.map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
}
if (sb.lastIndexOf(", ") == sb.length() - 2) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append("}");
System.out.println(sb.toString());
}
public DefaultOperation() {
DefaultOperation defaultOperation = this;
defaultOperation(null);
this.counter = new AtomicInteger();
}
private /* synthetic */ DefaultOperation(auxiliary.vawGY1XQ vawGY1XQ2) {
this.map = new HashMap<String, String>();
}
static {
ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, -923819650);
}
@Override
public String get(String key) {
return this.map.get(key);
}
@Override
public void put(String key, String value) {
this.map.put(key, value);
}
}
1.2 添加方法
// 新方法定义
public class AddMethod {
// @Argument表示方法入参引用, @FieldValue表示类成员变量引用
@RuntimeType
public static void method(@Argument(0) String key,
@Argument(1) String value,
@FieldValue("map") Map map) {
map.put(key, value);
}
}
public class AgentPremain {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Agent premain!");
new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
.and(ElementMatchers.not(ElementMatchers.isInterface()))
)
.transform((builder, typeDescription, classLoader, module) -> builder
// 定义新方法,方法名为put2,返回类型为void,作用于为public
.defineMethod("put2", void.class, Visibility.PUBLIC)
// 定义新方法的入参数量和类型
.withParameters(Arrays.asList(String.class, String.class))
// 将新方法的实现进行代理
.intercept(MethodDelegation.to(AddMethod.class))
)
.with(new TransformListener())
.installOn(instrumentation);
}
}
maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar
运行测试类:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Operation<String, String> operation = new DefaultOperation();
Method put2 = operation.getClass().getDeclaredMethod("put2", String.class, String.class);
put2.invoke(operation, "A", "a");
put2.invoke(operation, "B", "b");
operation.dump();
}
输出结果:
Agent premain!
{"A":"a", "B":"b"}
1.3 拦截方法(使用Advice)
// 构造函数Advice
public class ConstructorMethodAdvice {
@Advice.OnMethodEnter
public static void onMethodEnter(@Advice.Origin String methodName) {
System.out.printf("constructor %s start. \n", methodName);
}
@Advice.OnMethodExit
public static void onMethodExit(@Advice.Origin String methodName) {
System.out.printf("constructor %s end. \n", methodName);
}
}
// put(K,V)方法Advice
public class PutMethodAdvice {
@Advice.OnMethodEnter
public static Long onMethodEnter(@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
Long start = System.currentTimeMillis();
if (value instanceof String) {
value = "_" + value;
}
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
return start;
}
@Advice.OnMethodExit
public static void onMethodExit(@Advice.Origin Method method,
@Advice.Enter Long start) {
System.out.printf("method %s cost %d ms. \n", method.getName(), System.currentTimeMillis() - start);
}
}
public class AgentPremain {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Agent premain!");
new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
.and(ElementMatchers.not(ElementMatchers.isInterface()))
)
.transform((builder, typeDescription, classLoader, module) -> builder
.method(ElementMatchers.named("put"))
.intercept(Advice.to(PutMethodAdvice.class))
.constructor(ElementMatchers.any())
.intercept(Advice.to(ConstructorMethodAdvice.class))
)
.with(new TransformListener())
.installOn(instrumentation);
}
}
maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar
运行测试类:
public static void main(String[] args) {
Operation<String, String> operation = new DefaultOperation();
operation.put("A", "a");
operation.put("B", "b");
operation.put("C", null);
operation.dump();
}
输出结果:
Agent premain!
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() start.
constructor public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation() end.
method put cost 21 ms.
method put cost 20 ms.
method put cost 20 ms.
{"A":"_a", "B":"_b", "C":null}
使用Arthas的jad命令反编译DefaultOperation类:
public class DefaultOperation implements Operation<String, String> {
private Map<String, String> map;
@Override
public void dump() {
if (this.map.isEmpty()) {
System.out.println("{}");
}
StringBuilder sb = new StringBuilder("{");
for (Map.Entry<String, String> entry : this.map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
}
if (sb.lastIndexOf(", ") == sb.length() - 2) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append("}");
System.out.println(sb.toString());
}
private /* synthetic */ void put$original$QVENVsOQ(String key, String value) {
this.map.put(key, value);
}
public DefaultOperation() {
System.out.printf("constructor %s start. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()");
DefaultOperation defaultOperation = this;
defaultOperation(null);
System.out.printf("constructor %s end. \n", "public com.github.blackbaka.bytebuddy.interceptmethod.demo.DefaultOperation()");
}
private /* synthetic */ DefaultOperation(auxiliary.8KQdfbi7 kQdfbi7) {
this.map = new HashMap<String, String>();
}
static {
ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 0x91E11EE);
}
@Override
public String get(String key) {
return this.map.get(key);
}
@Override
public void put(String string, String string2) {
Long l = System.currentTimeMillis();
if (string2 instanceof String) {
string2 = "_" + string2;
}
try {
TimeUnit.MILLISECONDS.sleep(20L);
}
catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
DefaultOperation defaultOperation = this;
String string3 = string;
String string4 = string2;
defaultOperation.put$original$QVENVsOQ(string3, string4);
System.out.printf("method %s cost %d ms. \n", DefaultOperation.class.getMethod("put", String.class, String.class).getName(), System.currentTimeMillis() - l);
}
}
1.4 拦截方法(使用MethodDelegation)
// put(K,V)方法的Delegation
public class PutMethodDelegation {
@RuntimeType
public static void put(@RuntimeType @Argument(0) Object key,
@RuntimeType @Argument(1) Object value,
@SuperCall Runnable runnable) {
System.out.printf("enter method put, key=%s, value=%s \n", key, value);
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("exit method put");
}
}
// get(K)方法的Delegation
public class GetMethodDelegation {
@RuntimeType
public static Object get(@RuntimeType @Argument(0) Object key,
@SuperCall Callable<Object> callable) {
System.out.println("enter method get");
Object result = null;
try {
// @SuperCall修饰的Callable<Object>为对原方法调用的封装,返回类型为void时为Runnable
result = callable.call();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("exit method get");
return result;
}
}
public class AgentPremain {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Agent premain!");
new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(ElementMatchers.namedOneOf(Operation.class.getName()))
.and(ElementMatchers.not(ElementMatchers.isInterface()))
)
.transform((builder, typeDescription, classLoader, module) -> builder
.method(ElementMatchers.named("put"))
.intercept(MethodDelegation.to(PutMethodDelegation.class))
.method(ElementMatchers.named("get"))
.intercept(MethodDelegation.to(GetMethodDelegation.class))
)
.with(new TransformListener())
.installOn(instrumentation);
}
}
maven打包,运行时附带上Agent参数:-javaagent:/xxx/byte-buddy-agent.jar
运行测试类:
public static void main(String[] args) {
Operation<String, String> operation = new DefaultOperation();
operation.put("A", "a");
operation.put("B", "b");
operation.put("C", null);
operation.dump();
}
输出结果:
Agent premain!
enter method put, key=A, value=a
exit method put
enter method put, key=B, value=b
exit method put
enter method put, key=C, value=null
exit method put
{"A":"a", "B":"b", "C":null}
使用Arthas的jad命令反编译DefaultOperation类:
public class DefaultOperation implements Operation<String, String> {
private Map<String, String> map = new HashMap<String, String>();
@Override
public void dump() {
if (this.map.isEmpty()) {
System.out.println("{}");
}
StringBuilder sb = new StringBuilder("{");
for (Map.Entry<String, String> entry : this.map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key != null ? "\"" + key + "\"" : "null").append(":");
sb.append(value != null ? "\"" + value + "\"" : "null").append(", ");
}
if (sb.lastIndexOf(", ") == sb.length() - 2) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append("}");
System.out.println(sb.toString());
}
private /* synthetic */ void put$original$FYSPUuUe(String key, String value) {
this.map.put(key, value);
}
private /* synthetic */ String get$original$FYSPUuUe(String key) {
return this.map.get(key);
}
final /* synthetic */ String get$original$FYSPUuUe$accessor$qaEMliZ2(String string) {
return this.get$original$FYSPUuUe(string);
}
final /* synthetic */ void put$original$FYSPUuUe$accessor$qaEMliZ2(String string, String string2) {
this.put$original$FYSPUuUe(string, string2);
}
static {
ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, DefaultOperation.class, 420827887);
}
@Override
public String get(String string) {
return (String)GetMethodDelegation.get(string, new DefaultOperation$auxiliary$zU9K9I52(this, string));
}
@Override
public void put(String string, String string2) {
PutMethodDelegation.put(string, string2, new DefaultOperation$auxiliary$ywsBi3iU(this, string, string2));
}
}
2. 在Runtime中使用
在pom.xml中添加依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.19</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.19</version>
</dependency>
2.1 拦截方法(使用Advice)
public class PutMethodAdvice {
@Advice.OnMethodEnter
public static Long onEnter(@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object key,
@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
key = "_" + key;
value = "_" + (value != null ? value : "");
Long startMillis = System.currentTimeMillis();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return startMillis;
}
@Advice.OnMethodExit
public static void onExit(@Advice.Enter Long startMillis) {
System.out.printf("method put cost %d ms \n", System.currentTimeMillis() - startMillis);
}
}
public class InterceptMethodEnhancer {
public static void interceptByAdvice(){
ByteBuddyAgent.install();
new ByteBuddy()
.rebase(DefaultOperation.class)
.visit(Advice.to(PutMethodAdvice.class).on(ElementMatchers.named("put")))
.visit(Advice.to(GetMethodAdvice.class).on(ElementMatchers.named("get")))
.make()
.load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
}
运行测试类:
Operation<String, String> operation = new DefaultOperation();
InterceptMethodEnhancer.interceptByAdvice();
operation.put("A", "a");
operation.put("B", "b");
operation.put("C", null);
operation.dump();
输出结果:
method put cost 19 ms
method put cost 20 ms
method put cost 11 ms
{"_A":"_a", "_B":"_b", "_C":"_"}
2.2 添加注解(在类、成员变量、方法上)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Mark {
String value() default "";
}
public class AddAnnotationEnhancer {
public static void addOnClassAndFieldAndMethod() {
ByteBuddyAgent.install();
// 构建annotation description
AnnotationDescription annotationDescription = AnnotationDescription.Latent.Builder
// 设置注解常量值
.ofType(Mark.class).define("value", "marked").build();
new ByteBuddy()
.redefine(DefaultOperation.class)
.annotateType(annotationDescription)
.visit(new MemberAttributeExtension.ForField()
.annotate(annotationDescription)
.on(ElementMatchers.named("map")))
.visit(new MemberAttributeExtension.ForMethod()
.annotateMethod(annotationDescription)
.on(ElementMatchers.named("put")))
.make()
.load(Thread.currentThread().getContextClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
}
运行测试类:
Operation<String, String> operation = new DefaultOperation();
AddAnnotationEnhancer.addOnClassAndFieldAndMethod();
Field field = operation.getClass().getDeclaredField("map");
if (!field.isAccessible()) {
field.setAccessible(true);
}
Mark markOnClass = operation.getClass().getAnnotation(Mark.class);
Mark markOnField = field.getAnnotation(Mark.class);
Mark markOnMethod = operation.getClass().getMethod("put", String.class, String.class).getAnnotation(Mark.class);
System.out.printf("Class's Mark value is %s \n", markOnClass.value());
System.out.printf("Field's Mark value is %s \n", markOnField.value());
System.out.printf("Method's Mark value is %s \n", markOnMethod.value());
输出结果:
Class's Mark value is marked
Field's Mark value is marked
Method's Mark value is marked