Bootstrap

ByteBuddy



一、简介

ByteBuddy是基于ASM (ow2.io)实现的字节码操作类库。比起ASM,ByteBuddy的API更加简单易用。开发者无需了解class file format知识,也可通过ByteBuddy完成字节码编辑。

  • ByteBuddy使用java5实现,并且支持生成JDK6及以上版本的字节码(由于jdk6和jdk7使用未加密的HTTP类库, 作者建议至少使用jdk8版本)
  • 和其他字节码操作类库一样,ByteBuddy支持生成类和修改现存类
  • 与与静态编译器类似,需要在快速生成代码和生成快速的代码之间作出平衡,ByteBuddy主要关注以最少的运行时间生成代码
JIT优化后的平均ns纳秒耗时(标准差)基线Byte BuddycglibJavassistJava proxy
普通类创建0.003 (0.001)142.772 (1.390)515.174 (26.753)193.733 (4.430)70.712 (0.645)
接口实现0.004 (0.001)1’126.364 (10.328)960.527 (11.788)1’070.766 (59.865)1’060.766 (12.231)
stub方法调用0.002 (0.001)0.002 (0.001)0.003 (0.001)0.011 (0.001)0.008 (0.001)
类扩展0.004 (0.001)885.983 5’408.329 (7.901) (52.437)1’632.730 (52.737)683.478 (6.735)
super method invocation0.004 (0.001)0.004 0.004 (0.001) (0.001)0.021 (0.001)0.025 (0.001)

上表通过一些测试,对比各种场景下,不同字节码生成的耗时。对比其他同类字节码生成类库,Byte Buddy在生成字节码方面整体耗时还是可观的,并且生成后的字节码运行时耗时和基线十分相近。


1)Java 代理

Java 类库自带的一个代理工具包,它允许创建实现了一组给定接口的类。这个内置的代理很方便,但是受到的限制非常多。 例如,上面提到的安全框架不能以这种方式实现,因为我们想要扩展类而不是接口。

2)cglib

该代码生成库是在 Java 开始的最初几年实现的,不幸的是,它没有跟上 Java 平台的发展。尽管如此,cglib仍然是一个相当强大的库, 但它是否积极发展变得很模糊。出于这个原因,许多用户已不再使用它。

(cglib目前已不再维护,并且github中也推荐开发者转向使用Byte Buddy)

3)Javassist

该库带有一个编译器,该编译器采用包含 Java 源码的字符串,这些字符串在应用程序运行时被翻译成 Java 字节码。 这是非常雄心勃勃的,原则上是一个好主意,因为 Java 源代码显然是描述 Java 类的非常的好方法。但是, Javassist 编译器在功能上无法与 javac 编译器相比,并且在动态组合字符串以实现更复杂的逻辑时容易出错。此外, Javassist 带有一个代理库,它类似于 Java 的代理程序,但允许扩展类并且不限于接口。然而, Javassist 代理工具的范围在其API和功能方面同样受限限制。




二、常用API

1、入门使用

1)引入maven依赖

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.10.8</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2)创建代理类

package pers.mobian.bytebuddydemo.test;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.junit.Test;

import java.io.File;
import java.io.IOException;


public class Demo1 {

    private String path = Demo1.class.getClassLoader().getResource("").getPath();

    @Test
    public void test01() throws IOException {
        DynamicType.Unloaded<Object> make = new ByteBuddy()
                // 指定新生成代理类的父类
                .subclass(Object.class)
                // 创建
                .make();
        // 将生成的代理类保存到指定路径
        System.out.println("path  ==>   " + path);
        make.saveIn(new File(path));
    }
}

因为我们只进行了代理,所以代理对象内部只有一个代理方法


2、对类插桩

1)不指定任何特别的参数, 只声明为JDK自带类的子类

生成代理类全限定类名路径:net.bytebuddy.renamed.java.lang.Object B y t e B u d d y ByteBuddy ByteBuddyqm7vMycJ

@Test
public void test01() throws IOException {
    DynamicType.Unloaded<Object> make = new ByteBuddy()
            // 指定新生成代理类的父类
            .subclass(Object.class)
            // 创建
            .make();
    // 将生成的代理类保存到指定路径
    make.saveIn(new File(path));
}

2)指定父类为非JDK自带类, 不指定命名策略和其他参数

新建ByteBuddyHandler类,路径:pers.mobian.bytebuddydemo.handler.ByteBuddyHandler

public class ByteBuddyHandler {

    public String getName() {
        return "ByteBuddyHandler:____";
    }

    public String buildName(String name) {
        return "ByteBuddyHandler:" + name;
    }
}

生成代理类全限定类名路径:pers.mobian.bytebuddydemo.handler.ByteBuddyHandler B y t e B u d d y ByteBuddy ByteBuddyhCGT77X4

@Test
public void test01() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> make = new ByteBuddy()
            // 指定新生成代理类的父类
            .subclass(ByteBuddyHandler.class)
            // 创建
            .make();
    // 将生成的代理类保存到指定路径
    make.saveIn(new File(path));
}

3)指定父类为JDK自带类(ArrayList), 指定命名策略

使用官方教程建议的Byte Buddy自带的命名策略 (NamingStrategy.SuffixingRandom)

生成代理类全限定类名路径:net.bytebuddy.renamed.java.util.ArrayList$mobian$9sA0CzVt

@Test
public void test03() throws IOException {
    DynamicType.Unloaded<ArrayList> arrayListSubClass = new ByteBuddy()
            .with(new NamingStrategy.SuffixingRandom("mobian"))
            .subclass(ArrayList.class)
            .make();
    arrayListSubClass.saveIn(new File(path));
}

4)父类非JDK自带类, 指定命名策略和具体类名

生成代理类全限定类名路径:pers.mobian.bytebuddydemo.ByteBuddyHandlerWrapper

@Test
public void test04() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> handlerUnloaded = new ByteBuddy()
            // 因为name的存在,策略不生效
            .with(new NamingStrategy.SuffixingRandom("mobian"))
            .subclass(ByteBuddyHandler.class)
            .name("pers.mobian.bytebuddydemo.ByteBuddyHandlerWrapper")
            .make();
    handlerUnloaded.saveIn(new File(path));
}

5)将生成的字节码, 注入一个jar包中

生成代理类全限定类名路径:com.example.ByteBuddyHandlerWrapper

@Test
public void test07() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> handlerUnloaded = new ByteBuddy()
            .with(new NamingStrategy.SuffixingRandom("mobian"))
            .subclass(ByteBuddyHandler.class)
            // 指定类名
            .name("com.example.ByteBuddyHandlerWrapper")
            .make();
     File jarFile = new File( "/Users/mobian/person" + "/MobianProject-0.0.1-SNAPSHOT.jar");
    // 本地打开jar可以看到新生成的class文件也在其中
    handlerUnloaded.inject(jarFile);
}

6)修改/增强现有类主要有3种方法

  • .subclass(目标类.class):继承目标类,以子类的形式重写超类方法,达到增强效果
  • .rebase(目标类.class):变基,原方法变为private,并且方法名增加&origanl&{随机字符串}后缀,目标方法体替换为指定逻辑
  • .redefine(目标类.class):重定义,原方法体逻辑直接替换为指定逻辑

其中rebase无法直接在class中展示,具体效果需要通过bytecode进行查询


3、对方法插桩

@Test
public void test01() throws IOException, InstantiationException, IllegalAccessException {
    DynamicType.Unloaded<ByteBuddyHandler> handlerUnloaded = new ByteBuddy()
            .subclass(ByteBuddyHandler.class)
            // 指定拦截buildName方法
            .method(ElementMatchers.named("buildName"))
            // 拦截后返回
            .intercept(FixedValue.value("nothing to do"))
            .make();

    // 获取实例,执行方法
    Class<? extends ByteBuddyHandler> loaded = handlerUnloaded
            // 使用当前类的加载器进行加载
            .load(this.getClass().getClassLoader())
            .getLoaded();
    ByteBuddyHandler byteBuddyHandler = loaded.newInstance();
    System.out.printf("result ==> " + byteBuddyHandler.buildName("mobian"));
}

结果:


4、插桩插入

1)插入新方法

  • .defineMethod(方法名, 方法返回值类型, 方法访问描述符): 定义新增的方法
  • .withParameters(Type…): 定义新增的方法对应的形参类型列表
  • .intercept(XXX): 和修改/增强现有方法一样,对前面的方法对象的方法体进行修改
@Test
public void test02() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> redefine = new ByteBuddy()
            .redefine(ByteBuddyHandler.class)
            // 定义方法的 方法名, 方法返回值类型, 方法访问修饰符
            .defineMethod("createMethod", String.class, Modifier.PUBLIC | Modifier.STATIC)
            // 定义方法的形参
            .withParameters(String.class, Integer.class)
            // 定义方法体内逻辑
            .intercept(FixedValue.value("createMethod ret"))
            .name("pers.mobian.ByteBuddyHandlerWrapper")
            .make();
    redefine.saveIn(new File(path));
}

2)插入新属性

@Test
public void test03() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> redefine = new ByteBuddy()
            .redefine(ByteBuddyHandler.class)
            // 定义方法的 方法名, 方法返回值类型, 方法访问修饰符
            .defineField("createField", String.class, Modifier.PRIVATE | Modifier.STATIC)
            .name("pers.mobian.ByteBuddyHandlerWrapper")
            .make();
    redefine.saveIn(new File(path));
}

5、方法委托

方法委托,可简单理解将目标方法的方法体逻辑修改为调用指定的某个辅助类方法。

1)委托给相同签名的静态方法/实例方法

  • .intercept(MethodDelegation.to(Class<?> type)):将被拦截的方法委托给指定的增强类,增强类中需要定义和目标方法一致的方法签名,然后多一个static访问标识
  • .intercept(MethodDelegation.to(Object target)):将被拦截的方法委托给指定的增强类实例,增强类可以指定和目标类一致的方法签名,或通过@RuntimeType指示 Byte Buddy 终止严格类型检查以支持运行时类型转换。
public class ByteBuddyHandler {

    public ByteBuddyHandler() {
        System.out.println("ByteBuddyHandler construct");
    }

    public String getName() {
        return "ByteBuddyHandler:____";
    }

    public String buildStaticName(String name) {
        return "ByteBuddyHandler static:" + name;
    }

    public String buildName(String name) {
        return "ByteBuddyHandler:" + name;
    }
}
public class ByteBuddyHandlerDelegation01 {
    public static String buildStaticName(String name) {
        return "ByteBuddyHandlerDelegation01 static:" + name;
    }
}

public class ByteBuddyHandlerDelegation02 {
    public String buildName(String name) {
        return "ByteBuddyHandlerDelegation02 :" + name;
    }
}
@Test
public void test04() throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    DynamicType.Unloaded<ByteBuddyHandler> subClassUnloaded = new ByteBuddy()
            .subclass(ByteBuddyHandler.class)
            .method(ElementMatchers.named("buildStaticName"))
            // 将 ByteBuddyHandler#buildStaticName 方法委托给 ByteBuddyHandlerDelegation01 中的 相同方法签名(方法描述符)的静态方法 进行修改/增强
            .intercept(MethodDelegation.to(ByteBuddyHandlerDelegation01.class))
            .name("pers.mobian.ByteBuddyHandlerDelegation01Wrapper")
            .make();

    ByteBuddyHandler byteBuddyHandler = subClassUnloaded.load(getClass().getClassLoader())
                .getLoaded()
                .getConstructor()
                .newInstance();
    System.out.println("result ==> " + byteBuddyHandler.buildStaticName("mobian"));
}
@Test
public void test05() throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    DynamicType.Unloaded<ByteBuddyHandler> subClassUnloaded = new ByteBuddy()
            .subclass(ByteBuddyHandler.class)
            .method(ElementMatchers.named("buildName"))
            // 将 ByteBuddyHandler#buildName 方法委托给 ByteBuddyHandlerDelegation02 中的 相同方法签名(方法描述符)的静态方法 进行修改/增强
            .intercept(MethodDelegation.to(new ByteBuddyHandlerDelegation02()))
            .name("pers.mobian.ByteBuddyHandlerDelegation02Wrapper")
            .make();

    ByteBuddyHandler byteBuddyHandler = subClassUnloaded.load(getClass().getClassLoader())
            .getLoaded()
            .getConstructor()
            .newInstance();
    System.out.println("result ==> " + byteBuddyHandler.buildName("mobian"));
}

2)自定义方法

  • @RuntimeType:指示ByteBuddy终止严格类型检查以支持运行时类型转换
  • @This Object targetObj:表示被拦截的目标对象, 只有拦截实例方法时可用(无法拦截静态方法)
  • @Origin Method targetMethod:表示被拦截的目标方法, 只有拦截实例方法或静态方法时可用
  • @Origin Class clazz:获取静态方法所处的Class对象
  • @AllArguments Object[] targetMethodArgs:目标方法的参数
  • @Super Object targetSuperObj:表示被拦截的目标对象, 只有拦截实例方法时可用 (可用来调用目标类的super方法,无法拦截静态方法)。若明确知道具体的超类(父类类型),这里Object可以替代为具体超类(父类)
  • @SuperCall Callable<?> zuper:用于调用目标方法

其中调用目标方法时,需要通过Object result = zuper.call()调用。不能直接通过反射的Object result = targetMethod.invoke(targetObj,targetMethodArgs)进行原方法调用。因为后者会导致无限递归进入当前增强方法逻辑。

package pers.mobian.bytebuddydemo.delegation;

import net.bytebuddy.implementation.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;

public class ByteBuddyHandlerDelegation03 {

    /**
     * 使用@RuntimeType注解时,可以不需要和原目标方法保持相同的方法签名
     */
    @RuntimeType
    public Object selfMethodName(
            // 静态方法不可访问 @This Object targetObj,
            @This Object targetObj,
            @Origin Method targetMethod,
            // 静态方法对应的类class对象
            @Origin Class<?> clazz,
            @AllArguments Object[] targetMethodArgs,
            @Super Object targetSuperObj,
            // 若确定委托对象的超类(父类), 也可以用具体超类(父类)接收
            // 静态方法不可访问 @Super Object targetSuperObj,只能使用zuper
            // @Super ByteBuddyHandler targetSuperObj,
            @SuperCall Callable<?> zuper
    ) {
        System.out.println("委托对象@This ==> " + targetObj);
        System.out.println("被拦截的类@Origin#Class ==> " + clazz);
        System.out.println("被拦截的方法@Origin#Method ==> " + targetMethod);
        System.out.println("被拦截的方法参数列表@AllArguments ==> " + Arrays.toString(targetMethodArgs));
        System.out.println("委托对象@Super ==> " + targetSuperObj);
        System.out.println("真正调用方法@SuperCall ==> " + zuper);
        Object result = null;
        try {
            // 调用目标方法
            result = zuper.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
@Test
public void test6() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    DynamicType.Unloaded<ByteBuddyHandler> subClassUnloaded = new ByteBuddy()
            // 静态方式时需要使用rebase
            .subclass(ByteBuddyHandler.class)
            .method(ElementMatchers.named("buildName"))
            // 将 ByteBuddyHandler#buildName 方法委托给 SomethingInterceptor03 进行修改/增强
            .intercept(MethodDelegation.to(new ByteBuddyHandlerDelegation03()))
            .name("pers.mobian.ByteBuddyHandlerDelegation03Wrapper")
            .make();
    // 加载类
    ByteBuddyHandler byteBuddyHandler = subClassUnloaded.load(getClass().getClassLoader())
            .getLoaded()
            // 实例化并调用 selectUserName 方法验证是否被修改/增强
            .getConstructor()
            .newInstance();
    System.out.println("result ==> " + byteBuddyHandler.buildName("mobian"));
}

补充:

  1. @SuperCall仅在原方法仍存在的场合能够正常使用,比如subclass超类方法仍为目标方法,而rebase则是会重命名目标方法并保留原方法体逻辑;但redefine直接替换掉目标方法,所以@SuperCall不可用
  2. rebase和redefine都可以修改目标类静态方法,但是若想在原静态方法逻辑基础上增加其他增强逻辑,那么只有rebase能通过@SuperCall或@Morph调用到原方法逻辑;redefine不保留原目标方法逻辑

3)构造方法

  • .constructor(ElementMatchers.any()): 表示拦截目标类的任意构造方法
  • .intercept(SuperMethodCall.INSTANCE.andThen(Composable implementation): 表示在实例构造方法逻辑执行结束后再执行拦截器中定义的增强逻辑
  • @This: 被拦截的目标对象this引用,构造方法也是实例方法,同样有this引用可以使用
public class ByteBuddyHandlerDelegation05 {
    @RuntimeType
    public void constructEnhance(
            //  表示被拦截的目标对象, 在构造方法中同样是可用的(也是实例方法)
            @This Object targetObj) {
        System.out.println("constructEnhance() , " + targetObj);
    }
}
@Test
public void test07() throws IOException, InstantiationException, IllegalAccessException {
    DynamicType.Unloaded<ByteBuddyHandler> handlerUnloaded = new ByteBuddy()
            .subclass(ByteBuddyHandler.class)
            .constructor(ElementMatchers.any())
            // 拦截后返回
            .intercept(SuperMethodCall.INSTANCE
                    .andThen(MethodDelegation.to(new ByteBuddyHandlerDelegation05()))
            )
            .make();

    // 获取实例,执行方法
    Class<? extends ByteBuddyHandler> loaded = handlerUnloaded
            // 使用当前类的加载器进行加载
            .load(this.getClass().getClassLoader())
            .getLoaded();
    loaded.newInstance();
}

6、动态修改入参

@Morph和@SuperCall功能基本一致,主要区别在于@Morph支持传入参数。

使用@Morph时,需要在拦截方法注册代理类/实例前,指定install注册配合@Morph使用的函数式接口,其入参必须为Object[]类型,并且返回值必须为Object类型。

public interface MyCallable {
    
    Object apply(Object[] args);
}
package pers.mobian.bytebuddydemo.delegation;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;

public class ByteBuddyHandlerDelegation04 {

    /**
     * {@code @Morph}: 这个注解的工作方式与{@code @SuperCall}注解非常相似。然而,使用这个注解允许指定用于调用超类方法参数。
     * 注意, 仅当你需要调用具有与原始调用不同参数的超类方法时,才应该使用此注解,因为使用@Morph注解需要对所有参数装箱和拆箱。
     * 如果过你想调用一个特定的超类方法, 请考虑使用@Super注解来创建类型安全的代理。在这个注解被使用之前,需要显式地安装和注册,类似于@Pipe注解。
     * </p>
     */
    @RuntimeType
    public Object selfMethodName(
            // 目标方法的参数
            @AllArguments Object[] targetMethodArgs,
//            @SuperCall Callable<?> zuper
//             用于调用目标方法 (这里使用@Morph, 而不是@SuperCall, 才能修改入参)
            @Morph MyCallable zuper
    ) {
        Object result = null;
        try {
            // 修改参数
            if (null != targetMethodArgs && targetMethodArgs.length > 0) {
                String targetMethodArg = (String) targetMethodArgs[0];
                targetMethodArgs[0] = "after modify " + targetMethodArg;
            }
//             @SuperCall 不接受参数 
//             result = zuper.call();
            // 调用目标方法
            result = zuper.apply(targetMethodArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
@Test
public void test08() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    DynamicType.Unloaded<ByteBuddyHandler> subClassUnloaded = new ByteBuddy()
            .subclass(ByteBuddyHandler.class)
            .method(ElementMatchers.named("buildName"))
            .intercept(MethodDelegation
                    .withDefaultConfiguration()
                    // 向ByteBuddy 注册 用于中转目标方法入参和返回值的 函数式接口
                    .withBinders(Morph.Binder.install(MyCallable.class))
                    .to(new ByteBuddyHandlerDelegation04())
            )
            .name("pers.mobian.ByteBuddyHandlerDelegation04Wrapper")
            .make();
    ByteBuddyHandler byteBuddyHandler = subClassUnloaded.load(getClass().getClassLoader())
            .getLoaded()
            .getConstructor()
            .newInstance();
    System.out.println("result ==> " + byteBuddyHandler.buildName("mobian"));
}

7、清空方法体

@Test
public void test18() throws IOException {
    DynamicType.Unloaded<ByteBuddyHandler> allMethodIncludeSuper = new ByteBuddy()
            .redefine(ByteBuddyHandler.class)
            // 拦截所有方法(包括超类方法)
            .method(ElementMatchers.any())
            // 根据方法返回值类型, 返回对应类型的默认值
            .intercept(StubMethod.INSTANCE)
            .name("pers.mobian.ByteBuddyHandlerWrapper2")
            .make();
    allMethodIncludeSuper.saveIn(new File(path));
}



三、Java agent

1、原生JDK实现

1)引入maven依赖&指定打包方式

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.29.0-GA</version>
</dependency>

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>org.example.MainClass</mainClass>
                            <!-- 自动添加META-INF/MANIFEST.MF文件 -->
                            <addClasspath>true</addClasspath>
                            <!-- 将依赖的存放位置添加到 MANIFEST.MF-->
                            <classpathPrefix>/Users/mobian/person/ByteBuddyDemo/</classpathPrefix>
                        </manifest>
                        <manifestEntries>
                            <!-- MANIFEST.MF 配置项 -->
                            <Premain-Class>pers.test.bytebuddydemo.agent.PreMainClass</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                        </manifestEntries>
                    </archive>
                    <executions>
                        <execution>
                            <id>make-assembly</id>
                            <!-- 绑定到package生命周期 -->
                            <phase>package</phase>
                            <goals>
                                <!-- 只运行一次 -->
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </configuration>
            </plugin>
        </plugins>
    </build>

2)创建被调用接口

package pers.mobian.bytebuddydemo.health;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.mobian.bytebuddydemo.agent.HealthService;

@RestController
@RequestMapping("/test")
public class HealthController {

    @Autowired
    HealthService healthService;

    @RequestMapping("/health")
    public String health(String name) {
        return "health ==> " + healthService.returnHealth(name);
    }
}
package pers.mobian.bytebuddydemo.agent;

import org.springframework.stereotype.Service;

@Service
public class HealthService {
    public String returnHealth(String name) {
        return "health:" + name;
    }
}

3)编写Agent类

package pers.mobian.bytebuddydemo.agent;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class AgentTransformer implements ClassFileTransformer {
    /**
     * 在 某个类的字节码 被加载到JVM之前 都会先进入该方法. 如果对字节码进行修改则返回修改后的字节码, 否则直接返回null即可
     */
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] result = null;
        // 字节码中类名是使用/间隔, 而不是.
        if ("pers/mobian/bytebuddydemo/agent/HealthService".equals(className)) {
            System.out.println("transform start write method");
            try {
                ClassPool classPool = ClassPool.getDefault();
                CtClass ctClass = classPool.get("pers.mobian.bytebuddydemo.agent.HealthService");
                CtMethod healthServiceReturnHealth = ctClass.getDeclaredMethod("returnHealth",
                        new CtClass[]{classPool.get("java.lang.String")});
                healthServiceReturnHealth.insertBefore("System.out.println(\"insert before method\");");
                result = ctClass.toBytecode();
                System.out.println("transform over write method");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }
}

4)Agent启动类

package pers.mobian.bytebuddydemo.agent;

import java.lang.instrument.Instrumentation;

public class PreMainClass {

    /**
     * java agent启动的方式之一(另一个是agentmain), premain方法在main方法之前先执行, 是插桩入口
     *
     * @param arg             javaagent指定的参数
     * @param instrumentation jdk自带的工具类
     */
    public static void premain(String arg, Instrumentation instrumentation) {
        System.out.println("premain arg = " + arg);
        // 注册我们编写的 字节码转化器
        instrumentation.addTransformer(new AgentTransformer());
    }
}

5)当前项目maven完成打包,生产jar包

6)启动目标项目,启动vm参数指定agent的jar

-javaagent:/Users/mobian/person/ByteBuddyDemo/ByteBuddyDemo-0.0.1-SNAPSHOT.jar=name=mobian


2、ByteBuddy实现

参考



四、框架应用

SkyWalking、Mockito、Spring、Arthas等

;