Java字节码改写之asm进阶使用

在java世界里,字节码改写 + 反射可以让你变成"上帝",你可以完成任何你想做的事情,而字节码改写中asm是当之无愧的老大哥,对字节码认识不深的小伙伴可以看看我这篇文章

www.zhihu.com/question/75...

本文的目的是现有互联网上asm的资料不够体系和细致,其和传统java编程也非常的不一样,使用时有很多需要注意的地方,比如栈的平衡以及如何分析栈内操作数的状态。

接下来以我最近对随机数方法的实际字节码改写需求为例带大家熟悉整个字节码改写过程.

先描述下我的技术需求

复制代码
拦截所有生成随机数的方法,记录入参、调用类名、调用方法名、当前行号、方法内行号和出参
  • 桥接(代理)方法

    在字节码改写中非常重要的一个部分就是桥接(代理)方法,原来调用A方法,当你想做一些事情那么你可能需要将原有的调用修改为调用桥接方法,然后在桥接方法中实现你的处理逻辑

    java 复制代码
    public Object method() {
      // 业务处理逻辑
      invokeMethod(params);
      // 业务处理逻辑
    }
    java 复制代码
    public Object method() {
      // 业务处理逻辑
      invokeBrigeMethod(params...);
      // 业务处理逻辑
    }

    桥接方法是否是必须的呢?一般来说如果你的处理逻辑比较简单也是可以不需要桥接方法的,直接将相应的调用指令替换成处理指令,但一般来说如果处理没那么简单使用桥接方法可以使编程更简单,而且很多时候还有一些隐含的好处,比如可以维持原有业务代码的行号避免干扰业务排查问题,对业务类的字节码变更较小,可复用性更高等等。

    以上面的需求为例随机数的桥接方法被设计成这样

    java 复制代码
    // 一般来说桥接方法会设计成静态方法,这样在字节码修改时调用更加方便。
    // 第1个参数是Random实例,保证随机数结果依旧从原有的Random实例生成
    // 第2个参数是一个数组,因为随机数方法入参的个数是不一致,这里统一使用数组接收,这样就不用每个方法都对应一个桥接方法了
    // 第3个参数标识当前是哪个随机数方法,用来生成对应的结果
    // 第3个参数又是一个数组,里面是当前随机数方法的一些身份信息,这里主要是考虑到后续可能会新增新的参数,为了避免签名的修改这里使用数组
    public static Object invoke(Random random, Object[] request, RandomMethod randomMethod, Object[] randomIdentity);
  • 字节码改写

    标准使用姿势

    java 复制代码
    // classFileBuffer为原始字节码byte数组
    ClassReader classReader = new ClassReader(classFileBuffer);
    ClassWriter writer = new EasyClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES, loader);
    ClassVisitor visitor = new InvokeRandomTransformClassVisitor();
    classReader.accept(visitor, EXPAND_FRAMES);

    核心逻辑在InvokeRandomTransformClassVisitor

    java 复制代码
    public class InvokeRandomTransformClassVisitor {
    
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature,
          String[] exceptions) {
        // 跳过特殊方法、Object的内部方法、抽象方法、native方法
        return new InvokeRandomTransformMethodVisitor(api, visitor, access, name, desc);
      }
    }

    在做一系列的过滤之后引出InvokeRandomTransformMethodVisitor

    java 复制代码
    private class InvokeRandomTransformMethodVisitor extends AdviceAdapter {
    	
    	  private final String methodName;
        private int methodStartLineNumber = -1;
        private int currentLineNumber = -1;
        
        // 记录方法开始行数,用来计算方法内行数
        @Override
        public void visitLineNumber(int line, Label start) {
          if (this.methodStartLineNumber == -1) {
            this.methodStartLineNumber = line;
          }
          this.currentLineNumber = line;
          super.visitLineNumber(line, start);
        }
        
        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor,
            boolean isInterface) {
          if (INVOKEVIRTUAL == opcode) {
            if (根据owner、name和descriptor判断是否为随机数方法) {
              // 将Random方法的入参合并成一个数组压入操作数栈
    
              // 压入随机方法名
    
              // 创建一个大小为4的数组填入身份标识
    
    		  // 调用桥接方法
              invokeStatic(TYPE, INVOKE_METHOD);
              // 提取响应类型描述符做相应拆箱操作保证留在栈上的结果类型一致
              AsmClassUtil.unbox(this, Type.getReturnType(descriptor));
              return;
            }
          }
          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        }
      }
    }
    • 首先判断是否是随机数调用,通过指令和调用类、方法及其描述符来做判断,这里先判断是否是*INVOKEVIRTUAL ,这会过滤掉大多数无关指令,然后再根据*调用类、方法和描述符判断是否是随机数方法,一般来说先判断指令可以筛选掉很多无关的信息性能会更好。

    • 接下来将Random方法的入参合并成一个数组压入操作数栈,这个是整个改写中最难的部分

      java 复制代码
      public static void visitArgumentsAsArray(MethodVisitor methodVisitor, String descriptor) {
        // 解析方法描述符以获得参数类型
        // Parse the method descriptor to get the argument type
        Type[] argumentTypes = Type.getArgumentTypes(descriptor);
        int argumentCount = argumentTypes.length;
        // 创建一个指定大小的数组并压入栈
        // Create an array of a specified size and push it onto the stack
        if (argumentCount <= Byte.MAX_VALUE) {
          methodVisitor.visitIntInsn(BIPUSH, argumentCount);
        } else {
          methodVisitor.visitIntInsn(SIPUSH, argumentCount);
        }
        methodVisitor.visitTypeInsn(ANEWARRAY, OBJECT_JVM_CLASS_NAME);
        // 倒序遍历参数类型,将每个参数装箱并加入数组
        // Traverse the argument type in reverse order, box each argument and add it to the array
        for (int i = argumentCount - 1; i >= 0; i--) {
          Type type = argumentTypes[i];
          if (LONG_TYPE.equals(type) || DOUBLE_TYPE.equals(type)) {
            methodVisitor.visitInsn(DUP_X2);
          } else {
            methodVisitor.visitInsn(DUP_X1);
          }
          swap(methodVisitor, type);
          // 指定数组索引
          // Specify the array index
          if (i <= Byte.MAX_VALUE) {
            methodVisitor.visitIntInsn(BIPUSH, i);
          } else {
            methodVisitor.visitIntInsn(SIPUSH, i);
          }
          swap(methodVisitor, type);
      
          box(methodVisitor, type);
          methodVisitor.visitInsn(AASTORE);
        }
      }
      
      public static void swap(MethodVisitor methodVisitor, Type type) {
        if (LONG_TYPE.equals(type) || DOUBLE_TYPE.equals(type)) {
          methodVisitor.visitInsn(DUP_X2);
          methodVisitor.visitInsn(POP);
        } else {
          methodVisitor.visitInsn(SWAP);
        }
      }

      首先根据入参个数创建一个Object数组,数组的长度应该是原方法的参数个数,这里还有一个细节就是*BIPUSH*用于将一个字节(8位)的常量推送到操作数栈上,它的常量范围是从 -128 到 127,但理论上一个方法的入参最多可以可以有256个(随机数参数最多是3个),超过127时应该使用*SIPUSH ,对于当前场景BIPUSH 就够用了但为了后续可能会用于其它方法所以这里做了这个判断。*

      然后是 倒序遍历参数类型,将每个参数装箱并加入数组,和传统编程不一样的是大家一定要理解栈的特质------先入先出,所以此时留在栈顶的是原方法的最后一个参数,所以为了将参数按照原有顺序打包成数组应该是倒序地将参数存入数组,上面的字节码改写可能会让人眼花缭乱,我将会描述每个指令执行之后栈的状态大家会有更深刻的体会

      以最常用的public int nextInt(int bound)为例

      当执行到nextInt的*INVOKEVIRTUAL指令时栈的状态是这样的(可以看到栈顶是一个int )* 在执行*ANEWARRAY 后会新增一个数组(在栈顶)变成了这样*

      对于 int*由于它只占用一个Slot执行DUP_X1 让栈变成下面这样

      ps: 这里解释一下为什么这么做,因为后续将元素放入数组是要消耗三个操作数包括array本身的,所以这里使用DUP_X1对数组做一个备份

      使用swap*因为AASTORE 对操作数的顺序要求是数组,下标,元素

      使用*BIPUSHSIPUSH 设置下标(从后向前),这个因为只有一个参数所以是0*

      使用swap调整操作数顺序

      使用*AASTORE 设置数组,这会消耗最近的三个操作数*

      这样就将桥接方法的前两个参数准备好了,这里面还有一些细节:

      • 第一个是*box*(methodVisitor, type);,因为在java中数组只能存放引用类型无法存放基本类型,所以对于int这种需要装箱成包装类型,比如对于int 可以调用Integer.valueOf()
      java 复制代码
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
      • 第二个细节是对于int*reference 类型要交换它们只需要使用SWAP 指令即可,但如果入参是*long /double 这种2个Slot的类型则需要使用DUP_X2 + POP 来完成
      • 第三个细节是 多个参数第一次循环结束时栈的状态为*[..., random, 操作数, array] ,按照上述的处理继续把它们放入数组即可,保证最后留在栈顶的是array*
    • 然后是压入RandomMethod ,这是一个枚举值 所以使用*GETSTATIC 即可*

      java 复制代码
      visitFieldInsn(GETSTATIC,
          "RandomMethod的JVM描述符比如xxx/xxx/RandomMethod",
          randomMethod.name(),
          "RandomMethod的JVM返回描述符比如Lxxx/xxx/RandomMethod;");
    • 接下来是创建一个大小为4的数组填入身份标识,这个部分要简单一点

      java 复制代码
      visitLdcInsn(4);
      visitTypeInsn(ANEWARRAY, "java/lang/Object");
      // 填入类名
      visitInsn(DUP);
      visitLdcInsn(0);
      visitLdcInsn(className);
      visitInsn(AASTORE);
      // 填入方法名
      visitInsn(DUP);
      visitLdcInsn(1);
      visitLdcInsn(methodName);
      visitInsn(AASTORE);
      // 填入行号
      visitInsn(DUP);
      visitLdcInsn(2);
      visitLdcInsn(currentLineNumber);
      // 基本类型int转换为包装类型Integer,因为数组只能存放包装类型
      AsmClassUtil.box(this, INT_TYPE);
      visitInsn(AASTORE);
      // 填入方法内行号 (方法的第几行)
      visitInsn(DUP);
      visitLdcInsn(3);
      visitLdcInsn(currentLineNumber - methodStartLineNumber);
      // 基本类型int转换为包装类型Integer,因为数组只能存放包装类型
      AsmClassUtil.box(this, INT_TYPE);
      visitInsn(AASTORE);

      值得注意的还是基本类型要转换为包装类型,同时除了传统的行号以外,这里面额外记录了方法内行号(方法的第几行),作为一个相对坐标它的鲁棒性相比于文件行号要好很多。

    • 最后调用桥接方法并做相应拆箱操作保证留在栈上的结果类型一致,基本类型和包装类型的拆装箱也是我们作字节码改写时要时刻注意的地方

  • 代码验证

    由于字节码改写的复杂性以及无法debug的特殊性,验证测试变成了重中之重,毕竟很难有人能一次性就完成考虑各种场景的代码还没有问题,下面就是一个典型的验证测试

    java 复制代码
    public class InvokeRandomTransformClassVisitorTest extends ClassLoader {
    
      public Class<?> loadClassFromBytes(String className, byte[] classBytes) {
        return defineClass(className, classBytes, 0, classBytes.length);
      }
    
      public static void main(String[] args) throws Exception {
        String classPath = "原始class文件路径";
        String outputClassPath = "改写后class文件保存路径";
        executeByteCodeRewrite(classPath, outputClassPath);
      }
    
      private static void executeByteCodeRewrite(String classpath, String outputClassPath)
          throws Exception {
        // 从class文件中读取字节数组
        byte[] classFileBuffer = Files.readAllBytes(Paths.get(classpath));
        byte[] modifiedClassBytes = 调用InvokeRandomTransformClassVisitor做字节码改写;
        // 将修改后的字节数组写入到指定路径的文件中
        try (FileOutputStream fos = new FileOutputStream(outputClassPath)) {
          fos.write(modifiedClassBytes);
        }
        System.out.println("Class transformed and written to file: " + outputClassPath);
    
    		// 加载修改后的类并使用反射调用方法验证结果
        Class<?> clazz = new InvokeRandomTransformClassVisitorTest().loadClassFromBytes("类名", modifiedClassBytes);
        Object instance = clazz.getDeclaredConstructor().newInstance();
        Method method = clazz.getMethod("testRandom", String.class);
        System.out.println(method.invoke(instance, ""));
      }
    
    }

    其实无非就是将改写后的字节码输出到新的文件这样可以再通过IDE反编译获取改写后的java文件

    再将修改后的字节码加载到JVM中通过反射调用验证其逻辑

    一个是看一个是验证执行结果,看通常可以让你直接明白哪里的操作有问题,操作数的位置不对,但即使看上去没问题执行也不一定就ok就比如上面提到的拆装箱问题仅仅从看是很难发现的

    分析报错也是非常重要的一环

    java 复制代码
    Exception in thread "main" java.lang.VerifyError: Bad type on operand stack in aastore
    Exception Details:
      Location:
        xxx/xxx/InvokeRandomTransformClassVisitorEntity.testRandom(Ljava/lang/String;)Ljava/util/List; @54: aastore
      Reason:
        Type 'xxx/xxx/RandomMethod' (current frame, stack[3]) is not assignable to reference type
      Current Frame:
        bci: @54
        flags: { }
        locals: { 'xxx/xxx/InvokeRandomTransformClassVisitorEntity', 'java/lang/String', 'java/util/Random', 'java/util/ArrayList' }
        stack: { 'java/util/Random', '[Ljava/lang/Object;', 'xxx/xxx/RandomMethod', 'xxx/xxx/RandomMethod', integer, 'java/lang/String' }
      Bytecode:
        0x0000000: bb00 1559 b700 164d bb00 1859 b700 194e
        0x0000010: 2c14 001a 1001 bd00 045b 5b57 1000 5b57
        0x0000020: b800 2153 b200 2712 28bd 0004 1229 122b
        0x0000030: 5359 122c 122d 5359 122e 122f b800 3453
        0x0000040: 5912 3512 2eb8 0034 53b8 003b c000 3d2d
        0x0000050: 59b6 0041 57ba 0055 0000 b900 5902 002d
        0x0000060: 2c11 03e8 1001 bd00 045a 5f10 005f b800
        0x0000070: 3453 b200 5c12 28bd 0004 1229 122b 5359
        0x0000080: 122c 122d 5359 122e 125d b800 3453 5912
        0x0000090: 3512 35b8 0034 53b8 003b c000 31b6 0061
        0x00000a0: b800 34b9 0049 0200 572d b800 6711 03e8
        0x00000b0: 1001 bd00 045a 5f10 005f b800 3453 b200
        0x00000c0: 6a12 28bd 0004 1229 122b 5359 122c 122d
        0x00000d0: 5359 122e 126b b800 3453 5912 3512 28b8
        0x00000e0: 0034 53b8 003b c000 31b6 0061 b800 34b9
        0x00000f0: 0049 0200 572d b800 6710 00bd 0004 b200
        0x0000100: 6e12 28bd 0004 1229 122b 5359 122c 122d
        0x0000110: 5359 122e 126f b800 3453 5912 3512 70b8
        0x0000120: 0034 53b8 003b c000 31b6 0061 b800 34b9
        0x0000130: 0049 0200 572d b800 6704 100a 1002 bd00
        0x0000140: 045a 5f10 015f b800 3453 5a5f 1000 5fb8
        0x0000150: 0034 53b2 0073 1228 bd00 0412 2912 2b53
        0x0000160: 5912 2c12 2d53 5912 2e12 74b8 0034 5359
        0x0000170: 1235 1275 b800 3453 b800 3bc0 0031 b600
        0x0000180: 61b8 0034 b900 4902 0057 2db8 0067 1000
        0x0000190: bd00 04b2 0078 1228 bd00 0412 2912 2b53
        0x00001a0: 5912 2c12 2d53 5912 2e12 79b8 0034 5359
        0x00001b0: 1235 127a b800 3453 b800 3bc0 007c b600
        0x00001c0: 80b8 0083 b900 4902 0057 2db8 0067 1000
        0x00001d0: bd00 04b2 0086 1228 bd00 0412 2912 2b53
        0x00001e0: 5912 2c12 2d53 5912 2e12 87b8 0034 5359
        0x00001f0: 1235 1288 b800 3453 b800 3bc0 001d b600
        0x0000200: 8cb8 0021 b900 4902 0057 1010 bc08 3a04
        0x0000210: bb00 8e59 b700 8f19 0410 01bd 0004 5a5f
        0x0000220: 1000 5f53 b200 9212 28bd 0004 1229 122b
        0x0000230: 5359 122c 122d 5359 122e 1293 b800 3453
        0x0000240: 5912 3512 94b8 0034 53b8 003b 2d19 04b9
        0x0000250: 0049 0200 572d b0                      
    
    	at java.lang.Class.getDeclaredConstructors0(Native Method)
    	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
    	at java.lang.Class.getConstructor0(Class.java:3075)
    	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    	at xxx.xxx.InvokeRandomTransformClassVisitorTest.executeByteCodeRewrite(InvokeRandomTransformClassVisitorTest.java:46)
    	at xxx.xxx.InvokeRandomTransformClassVisitorTest.main(InvokeRandomTransformClassVisitorTest.java:27)

    这个就是非常典型的报错,它的含义是在执行aastore时操作数的类型不对,Reason里面是更详细的信息:栈中下标为3的元素不是一个引用类型,这个时候你就需要检查栈的操作数,确保aastore指令执行时栈上前3个操作数是数组、下标、元素。

    在排查问题时像我上面教的那样分析栈的状态,确定操作数的位置,这可以让你少走很多弯路。

好了,到这里如果你真的有在细心阅读且跟着我的例子实践了一遍,那么恭喜你你在asm使用上已经入门了,很多字节码改写对你而言将没有秘密,从此你将自由穿梭在java世界之中而没有什么可以阻拦你。

但修行在个人,jvm的庞大世界才刚刚向你展开,享受且继续努力吧。 原文地址:pebble-skateboard-d46.notion.site/Java-asm-19...

附件(完整代码)

EasyClassWriter

java 复制代码
public class EasyClassWriter extends ClassWriter {

  private final ClassLoader classLoader;

  public EasyClassWriter(int flags, ClassLoader classLoader) {
    super(flags);
    this.classLoader = classLoader;
  }

  @Override
  public String getCommonSuperClass(String type1, String type2) {
    try {
      String className1 = type1.replace("/", ".");
      String className2 = type2.replace("/", ".");
      if (classLoader != null) {
        // 从字节流中读读取公共superClass
        String[] type1SupperArr = AsmClassUtil.readSuperClass(type1, classLoader);
        String[] type2SupperArr = AsmClassUtil.readSuperClass(type2, classLoader);
        for (int i = type1SupperArr.length; i >= 0; i--) {
          for (int j = type2SupperArr.length; j >= 0; j--) {
            String type1Idx = i == type1SupperArr.length ? className1 : type1SupperArr[i];
            String type2Idx = j == type2SupperArr.length ? className2 : type2SupperArr[j];
            if (type1Idx.equals(type2Idx)) {
              return type1Idx.replace('.', '/');
            }
          }
        }
      }
    } catch (Throwable ignored) {
    }
    return OBJECT_JVM_CLASS_NAME;
  }

}

InvokeRandomTransformClassVisitor

java 复制代码
public class InvokeRandomTransformClassVisitor extends TransformClassVisitor {

  private static final Type TYPE = Type.getType(RandomMethodBridge.class);
  private static final String RANDOM_METHOD_NAME = RandomMethod.class.getName();
  private static final Method INVOKE_METHOD = Method.getMethod(
      String.format("Object invoke(java.util.Random,Object[],%s,Object[])", RANDOM_METHOD_NAME));
  private static final String RANDOM_METHOD_JVM_NAME = RANDOM_METHOD_NAME.replace(".", "/");
  private static final String RANDOM_METHOD_JVM_RETURN_NAME = "L" + RANDOM_METHOD_JVM_NAME + ";";

  public InvokeRandomTransformClassVisitor(ClassVisitor classVisitor) {
    super(AsmCompatible.apiVersion(), classVisitor);
  }

  @Override
  public MethodVisitor visitMethod(int access, String name, String desc, String signature,
      String[] exceptions) {
    MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);
    // 跳过特殊方法、Object的内部方法、抽象方法、native方法
    if (ExtensionUtil.ignoreMethod(name) || AsmClassUtil.isObjectMethod(name, desc)
        || Modifier.isAbstract(access) || Modifier.isNative(access)) {
      return visitor;
    }

    // 当前方法需要增强
    if (RandomUtil.shouldTransformMethod(className, name)) {
      return new InvokeRandomTransformMethodVisitor(api, visitor, access, name, desc);
    }

    return visitor;
  }

  private class InvokeRandomTransformMethodVisitor extends AdviceAdapter {

    private final String methodName;
    private int methodStartLineNumber = -1;
    private int currentLineNumber = -1;

    protected InvokeRandomTransformMethodVisitor(int api, MethodVisitor methodVisitor, int access,
        String name, String descriptor) {
      super(api, methodVisitor, access, name, descriptor);
      this.methodName = name;
    }

    @Override
    public void visitLineNumber(int line, Label start) {
      if (this.methodStartLineNumber == -1) {
        this.methodStartLineNumber = line;
      }
      this.currentLineNumber = line;
      super.visitLineNumber(line, start);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor,
        boolean isInterface) {
      if (INVOKEVIRTUAL == opcode) {
        RandomMethod randomMethod = RandomMethod.getByMethodIdentity(
            MethodSignatureParser.toNormalClass(owner), name, descriptor);
        if (null != randomMethod) {
          result.setEnhanced(true);

          // 将Random方法的入参合并成一个数组压入操作数栈
          AsmClassUtil.visitArgumentsAsArray(this, descriptor);

          // 压入随机方法名
          visitFieldInsn(GETSTATIC,
              RANDOM_METHOD_JVM_NAME,
              randomMethod.name(),
              RANDOM_METHOD_JVM_RETURN_NAME);

          // 创建一个大小为4的数组
          visitLdcInsn(4);
          visitTypeInsn(ANEWARRAY, "java/lang/Object");
          // 填入类名
          visitInsn(DUP);
          visitLdcInsn(0);
          visitLdcInsn(className);
          visitInsn(AASTORE);
          // 填入方法名
          visitInsn(DUP);
          visitLdcInsn(1);
          visitLdcInsn(methodName);
          visitInsn(AASTORE);
          // 填入行号
          visitInsn(DUP);
          visitLdcInsn(2);
          visitLdcInsn(currentLineNumber);
          // 基本类型int转换为包装类型Integer,因为数组只能存放包装类型
          AsmClassUtil.box(this, INT_TYPE);
          visitInsn(AASTORE);
          // 填入方法内行号 (方法的第几行)
          visitInsn(DUP);
          visitLdcInsn(3);
          visitLdcInsn(currentLineNumber - methodStartLineNumber);
          // 基本类型int转换为包装类型Integer,因为数组只能存放包装类型
          AsmClassUtil.box(this, INT_TYPE);
          visitInsn(AASTORE);

          invokeStatic(TYPE, INVOKE_METHOD);
          // 提取响应类型描述符
          AsmClassUtil.unbox(this, Type.getReturnType(descriptor));
          return;
        }
      }
      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
  }

}

TransformClassVisitor

java 复制代码
public class TransformClassVisitor extends ClassVisitor {

  protected final int api;
  protected final TransformResult result;
  protected String className;

  public TransformClassVisitor(int api, ClassVisitor classVisitor) {
    super(api, classVisitor);
    this.api = api;
    this.result = TransformResult.notEnhanced();
  }

  @Override
  public void visit(int apiVersion, int access, String name, String signature, String superName,
      String[] interfaces) {
    this.className = name.replace("/", ".");
    super.visit(apiVersion, access, name, signature, superName, interfaces);
  }

  public TransformResult getTransformResult() {
    return result;
  }

}

TransformResult

java 复制代码
public class TransformResult {

  private boolean enhanced = false;
  private byte[] classBuffer = null;

  public static TransformResult notEnhanced() {
    TransformResult transformResult = new TransformResult();
    transformResult.enhanced = false;
    return transformResult;
  }

  public static TransformResult enhanced(byte[] classBuffer) {
    TransformResult transformResult = new TransformResult();
    transformResult.enhanced = true;
    transformResult.classBuffer = classBuffer;
    return transformResult;
  }

  public boolean isEnhanced() {
    return enhanced;
  }

  public byte[] getClassBuffer() {
    return classBuffer;
  }

  public void setEnhanced(boolean enhanced) {
    this.enhanced = enhanced;
  }

  public void setClassBuffer(byte[] classBuffer) {
    this.classBuffer = classBuffer;
  }

}

AsmClassUtil

java 复制代码
public class AsmClassUtil {

  private static final Map<Object, Map<String, String[]>> LOADER_SUPER_CLASS_CACHE_MAP = new IdentityHashMap<>();

  private static final Lock SUPPER_CLASS_CACHE_INIT_LOCK = new ReentrantLock();
  private static final Lock SUPER_CLASS_ARR_INIT_LOCK = new ReentrantLock();
  public static final String OBJECT_JVM_CLASS_NAME = "java/lang/Object";

  /**
   * @author: Ares
   * @description: 包装类型拆箱
   * @description: box type unboxing
   * @time: 2025-02-11 17:38:30
   * @params: [methodVisitor, descriptor] 方法访问者,描述符
   */
  public static void unboxing(MethodVisitor methodVisitor, String descriptor) {
    unboxing(methodVisitor, Type.getType(descriptor));
  }

  /**
   * @author: Ares
   * @description: 包装类型拆箱
   * @description: box type unboxing
   * @time: 2025-01-23 22:10:15
   * @params: [methodVisitor, descriptor, isMethod] 方法访问者,描述符, 是否是方法
   */
  public static void unboxing(MethodVisitor methodVisitor, Type type) {
    switch (type.getSort()) {
      case Type.BOOLEAN:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z",
            false);
        break;
      case Type.BYTE:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Byte");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
        break;
      case Type.CHAR:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Character");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C",
            false);
        break;
      case Type.SHORT:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Short");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
        break;
      case Type.INT:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Integer");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
        break;
      case Type.FLOAT:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Float");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
        break;
      case Type.LONG:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Long");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
        break;
      case Type.DOUBLE:
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Double");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D",
            false);
        break;
      case Type.VOID:
        break;
      default:
        String descriptor = type.getDescriptor();
        char c = descriptor.charAt(0);
        if (c == '[') {
          methodVisitor.visitTypeInsn(CHECKCAST, descriptor);
        } else {
          methodVisitor.visitTypeInsn(CHECKCAST, descriptor.substring(1, descriptor.length() - 1));
        }
    }
  }

  /**
   * @author: Ares
   * @description: 基本类型装箱
   * @description: base type boxing
   * @time: 2025-01-23 22:10:15
   * @params: [methodVisitor, descriptor] 方法访问者,描述符
   */
  public static void boxing(MethodVisitor methodVisitor, String descriptor) {
    Type type = Type.getType(descriptor);
    boxing(methodVisitor, type);
  }

  public static void boxing(MethodVisitor methodVisitor, Type type) {
    // 根据参数类型进行加载和装箱
    // load and box according to the parameter type
    switch (type.getSort()) {
      case Type.INT:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf",
            "(I)Ljava/lang/Integer;", false);
        break;
      case Type.BOOLEAN:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf",
            "(Z)Ljava/lang/Boolean;", false);
        break;
      case Type.CHAR:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf",
            "(C)Ljava/lang/Character;", false);
        break;
      case Type.BYTE:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf",
            "(B)Ljava/lang/Byte;", false);
        break;
      case Type.SHORT:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf",
            "(S)Ljava/lang/Short;", false);
        break;
      case Type.LONG:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf",
            "(J)Ljava/lang/Long;", false);
        break;
      case Type.FLOAT:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf",
            "(F)Ljava/lang/Float;", false);
        break;
      case Type.DOUBLE:
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf",
            "(D)Ljava/lang/Double;", false);
        break;
      case Type.OBJECT:
      case Type.ARRAY:
      case Type.VOID:
        break;
      default:
        throw new IllegalArgumentException("Unsupported argument type: " + type);
    }
  }

  /**
   * @author: Ares
   * @description: 将原方法的入参合并成一个数组传给桥接方法
   * @description: Merge the original method's arguments into an array and pass them to the bridge
   * method
   * @time: 2025-02-11 17:55:36
   * @params: [methodVisitor, descriptor] 方法访问者,描述符
   */
  public static void visitArgumentsAsArray(MethodVisitor methodVisitor, String descriptor) {
    /**
     [..., random, int]
     ANEWARRAY [..., random, int, array]
     DUP_X1    [..., random, array, int, array]
     SWAP      [..., random, array, array, int]
     *PUSH     [..., random, array, array, int, 0]
     SWAP      [..., random, array, array, 0, int]
     AASTORE   [..., random, array]

     [..., random, int, int]
     ANEWARRAY [..., random, int, int, array]
     DUP_X1    [..., random, int, array, int, array]
     SWAP      [..., random, int, array, array, int]
     *PUSH     [..., random, int, array, array, int, 1]
     SWAP      [..., random, int, array, array, 1, int]
     AASTORE   [..., random, int, array]
     DUP_X1    [..., random, array, int, array]
     SWAP      [..., random, array, array, int]
     *PUSH     [..., random, array, array, int, 0]
     SWAP      [..., random, array, array, 0, int]
     AASTORE   [..., random, array]

     [..., random, long]
     ANEWARRAY [..., random, long, array]
     DUP_X2    [..., random, array, long, array]
     DUP_X2    [..., random, array, array, long, array]
     POP       [..., random, array, array, long]
     *PUSH     [..., random, array, array, long, 0]
     DUP_X2    [..., random, array, array, 0, long, 0]
     POP       [..., random, array, array, 0, long]
     AASTORE   [..., random, array]
     */
    // 解析方法描述符以获得参数类型
    // Parse the method descriptor to get the argument type
    Type[] argumentTypes = Type.getArgumentTypes(descriptor);
    int argumentCount = argumentTypes.length;
    // 创建一个指定大小的数组并压入栈
    // Create an array of a specified size and push it onto the stack
    if (argumentCount <= Byte.MAX_VALUE) {
      methodVisitor.visitIntInsn(BIPUSH, argumentCount);
    } else {
      methodVisitor.visitIntInsn(SIPUSH, argumentCount);
    }
    methodVisitor.visitTypeInsn(ANEWARRAY, OBJECT_JVM_CLASS_NAME);
    // 倒序遍历参数类型,将每个参数装箱并加入数组
    // Traverse the argument type in reverse order, box each argument and add it to the array
    for (int i = argumentCount - 1; i >= 0; i--) {
      Type type = argumentTypes[i];
      if (LONG_TYPE.equals(type) || DOUBLE_TYPE.equals(type)) {
        methodVisitor.visitInsn(DUP_X2);
      } else {
        methodVisitor.visitInsn(DUP_X1);
      }
      swap(methodVisitor, type);
      // 指定数组索引
      // Specify the array index
      if (i <= Byte.MAX_VALUE) {
        methodVisitor.visitIntInsn(BIPUSH, i);
      } else {
        methodVisitor.visitIntInsn(SIPUSH, i);
      }
      swap(methodVisitor, type);

      box(methodVisitor, type);
      methodVisitor.visitInsn(AASTORE);
    }
  }

  /**
   * @author: Ares
   * @description: 根据类型交换栈顶元素
   * @description: Exchange the top element of the stack according to the type
   * @time: 2025-02-12 18:46:54 
   * @params: [methodVisitor, type] 方法访问者,类型
   */
  public static void swap(MethodVisitor methodVisitor, Type type) {
    if (LONG_TYPE.equals(type) || DOUBLE_TYPE.equals(type)) {
      methodVisitor.visitInsn(DUP_X2);
      methodVisitor.visitInsn(POP);
    } else {
      methodVisitor.visitInsn(SWAP);
    }
  }

  /**
   * @author: Ares
   * @description: 读取父类数组
   * @description: read super class array
   * @time: 2025-02-11 17:36:58
   * @params: [className, loader] 类名,类加载器
   * @return: java.lang.String[] 父类数组
   */
  public static String[] readSuperClass(String className, ClassLoader loader) throws IOException {
    return readSuperClass(className, loader, null);
  }

  /**
   * @author: Ares
   * @description: 读取父类数组
   * @description: read super class array
   * @time: 2025-02-11 17:36:58
   * @params: [className, loader, classBytes] 类名,类加载器,类字节码
   * @return: java.lang.String[] 父类数组
   */
  public static String[] readSuperClass(String className, ClassLoader loader, byte[] classBytes)
      throws IOException {
    if (loader == null) {
      return null;
    }

    Map<String, String[]> supperClassCacheMap = LOADER_SUPER_CLASS_CACHE_MAP.get(loader);
    if (supperClassCacheMap == null) {
      SUPPER_CLASS_CACHE_INIT_LOCK.lock();
      try {
        // 类加载器的父类加载器一般不会超过4层(根加载器是BootStrap加载器)
        // The parent class loader of the class loader generally does not exceed 4 layers (the root loader is the BootStrap loader)
        supperClassCacheMap = MapUtil.newConcurrentMap(4);
        LOADER_SUPER_CLASS_CACHE_MAP.put(loader, supperClassCacheMap);
      } finally {
        SUPPER_CLASS_CACHE_INIT_LOCK.unlock();
      }
    }

    String[] superClassArr = supperClassCacheMap.get(className);
    if (superClassArr == null) {
      SUPER_CLASS_ARR_INIT_LOCK.lock();
      try {
        superClassArr = supperClassCacheMap.get(className);
        if (superClassArr == null) {
          superClassArr = innerReadSuperClass(className, loader, classBytes);
          if (superClassArr == null) {
            superClassArr = new String[0];
          }
          supperClassCacheMap.put(className, superClassArr);
        }
      } finally {
        SUPER_CLASS_ARR_INIT_LOCK.unlock();
      }
    }
    return superClassArr;
  }

  private static String[] innerReadSuperClass(String className, ClassLoader loader,
      byte[] classBytes) throws IOException {
    InputStream inputStream;
    if (null != classBytes) {
      inputStream = new ByteArrayInputStream(classBytes);
    } else {
      inputStream = loader.getResourceAsStream(className.replace(".", "/") + ".class");
    }
    if (null == inputStream) {
      return null;
    }
    ClassReader classReader = new ClassReader(inputStream);
    String superClassName = classReader.getSuperName();

    if (null != superClassName && !OBJECT_JVM_CLASS_NAME.equals(superClassName)) {
      String superClassNameReal = superClassName.replace("/", ".");
      String[] superClasses = readSuperClass(superClassName, loader);

      if (null != superClasses && superClasses.length > 0) {
        String[] mergedSuperClasses = new String[superClasses.length + 1];
        System.arraycopy(superClasses, 0, mergedSuperClasses, 0, superClasses.length);
        mergedSuperClasses[superClasses.length] = superClassNameReal;
        return mergedSuperClasses;
      } else {
        return new String[]{superClassNameReal};
      }
    }
    return null;
  }

  public static String forPath(String className) {
    return className.replaceAll("\\.", "/").concat(".class");
  }

  public static boolean isObjectMethod(String methodName, String methodDesc) {
    if ("hashCode".equals(methodName) && "()I".equals(methodDesc)) {
      return true;
    }
    if ("equals".equals(methodName) && "(Ljava/lang/Object;)Z".equals(methodDesc)) {
      return true;
    }
    if ("clone".equals(methodName) && "()Ljava/lang/Object;".equals(methodDesc)) {
      return true;
    }
    if ("finalize".equals(methodName) && "()V".equals(methodDesc)) {
      return true;
    }
    if ("toString".equals(methodName) && "()Ljava/lang/String;".equals(methodDesc)) {
      return true;
    }
    if ("getClass".equals(methodName) && "()Ljava/lang/Class;".equals(methodDesc)) {
      return true;
    }
    if ("notify".equals(methodName) && "()V".equals(methodDesc)) {
      return true;
    }
    if ("notifyAll".equals(methodName) && "()V".equals(methodDesc)) {
      return true;
    }
    if ("wait".equals(methodName) && "(J)V".equals(methodDesc)) {
      return true;
    }

    return false;
  }

}

ExtensionUtil

java 复制代码
public class ExtensionUtil {

  private static final Set<String> IGNORE_METHOD_SET = CollectionUtil.asSet(JACOCO_INIT_METHOD_NAME,
      CLASS_INIT_METHOD_NAME, CONSTRUCTOR_METHOD_NAME);

  public static boolean ignoreMethod(String methodName) {
    return IGNORE_METHOD_SET.contains(methodName);
  }
}

RandomMethod

java 复制代码
public enum RandomMethod {

  /**
   * Random
   */
  RANDOM_NEXT_BOOLEAN(0, Random.class.getName() + "/nextBoolean~()Z"),
  RANDOM_NEXT_DOUBLE(1, Random.class.getName() + "/nextDouble~()D"),
  RANDOM_NEXT_FLOAT(2, Random.class.getName() + "/nextFloat~()F"),
  RANDOM_NEXT_GAUSSIAN(3, Random.class.getName() + "/nextGaussian~()D"),
  RANDOM_NEXT_INT(4, Random.class.getName() + "/nextInt~()I"),
  Random_NEXT_INT_WITH_INT(5, Random.class.getName() + "/nextInt~(I)I", true),
  Random_NEXT_LONG(6, Random.class.getName() + "/nextLong~()J"),
  RANDOM_NEXT_BYTES(7, Random.class.getName() + "/nextBytes~([B)V", true, true),
  RANDOM_DOUBLES(8, Random.class.getName() + "/doubles~()Ljava/util/stream/DoubleStream;"),
  RANDOM_DOUBLES_WITH_TWO_DOUBLES(9, Random.class.getName() + "/doubles~(DD)Ljava/util/stream/DoubleStream;", true),
  RANDOM_DOUBLES_WITH_LONG(10, Random.class.getName() + "/doubles~(J)Ljava/util/stream/DoubleStream;", true),
  RANDOM_DOUBLES_WITH_LONG_AND_TWO_DOUBLES(11, Random.class.getName() + "/doubles~(JDD)Ljava/util/stream/DoubleStream;"),
  RANDOM_INTS(12, Random.class.getName() + "/ints~()Ljava/util/stream/IntStream;"),
  RANDOM_INTS_WITH_TWO_INT(13, Random.class.getName() + "/ints~(II)Ljava/util/stream/IntStream;", true),
  RANDOM_INTS_WITH_LONG(14, Random.class.getName() + "/ints~(J)Ljava/util/stream/IntStream;", true),
  RANDOM_INTS_WITH_LONG_AND_TWO_INTS(15, Random.class.getName() + "/ints~(JII)Ljava/util/stream/IntStream;", true),
  RANDOM_LONGS(16, Random.class.getName() + "/longs~()Ljava/util/stream/LongStream;"),
  RANDOM_LONGS_WITH_LONG(17, Random.class.getName() + "/longs~()Ljava/util/stream/LongStream;", true),
  RANDOM_LONGS_WITH_TWO_LONGS(18, Random.class.getName() + "/longs~(JJ)Ljava/util/stream/LongStream;", true),
  RANDOM_LONGS_WITH_THIRD_LONGS(19, Random.class.getName() + "/longs~(JJJ)Ljava/util/stream/LongStream;", true),
  /**
   * ThreadLocalRandom
   */
  THREAD_LOCAL_RANDOM_BOOLEAN(20, ThreadLocalRandom.class.getName() + "/nextBoolean~()Z"),
  THREAD_LOCAL_RANDOM_NEXT_DOUBLE(21, ThreadLocalRandom.class.getName() + "/nextDouble~()D"),
  THREAD_LOCAL_RANDOM_NEXT_DOUBLE_WITH_DOUBLE(22, ThreadLocalRandom.class.getName() + "/nextDouble~(D)D", true),
  THREAD_LOCAL_RANDOM_NEXT_DOUBLE_WITH_TWO_DOUBLE(23, ThreadLocalRandom.class.getName() + "/nextDouble~(DD)D", true),
  THREAD_LOCAL_RANDOM_NEXT_FLOAT(24, ThreadLocalRandom.class.getName() + "/nextFloat~()F"),
  THREAD_LOCAL_RANDOM_NEXT_GAUSSIAN(25, ThreadLocalRandom.class.getName() + "/nextGaussian~()D"),
  THREAD_LOCAL_RANDOM_NEXT_INT(26, ThreadLocalRandom.class.getName() + "/nextInt~()I"),
  THREAD_LOCAL_RANDOM_NEXT_INT_WITH_INT(27, ThreadLocalRandom.class.getName() + "/nextInt~(I)I", true),
  THREAD_LOCAL_RANDOM_NEXT_INT_WITH_TWO_INT(28, ThreadLocalRandom.class.getName() + "/nextInt~(II)I", true),
  THREAD_LOCAL_RANDOM_NEXT_LONG(29, ThreadLocalRandom.class.getName() + "/nextLong~()J"),
  THREAD_LOCAL_RANDOM_NEXT_LONG_WITH_LONG(30, ThreadLocalRandom.class.getName() + "/nextLong~(J)J", true),
  THREAD_LOCAL_RANDOM_NEXT_LONG_WITH_TWO_LONG(31, ThreadLocalRandom.class.getName() + "/nextLong~(JJ)J", true),
  THREAD_LOCAL_RANDOM_DOUBLES(32, ThreadLocalRandom.class.getName() + "/doubles~()Ljava/util/stream/DoubleStream;"),
  THREAD_LOCAL_RANDOM_DOUBLES_WITH_TWO_DOUBLES(33, ThreadLocalRandom.class.getName() + "/doubles~(DD)Ljava/util/stream/DoubleStream;", true),
  THREAD_LOCAL_RANDOM_DOUBLES_WITH_LONG(34, ThreadLocalRandom.class.getName() + "/doubles~(J)Ljava/util/stream/DoubleStream;", true),
  THREAD_LOCAL_RANDOM_DOUBLES_WITH_LONG_AND_TWO_DOUBLES(35, ThreadLocalRandom.class.getName() + "/doubles~(JDD)Ljava/util/stream/DoubleStream;"),
  THREAD_LOCAL_RANDOM_INTS(36, ThreadLocalRandom.class.getName() + "/ints~()Ljava/util/stream/IntStream;"),
  THREAD_LOCAL_RANDOM_INTS_WITH_TWO_INT(37, ThreadLocalRandom.class.getName() + "/ints~(II)Ljava/util/stream/IntStream;", true),
  THREAD_LOCAL_RANDOM_INTS_WITH_LONG(38, ThreadLocalRandom.class.getName() + "/ints~(J)Ljava/util/stream/IntStream;", true),
  THREAD_LOCAL_RANDOM_INTS_WITH_LONG_AND_TWO_INTS(39, ThreadLocalRandom.class.getName() + "/ints~(JII)Ljava/util/stream/IntStream;", true),
  THREAD_LOCAL_RANDOM_LONGS(40, ThreadLocalRandom.class.getName() + "/longs~()Ljava/util/stream/LongStream;"),
  THREAD_LOCAL_RANDOM_LONGS_WITH_LONG(41, ThreadLocalRandom.class.getName() + "/longs~()Ljava/util/stream/LongStream;", true),
  THREAD_LOCAL_RANDOM_LONGS_WITH_TWO_LONGS(42, ThreadLocalRandom.class.getName() + "/longs~(JJ)Ljava/util/stream/LongStream;", true),
  THREAD_LOCAL_RANDOM_LONGS_WITH_THIRD_LONGS(43, ThreadLocalRandom.class.getName() + "/longs~(JJJ)Ljava/util/stream/LongStream;", true),
  /**
   * SecureRandom
   */
  SECURE_RANDOM_NEXT_BYTES(44, SecureRandom.class.getName() + "/nextBytes~([B)V", true, true),
  ;

  private static final Map<Integer, RandomMethod> CODE_CACHED = new HashMap<>(values().length);
  private static final Map<String, RandomMethod> METHOD_IDENTITY_CACHED = new HashMap<>(values().length);

  static {
    for (RandomMethod randomMethod : values()) {
      if (null != randomMethod) {
        METHOD_IDENTITY_CACHED.put(randomMethod.getMethodDesc(), randomMethod);
        CODE_CACHED.put(randomMethod.getCode(), randomMethod);
      }
    }
  }

  public static RandomMethod getByMethodIdentity(String className, String methodName,
      String methodDesc) {
    return METHOD_IDENTITY_CACHED.get(String.format("%s/%s~%s", className, methodName, methodDesc));
  }

  public static RandomMethod getByCode(Integer code) {
    return CODE_CACHED.get(code);
  }

  private final Integer code;
  private final String methodDesc;
  private final boolean hasParam;
  private final boolean nextBytes;

  RandomMethod(Integer code, String methodDesc) {
    this(code, methodDesc, false, false);
  }

  RandomMethod(boolean nextBytes, Integer code, String methodDesc) {
    this(code, methodDesc, false, nextBytes);
  }

  RandomMethod(Integer code, String methodDesc, boolean hasParam) {
    this(code, methodDesc, hasParam, false);
  }

  RandomMethod(Integer code, String methodDesc, boolean hasParam, boolean nextBytes) {
    this.code = code;
    this.methodDesc = methodDesc;
    this.hasParam = hasParam;
    this.nextBytes = nextBytes;
  }

  public Integer getCode() {
    return code;
  }

  public String getMethodDesc() {
    return methodDesc;
  }

  public boolean isHasParam() {
    return hasParam;
  }

  public boolean isNextBytes() {
    return nextBytes;
  }

}

InvokeRandomTransformClassVisitorTest

java 复制代码
public class InvokeRandomTransformClassVisitorTest extends ClassLoader {

  public Class<?> loadClassFromBytes(String className, byte[] classBytes) {
    // Define class from byte array
    return defineClass(className, classBytes, 0, classBytes.length);
  }

  public static void main(String[] args) throws Exception {
    String classpath = "原始class文件";
    String outputClassPath = "改写后class文件保存路径";
    executeByteCodeRewrite(classPath, outputClassPath);
  }

  private static void executeByteCodeRewrite(String classPath, String outputClassPath)
      throws Exception {
    // 从class文件中读取字节数组
    byte[] classFileBuffer = Files.readAllBytes(Paths.get(classPath));
    TransformResult transformResult = ExtensionUtil.transformMethod(null, classFileBuffer,
        InvokeRandomTransformClassVisitor::new);
    byte[] modifiedClassBytes = transformResult.getClassBuffer();
    // 将修改后的字节数组写入到指定路径的文件中
    try (FileOutputStream fos = new FileOutputStream(outputClassPath)) {
      fos.write(modifiedClassBytes);
    }
    System.out.println("Class transformed and written to file: " + outputClassPath);

    // 加载修改后的类并使用反射调用方法验证结果
    Class<?> clazz = new InvokeRandomTransformClassVisitorTest().loadClassFromBytes(
        InvokeRandomTransformClassVisitorEntity.class.getName(), modifiedClassBytes);
    Object instance = clazz.getDeclaredConstructor().newInstance();
    Method method = clazz.getMethod("testRandom", String.class);
    System.out.println(method.invoke(instance, ""));
  }

}

InvokeRandomTransformClassVisitorEntity

java 复制代码
public class InvokeRandomTransformClassVisitorEntity {

  public List<Object> testRandom(String noUse) {
    Random random = new Random();
    List<Object> list = new ArrayList<>();
    random.ints(4).forEach(list::add);
    list.add(random.nextInt(1_000));
    list.add(ThreadLocalRandom.current().nextInt(1_000));
    list.add(ThreadLocalRandom.current().nextInt());
    list.add(ThreadLocalRandom.current().nextInt(1, 10));
    list.add(ThreadLocalRandom.current().nextDouble());
    list.add(ThreadLocalRandom.current().nextLong());
//    list.add(ThreadLocalRandom.current().nextDouble(1.0));
//    list.add(ThreadLocalRandom.current().nextDouble(1.0, 100.0));
    byte[] bytes = new byte[16];
    new SecureRandom().nextBytes(bytes);
    list.add(bytes);
    return list;
  }

}
相关推荐
AresXue4 小时前
聊聊为什么java会有这么多的字节码改写方式(jdk/cglib/asm/javasist)?
jvm
程序员卷卷狗9 小时前
JVM实战:从内存模型到性能调优的全链路剖析
java·jvm·后端·性能优化·架构
晓风残月淡12 小时前
JVM字节码与类的加载(一):类的加载过程详解
开发语言·jvm·python
lpruoyu1 天前
颜群JVM【05】强软弱虚引用
jvm
勤奋菲菲2 天前
使用Mybatis-Plus,以及sqlite的使用
jvm·sqlite·mybatis
稚辉君.MCA_P8_Java2 天前
JVM第二课:一文讲透运行时数据区
jvm·数据库·后端·容器
杨DaB2 天前
【JavaSE】JVM
java·jvm
code小毛孩2 天前
如何简单的并且又能大幅度降低任务队列的锁粒度、提高吞吐量?
java·jvm·数据库
用手手打人2 天前
JVM(九)-- 类的生命周期
jvm