ByteBuddy官方文档
首先需要了解ByteBuddy是什么,ByteBuddy是一款java字节码增强框架,可以动态的生成java字节码文件,比起我们自己进行字节码文件的生成,它屏蔽了底层细节,提供一套统一易上手的Api,简化了字节码增强的学习难度。
为什么需要字节码增强技术?ByteBuddy官方文档已经给出了答案
The Java language comes with a comparatively strict type system. Java requires all variables and objects to be of a specific type and any attempt to assign incompatible types always causes an error. These errors are usually emitted by the Java compiler or at the very least by the Java runtime when casting a type illegally. Such strict typing is often desirable, for example when writing business applications. Business domains can usually be described in such an explicit manner where any domain item represents its own type. This way, we can use Java to build very readable and robust applications where mistakes are caught close to their source. Among other things, it is Java’s type system that is responsible for Java’s popularity in enterprise programming.
=
However, by enforcing its strict type system, Java imposes limitations that restrict the language’s scope in other domains. For example, when writing a general-purpose library that is to be used by other Java applications, we are normally not able to reference any type that is defined in the user’s application because these types are unknown to us when our library is compiled. In order to call methods or to access fields of the user’s unknown code, the Java Class Library comes with a reflection API. Using the reflection API, we are able to introspect unknown types and to call methods or access fields. Unfortunately, the use of the reflection API has two significant downsides:
Using the reflection API is slower than a hard-coded method invocation: First, one needs to perform a rather expensive method lookup to get hold of an object that describes a specific method. And when a method is invoked, this requires the JVM to run native code which requires long run time compared to a direct invocation. However, modern JVMs know a concept called inflation where the JNI-based method invocation is replaced by generated byte code that is injected into a dynamically created class. (Even the JVM itself uses code generation!) After all, Java’s inflation system remains with the drawback of generating very general code that for example only works with boxed primitive types such that the performance drawback is not entirely settled.
The reflection API defeats type-safety: Even though the JVM is capable of invoking code by reflection, the reflection API is itself not type-safe. When writing a library, this is not a problem as long as we do not need to expose the reflection API to the library’s user. After all, we do not know the user code during compilation and could not validate our library code against its types. Sometimes, it is however required to expose the reflection API to a user by for example letting a library invoke one of our own methods for us. This is where using the reflection API becomes problematic as the Java compiler would have all the information to validate our program’s type safety. For example, when implementing a library for method-level security, a user of this library would want the library to invoke a method only after enforcing a security constraint. For this, the library would need to reflectively call a method after the user handed over the required arguments for this method. Doing so, there is however no longer a compile-time type check if these method arguments match with the method’s reflective invocation. The method call is still validated but the check is delayed until runtime. Doing so, we voided a great feature of the Java programming language.
This is where runtime code generation can help us out. It allows us to emulate some features that are normally only accessible when programming in a dynamic languages without discarding Java’s static type checks. This way, we can get the best of both worlds and additionally improve runtime performance. To get a better understanding of this problem, let us look at the example of implementing the mentioned method-level security library.
简单总结就是java的反射存在诸多限制,java开发者需要一种手段模拟一些动态语言才具有的特性,而且不失去自己安全类型的特性,相比cglib,javasist等相同功能的工具,bytebuddy 更容易上手且具有更高的性能。
本篇博客将根据官方文档,介绍bytebuddy的一些功能和特性的使用,也作为笔者自己的一个学习记录。
本篇博客所有的代码示例都基于bytebuddy的1.8.0版本
<dependency>
<groupId>net.bytebuddygroupId>
<artifactId>byte-buddyartifactId>
<version>1.8.0version>
dependency>
既然是字节码增强框架,那么作为入门的HelloWorld程序生成一个类用来演示是再好不过的了,ByteBuddy的Api设计相当优秀,我们只需要简单几行代码就可以生成一个自定义的类:
@Test
public void test() throws Exception {
Object helloWorld = new ByteBuddy()
.subclass(Object.class)
.name("com.tinysakura.HelloWorld")
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
}
我们也可以将生成的字节码输出到指定的文件观察
@Test
public void test() throws Exception {
new ByteBuddy()
.subclass(Object.class)
.name("com.tinysakura.HelloWorld")
.make()
.saveIn(new File("/Users/chenfeihao/Desktop"));
}
我们可以在这个路径下找到我们生成的class文件
/Users/chenfeihao/Desktop/com/tinysakura
反编译一下看看长什么样
package com.tinysakura;
public class HelloWorld {
public HelloWorld() {
}
}
除了构造方法光秃秃的什么都没有,接下来会一步步去充实生成的字节码文件。
我们在subClass中指定了父类为Object,而Object是所有java的父类所以在反编译的文件中没有显式的展现出来,那我们可以指定生成其它类型的子类吗?当然可以
@Test
public void test() throws Exception {
Object helloWorld = new ByteBuddy()
.subclass(Moo.class)
.name("com.tinysakura.HelloWorld")
.make()
.saveIn(new File("/Users/chenfeihao/Desktop/com/tinysakura"));
}
反编译的结果:
package com.tinysakura;
import com.tinysakura.bytebuddylearn.method.Moo;
public class HelloWorld extends Moo {
public HelloWorld() {
}
}
可以看到这次生成的class文件继承了我们指定的Moo类型,那么所有类型的类都可以被继承吗?
尝试去继承String类型:
Object helloWorld = new ByteBuddy()
.subclass(String.class)
直接抛出了一个异常:
java.lang.IllegalArgumentException: Cannot subclass primitive, array or final types: class java.lang.String
可以看到原基本类型(int,double,char…),数组类型和final类型的类不允许被继承,看来ByteBuddy并没有打破java的规范。
以上几个示例里我都指定了生成类的类名,其实我们也可以不指定,ByteBuddy提供了一套命名策略,我们也可以提供自定义的命名策略(比如生成的子类如果是父类的实现类,我们可以指定生成类的类名为父类名+impl),下面这个例子来自bytebuddy官方文档:
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
public String subclass(TypeDescription superClass) {
return "i.love.ByteBuddy." + superClass.getSimpleName();
}
})
.subclass(Object.class)
.make();
提供了一个NamingStrategy的实现,在自定义的i.love.ByteBuddy包下生成了与父类类名相同的子类。
可以看到bytebuddy生成类的代码相当简单,比起自己动手生成不知道简单了多少,而且十分灵活,看到这里是不是更想要了解
它了呢
比起生成一个全新的类,工作中更多的需求是对已有的类做修改或增强(大部分是第三方中间件甚至基础java类),bytebuddy自然对这部分能力做了支持
现在我们已经定义了一个HelloWorld类:
package com.tinysakura.bytebuddylearn.clazz;
public class HelloWorld {
String helloWorld() {
return "hello world";
}
}
简单的对其redefine一下:
import com.tinysakura.bytebuddylearn.clazz.HelloWorld;
public void test() throws Exception {
new ByteBuddy().redefine(HelloWorld.class)
.make()
.saveIn(new File("/Users/chenfeihao/Desktop/"));
}
最终我们会在如下路径找到重新生成的class文件
/Users/chenfeihao/Desktop/com/tinysakura/bytebuddylearn/clazz
反编译一下:
package com.tinysakura.bytebuddylearn.clazz;
public class HelloWorld {
public HelloWorld() {
}
String helloWorld() {
return "hello world";
}
}
好像和redefine之前的类没有什么区别?因为我们根本还什么都没做呀,后面的内容会介绍如何修改已存在的类,bytebuddy的redifine和rebase只能重定义没有被jvm加载的类,如果试图redifine已经加载的类,会抛出异常
java.lang.IllegalStateException: Cannot inject already loaded type: class java.lang.Object
bytebuddy提供了一个类型池的概念,我们不仅可以redifine classpath路径下的类,也可以从外部文件,jar包,网络redefine,我们需要一个ClassFileLocator去指导bytebuddy从那找到我们需要redefine的字节码文件:
ClassFileLocator fileLocator = ClassFileLocator.ForJarFile.of(new File("/Users/chenfeihao/Desktop/test.jar"));
TypePool typePool = TypePool.Default.of(fileLocator);
new ByteBuddy().redefine(typePool.describe("com.tinysakura.TestClassFileLocator").resolve(), fileLocator)
.make()
.load(ClassLoader.getSystemClassLoader());
前几章简单的介绍了bytebuddy的使用,但是好像不能满足我们使用字节码增强的核心诉求:增强。这章将从组成java类的两个基本属性fied,method开始,介绍如何对你的类做增强
还是从hello world开始,介绍类生成的时候我们已经生成了一个HellowWorld类,但是这个类除了构造方法没有任何方法,不会说hello world的hello world程序是没有灵魂的,我们通过简单几行代码给他注入灵魂:
@Test
public void test() throws Exception {
Object helloWorld = new ByteBuddy()
.subclass(Object.class)
.name("com.tinysakura.HelloWorld")
.defineMethod("helloWorld", String.class, Modifier.PUBLIC)
.intercept(FixedValue.value("hello world"))
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
Method method = helloWorld.getClass().getMethod("helloWorld");
System.out.println(method.invoke(helloWorld));
}
相比之前单纯生成类的代码,我们新加了defineMethod和interceptor两行代码
defineMethod
顾名思议,作用是定义一个方法,三个参数分别描述了方法名,返回值类型,方法可见性
如果我们希望方法接收参数,可以在defineMethod后通过withParameter方法指定
defineMethod("helloWorld", String.class, Modifier.PUBLIC).withParameter(String.class, "arg1")
interceptor
拦截方法,提供或修改实现,在我们的示例代码中我们拦截了自定义的helloWorld方法,然后定义他的返回值为我们期望的hello world,我们当然不只是可以返回一个固定的值,相反interceptor的机制非常灵活,我们可以钩入任何自定义的逻辑。
现实生产中需要自定义方法的情况是很少的,大部分需求是修改已有方法的逻辑,或者在已有方法中织入自定义的逻辑,而官方的示例就是从修改Object类型的toString方法开始的
String toString = new ByteBuddy()
.subclass(Object.class)
.name("example.Type")
.method(named("toString")).intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance()
.toString();
bytebuddy提供了一套ElementMatcher机制去匹配符合各种条件组合的方法(包括其他属性也是通过这套机制去匹配)
举几个例子
.method(ElementMatchers.isPublic())
.method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")))
.method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")).and(ElementMatchers.not(ElementMatchers.returns(String.class))))
.method(ElementMatchers.isPublic().and(ElementMatchers.named("helloWorld")).and(ElementMatchers.takesArgument(1, String.class)))
可以看出这套匹配机制十分灵活,限定范围除了上述还包括注解,是否属于static等诸多其它维度,可以满足绝大部分场景的匹配需求,但也因为ElementMatcher太过灵活,匹配的时候注意不要误匹配到了不想增强的方法导致bug
匹配到了方法后就要对方法做增强,helloWorld的示例中的增强有些简陋,返回固定值没法满足大部分场景的需求,我们的需求大多是比较复杂的,这时候就可以依靠bytebuddy的MethodDelegation机制,将匹配到的方法"委派"给定义好的实现
In many scenarios, returning a fixed value from a method is of course insufficient. For more flexibility, Byte Buddy provides the MethodDelegation implementation which offers maximal freedom in reacting to method calls. A method delegation defines a method of the dynamically created type to forward any call to another method which may live outside of the dynamic type. This way, a dynamic class’s logic can be represented using plain Java while only the binding to another method is achieved by code generation
MethodDelegation
直接通过示例进行学习,我们定义一个Moo类,他有两个方法:
public class Moo {
public String Moo1(String param1) {return "parm1:" + param1;}
public String Moo(String param1, Integer param2) {return "param1:" + param1 + ",param2:"+ param2;}
}
这两个个方法都已经有默认的实现,我们现在定义一个“委托类”去覆盖他们的实现:
public class DelegateMoo {
public static String Moo(String param1, Integer param2) {
return "my name is " + param1 + ",my age is " + param2;
}
public static String Moo1(String param1) {
return "my name is " + param1;
}
}
委托类中有两个和Moo中很类似的方法,接下来我们调用bytebuddy的api去实现覆盖
@Test
public void testMethodDelegation() throws Exception {
Moo moo = new ByteBuddy()
.subclass(Moo.class)
.method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
.intercept(MethodDelegation.to(DelegateMoo.class))
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
System.out.println("moo:" + moo.Moo("tinysakura", 21));
System.out.println("moo1:" + moo.Moo1("tinysakura"));
}
输出结果
可以看到被匹配到的Moo和Moo1方法的实现都被覆盖为了DelegateMoo中的实现,ByteBuddy会根据最相似匹配原则去匹配委托类中可以覆盖原方法的实现,因此即使我将DelegateMoo中的Moo方法改名为Foo:
public class DelegateMoo {
public static String Moo1(String param1) {
return "my name is " + param1;
}
public static String Foo(String param1, Integer param2) {
return "my name is " + param1 + ",my age is " + param2;
}
}
得到的依然是一样的结果,因为bytebuddy这个方法除了方法名以外的签名都和委托的Moo方法一致。根据最似匹配原则会使用Foo方法的实现进行委托,而当匹配到的委托方法在被委托类中找不到任何可以匹配到的方法时,bytebuddy会抛出异常,比如我们在委托类中加入一个重载的不接受任何参数的Moo1方法:
public class Moo {
public String Moo1() {return null;}
public String Moo1(String param1) {return "parm1:" + param1;}
public String Moo(String param1, Integer param2) {return "param1:" + param1 + ",param2:"+ param2;}
}
此时再执行下面这段代码就会报错
Moo moo = new ByteBuddy()
.subclass(Moo.class)
.method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
.intercept(MethodDelegation.to(DelegateMoo.class))
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
java.lang.IllegalArgumentException: None of [public static java.lang.String com.tinysakura.bytebuddylearn.method.DelegateMoo.Moo1(java.lang.String), public static java.lang.String com.tinysakura.bytebuddylearn.method.DelegateMoo.Foo(java.lang.String,java.lang.Integer)] allows for delegation from public java.lang.String com.tinysakura.bytebuddylearn.method.Moo.Moo1()
因为在DelegateMoo中找不到任何可以匹配无参Moo1的实现,此时使用者需要被委托类的实现保证委托方法可以顺利找到被委托的方法。
上面的例子说明delegate的机制已经十分灵活了,但不止于此,比如我们想通过bytebuddy实现一个类似切面的功能,在匹配到的方法调用前加入执行时刻的日志,此时我们需要知道到委托方法原本的实现,该如何做?bytebuddy已经帮你想好了
@Super
我们定义一个新的被委托类DelegateMooWithSuper,在被委托方法Moo1的入参中加入了一个使用了@Super注解的委托类对象,此时这个被注解的入参在进行最似匹配时会被忽略,bytebuddy匹配到的依然是只有一个String入参的委托方法。这里也体现了bytebuddy结合注解达到高使用灵活度的代码风格。
public class DelegateMooWithSuper {
public static String Moo1(String param1, @Super Moo zuper) {
System.out.println("invoke time:" + System.currentTimeMillis());
return zuper.Moo1(param1);
}
}
@Test
public void testMethodDelegation() throws Exception {
Moo moo = new ByteBuddy()
.subclass(Moo.class)
.method(ElementMatchers.named("Moo").or(ElementMatchers.named("Moo1")))
.intercept(MethodDelegation.to(DelegateMooWithSuper.class))
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
System.out.println("moo1:" + moo.Moo1("tinysakura"));
}
可以看到执行方法前打印了一行执行时间日志,我们这次没有在被委托方法中修改原方法的实现,所以依然使用的原方法的实现,通过这种机制我们可以在不破坏原方法逻辑的前提下,插入很多前置后置的逻辑,改变参数等,大大增加了字节码修改的灵活度。
@superCall
如果不需要修改入参,或者在被委托方法中调用超类其它方法,也可以使用@superCall注解
public static String Moo(String param1, Integer param2,
@SuperCall Callable<String> superCall) {
try {
String superCallResult = superCall.call();
return "wrapped by delegate:{" + superCallResult + "}";
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
调用callable的call方法可以获取被委托方法的实现结果。
bytebuddy还提供了一些api对一些特殊方法,如构造方法,接口的默认方法做操作,这部分内容可以直接参考官方文档,最后,bytebuddy允许在interceptor中通过MethodCall去调用一个特定的方法,可以调用的包括构造方法,方法描述,可以通过这种机制更灵活的替换委托方法的实现。
上一节我们看到如何通过bytebuddy生成、实现方法,作为java类另一个非常重要的部分,bytebuddy也提供了大量操作属性的api,我们先从最简单的定义一个属性开始:
@Test
public void testField() throws Exception {
Moo moo = new ByteBuddy()
.subclass(Moo.class)
.defineField("name", String.class)
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance();
System.out.println(moo.getClass().getDeclaredField("name") != null);
}
看看反编译的结果,生成的子类中定义了一个string类型的name属性。
接下来我想给这个name赋值,那么我们需要一个getter和setter方法,可以通过FieldAccessor方便的为类的属性生成对应的getter和setter方法,为此我们需要准备一个对应属性的interceptor接口:
public interface NameInterceptor {
String getName();
void setName(String name);
}
然后通过FieldAccessor为我们新声明的name属性加入getter/setter方法:
@Test
public void testField() throws Exception {
new ByteBuddy()
.subclass(Moo.class)
.defineField("name", String.class, Modifier.PUBLIC)
.implement(NameInterceptor.class).intercept(FieldAccessor.ofBeanProperty())
.make().saveIn(new File("/Users/chenfeihao/Desktop/"));
}
下面是反编译的结果
public class Moo$ByteBuddy$FDuX1G5O extends Moo implements NameInterceptor {
public String name;
public String getName() {
return this.name;
}
public void setName(String var1) {
this.name = var1;
}
public Moo$ByteBuddy$FDuX1G5O() {
}
}
@FieldValue
之后我们就可以通过反射给name注入值了,在后续方法的代理实现中也可以通过@FieldValue注解使用这个原本的类中不存在的属性
我们通过DelegateWithField代理原始类的Moo1方法,同时打印出传参和name属性的值
public class DelegateWithField {
String name;
public static String Moo1(String param1, @FieldValue(value = "name") String name) throws Exception {return "parm1:" + param1 + ";name:" + name;}
}
@Test
public void testField() throws Exception {
Moo moo = new ByteBuddy()
.subclass(Moo.class)
.defineField("name", String.class, Modifier.PUBLIC)
.implement(NameInterceptor.class).intercept(FieldAccessor.ofBeanProperty())
.method(ElementMatchers.named("Moo1"))
.intercept(MethodDelegation.to(DelegateWithField.class))
.make().load(ClassLoader.getSystemClassLoader())
.getLoaded().newInstance();
moo.getClass().getMethod("setName", String.class).invoke(moo, "tinysakura");
System.out.println(moo.getClass().getMethod("Moo1", String.class).invoke(moo, "param1"));
}
public class Moo$ByteBuddy$YNq6kjNU extends Moo implements NameInterceptor {
public String name;
public String Moo1(String var1) {
return DelegateWithField.Moo1(var1, this.name);
}
public String getName() {
return this.name;
}
public void setName(String var1) {
this.name = var1;
}
public Moo$ByteBuddy$YNq6kjNU() {
}
}
bytebuddy中使用了非常多的注解方便我们coding,当然也支持在字节码生成时加入注解,事实上注解在java代码中的使用非常普遍,bytebuddy提供的这些能力可以更方便的为增强的类提供能力
首先声明一个自定义的注解以及它的实现
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeDefinition {
}
public class RuntimeDefinitionImpl implements RuntimeDefinition {
@Override
public Class<? extends Annotation> annotationType() {
return RuntimeDefinition.class;
}
}
然后我们可以在任何声明,匹配到的属性或方法上加上我们自定义的注解
@Test
public void testAnnotation() throws Exception {
new ByteBuddy()
.subclass(Moo.class)
.defineField("name", String.class, Modifier.PUBLIC)
.annotateField(new RuntimeDefinitionImpl())
.method(ElementMatchers.named("Moo1"))
.intercept(MethodDelegation.to(DelegateMoo.class))
.annotateMethod(new RuntimeDefinitionImpl())
.make().saveIn(new File("/Users/chenfeihao/Desktop/"));
}
反编译结果:
public class Moo$ByteBuddy$n5Zp31xO extends Moo {
@RuntimeDefinition
public String name;
@RuntimeDefinition
public String Moo1(String var1) {
return DelegateMoo.Moo1(var1);
}
public Moo$ByteBuddy$n5Zp31xO() {
}
}
之前介绍类的redefine时特别说明了我们只能redefine尚未被加载的类,然而有些需求必须得对那些”已加载的类"做增强,java自己本身提供了一套java agent的机制在虚拟机启动前对字节码做增强,而byte buddy很好的对其做了支持,阅读这一节需要有java agent方面的基础,可以通过阅读JAVA SE 6新特性:Instrumentation了解
我们还是通过一个示例进行学习,内容是对线程做增强,打印出所有线程执行前后的时间戳,并在任务执行前输出一句hello world。
首先定义我们的preMain程序
public class BytebuddyAgent {
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
Class<?> bootStrapClass = Class.forName("com.tinysakura.bytebuddylearn.BootStrap", true, getClassLoader());
//Class> bootStrapClass = Class.forName("com.tinysakura.bytebuddylearn.BootStrap");
Method preMain = bootStrapClass.getMethod("preMain", String.class, Instrumentation.class);
preMain.invoke(null, agentArgs, inst);
}
public static ClassLoader getClassLoader(){
ClassLoader classLoader= Thread.currentThread().getContextClassLoader();
if (classLoader!=null){
return classLoader;
}
return ClassLoader.getSystemClassLoader();
}
}
agent启动的时候会执行preMain方法,我们在preMain方法中通过反射调用另外一个jar包中BootStrap类的同名preMain方法,注意这里将Instrumentation作为参数进行了传递
public static void preMain(String args, Instrumentation inst) {
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, module) -> builder.visit(Advice.to(Interceptor.class).on(ElementMatchers.named("start")));
new AgentBuilder.Default()
.disableClassFormatChanges()
.enableBootstrapInjection(inst, new File("/Users/chenfeihao/Desktop/lib"))
.ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.")))
.ignore(ElementMatchers.noneOf(Thread.class))
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(ElementMatchers.is(Thread.class))
.transform(transformer)
.with(new MyAgentBuilderListener())
.installOn(inst);
}
bootStrap的preMain方法看上去有点复杂,我们逐步拆解
先来看增强逻辑
public static class Interceptor {
@Advice.OnMethodEnter
public static void enter(@Advice.This Thread thread, @Advice.Origin Method origin) {
System.out.println("thread:" + thread.getName() + " enter thread timeMills:" + System.currentTimeMillis());
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
if (classLoader == null) {
try {
origin.invoke(null, thread);
} catch (Throwable e) {
e.printStackTrace();
}
return;
}
try {
Class reflectClass = Class.forName("com.tinysakura.bytebuddylearn.CustomThread", true, classLoader);
Method start = reflectClass.getMethod("start", Thread.class);
start.invoke(null, thread);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Advice.OnMethodExit
public static void exit(@Advice.This Thread thread, @Advice.Origin Method origin) {
System.out.println("thread:" + thread.getName() + " exit thread timeMills:" + System.currentTimeMillis());
}
}
通过@Advice.This注解我们可以拿到实际执行方法的对象,通过@Advice.Origin注解可以拿到原方法,我们首先在进入和退出方法前打印出当前线程的线程名和当前时间戳,其中在enter方法的实现中通过反射获取到了我们”增强“过的start方法交给thread执行,下面我们看这个增强的start方法是怎么实现的
public class CustomThread {
public static void start(Thread thread) {
try {
System.out.println("welcome to my custom thread");
Field field = Thread.class.getDeclaredField("target");
field.setAccessible(true);
Object targetValue = field.get(thread);
if (targetValue instanceof Runnable) {
Runnable runnable = (Runnable) targetValue;
field.set(thread, new CustomeRunnable(runnable));
}
} catch (Throwable e) {
System.out.println(e);;
}
}
}
首先打印出"welcome to my custom thread"标志走进了增强后的start方法,随后通过反射获取到thread的target属性,最后使用我们自己的CustomeRunnable对Runnable类型的target属性再做一次封装:
public class CustomeRunnable implements Runnable {
Runnable delegate;
public CustomeRunnable(Runnable delegate) {
this.delegate = delegate;
}
@Override
public void run() {
System.out.println("welcome to my custom thread");
delegate.run();
}
}
为了演示,CustomeRunnable做的事情和增强的start方法差不多,也只是简单打印”welcome to my custom thread“
现在我们对增强的Thread的期望是在执行runnable的逻辑前先打印一下当前线程名和时间戳,随后打印两次welcome to my custom thread,接着执行增强前的逻辑,最后退出线程打印当前线程名和时间戳,我们启动一个线程观察增强结果:
public static void main(String[] args) {
new Thread(() -> {
System.out.println("hello bytebuddy agent");
for (int i=0; i < 10000L; i++) {
}
}, "tinysakura").start();
}