Java字节码介绍及动态修改类

前言

对于Java字节码,它是在Java类的编译过程产生的,即由.java源文件到.class二进制字节码文件的过程。而Java类的加载又是通过类的名字获取二进制字节流,然后在内存中将字节流生成类对象。所以动态修改类的时机在于修改.class文件,只要通过修改.class文件的字节码,即可达到修改类的目的。修改字节码可以通过ASM这个开源框架实现,ASM是一个Java字节码引擎的库,具体可以查看官网,它可以通过操作字节码实现修改类或者生成类。

介绍

Java字节码的执行操作主要是在虚拟机的栈执行,这个栈主要有局部变量表,操作数栈等几个部分。

(一)局部变量表

主要用来保存方法中的局部变量,基本的存储单位为slot(32位的存储空间),所以long double的数据类型需要两个slot, 当方法被调用时,参数会传递从0开始的局部变量表的索引位置上,所以局部变量最大的大小是在编译期就决定的,特别需要注意的是如果调用的是实例方法,局部变量第0个位置是实例对象的引用

(二)操作数栈

主要用来当作字节码指令操作的出栈入栈的容器,例如变量的出栈入栈都是在操作数栈里面进行的。

(三)指令

指令主要是由操作码+操作数组成的,指令包括加载和存储指令,运算指令和类型转换指令,方法调用指令等等。指令所需要的操作,调用方法,赋值等,都是在操作数栈进行的。

过程

首先是导包,包的版本关系可以查看发布版本,这里我导入的是implementation "org.ow2.asm:asm:6.2"。修改字节码主要需要以下这几个类:ClassReader, ClassWriter, ClassVisitor, MethodVisitor。各个类的作用如下:

  1. ClassReader: 读取类文件
  2. ClassWriter: 继承ClassVisitor 主要用来生成修改类之后的字节
  3. ClassVisitor: 用于访问修改类
  4. MethodVisitor: 用于访问修改类的方法

一般用法如下:

    try {
      String classPath = "asmdemo/ModifyInstanceClass";
      ClassReader classReader = new ClassReader(classPath);
      ClassWriter classWriter = new ClassWriter(classReader, 0);
      ClassVisitor classVisitor = new ClassVisitorDemo(classWriter);
      classReader.accept(classVisitor, 0);

      File file = new File(ROOT_SUFFIX + "ClassDynamicLoader/ASMProject/build/classes/java/main/asmdemo/ModifyInstanceClass.class");
      FileOutputStream output = new FileOutputStream(file);
      output.write(classWriter.toByteArray());
      output.close();
    } catch (IOException e) {
      e.printStackTrace();
    }



  private static class ClassVisitorDemo extends ClassVisitor {
   
    ClassVisitorDemo(ClassVisitor classVisitor) {
      super(Opcodes.ASM5, classVisitor);
    }

    @Override
    public void visitEnd() {

      cv.visitField(Opcodes.ACC_PRIVATE, "timer", Type.getDescriptor(long.class), null, null);

      MethodVisitor methodVisitor = cv.visitMethod(Opcodes.ACC_PUBLIC, "newFunc","()V", null,null);
      methodVisitor.visitInsn(Opcodes.RETURN);
      methodVisitor.visitMaxs(0,1);

      super.visitEnd();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
      MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);

      if (name.equals("print") && desc.equals("()V")) {
        methodVisitor = new MethodVisitorHub.FirstMethodVisitor(methodVisitor);
      } else if(name.equals("print") && desc.equals("(Ljava/lang/String;)V")) {
        methodVisitor = new MethodVisitorHub.SecondMethodVisitor(methodVisitor);
      } else if (name.equals("connectStr")) {
        methodVisitor = new MethodVisitorHub.ThirdMethodVisitor(methodVisitor);
      }
      return methodVisitor;
    }


  }

先利用ClassReader读取待修改的类文件,然后基于Reader创建了对应的ClassWriter,再基于ClassWriter创建了对应的ClassVisitor, 再接着ClassReader委托ClassVisitor去读取修改类,最后,创建文件输出流,利用ClassWriter生成的字节,将重新生成的字节码写回build目录生成的class文件,替换编译生成的class文件,这样就可以达到修改类的目的。

Created with Raphaël 2.1.2
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值