Java虚拟机--ASM(十八)

来源:互联网 发布:科幻小说推荐 知乎 编辑:程序博客网 时间:2024/05/22 12:56
  • ASM体系结构
    • ASM是Java字节码的操作库,包括Eclipse,Spring,CGLIB都是ASM的使用者;
    • 优势:
      • 性能高;
      • 直接工作于底层,使用更加灵活与强大;
    • 劣势:
      • 相对复杂;
    • 核心组件
      • Opcodes接口定义了一些常量,尤其是版本号,访问标示符,字节码等信息;
      • ClassReader用于读取Class文件,主要用于Class文件的分析,可接受一个ClassVisitor;
        • ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法逐个送入ClassVisitor,后者在接收到对应的信息后,进行各自的处理;
      • ClassVisitor的子类ClassWriter,负责进行Class文件的输出和生成。ClassVisitor在进行字段和方法处理的时候,会委托给FieldVistor和MethodVisitor进行处理;
        • 在类的处理过程中,会创建对应的FieldVisitor和MethodVisitor对象;
        • FieldVisitor和MethodVisitor类也各自有1个重要的子类,FieldWriter和MethodWriter;当ClassWriter进行字段和方法的处理时,也是依赖这两个类进行的;
      • ClassVisitor,FieldVisitor,MethodVisitor都可以使用委托的方式,将实际的处理工作交给内部的委托类进行;它们内部有一些列的visitXXX方法:
        • 这些方法在ClassWriter和MethodWriter内部实现时,绝大部分情况下,都会去生成该方法对应的内容;
          • 比如,当MethodWriter的visitInsn(int opcode)方法被调用时,MethodWriter就会生成一条由参数opcode指定的字节码;
          • 而visitInsn(int opcode)方法作为MethodVisitor的方法,将会在ClassReader访问Class时被回调;
          • 即,当使用ClassReader读取一个类,ClassWriter作为访问者时,当ClassReader读取到一条不带参数的字节码信息时,就会通知ClassWriter,visitInsn(int opcode)方法,让ClassWriter生成这条字节码信息;
  • 示例:为类增加安全控制
    • 现在有一个账户类,可以进行某些操作如下:

现在要为operation()方法增加一些安全校验,以判断这个对象是否有权限执行这个方法,如果有,则执行该方法,如果没有,则直接退出。

增加的权限校验函数:

该代码模拟了权限校验,通过随机方式给出是否校验通过。现在系统要做的就是将SecurityChecker.checkSecurity()函数置于Account.operation()函数之前运行,如果权限校验失败,则阻止Account.operation()继续处理;

  • 下面的SecurityWeaveGenerator类,将checkSecutrity()函数放置到operation()函数的第1行执行。它的核心是代码第6行的AddSecurityCheckClassAdapter类。这是一个ClassVisitor,它负责实际的字节码织入操作。在代码的第8~12行,将新生成的Account类写入文件,覆盖由Java编译器产生的Account类的Class文件;

AddSecurityCheckClassAdapter类的代码如下:

在AddSecurityCheckClassAdapter中,覆盖了visitMethod()方法,在代码第11行,当访问到operation方法时,交由AddSecutiryCheckMethodAdapter类处理。AddSecutiryCheckMethodAdapter是一个MethodVisitor,它负责最终的字节码修改:

AddSecurityCheckClassAdapter类覆盖了MethodVisitor的visitCode()方法。当访问到方法的字节码时,在第7~12行,织入了对SecurityCheckercheckSecurity()的调用。如果checkSecurity()返回了false(栈顶为0),根据指令ifne,跳转不会发生,程序返回。如果checkSecurity()返回了true(栈顶为1),则指令ifne发生跳转,继续执行原先的operation()操作

先使用javac编译并生成Account.class,接着使用SecurityWeaveGenerator类对Account.class进行处理,织入权限校验的字节码,最后使用下面的代码测试:

  • 统计函数执行时间示例
    • 统计函数时间可利用System.currentTImeMillis(),现在可利用ASM框架,在不修改源码,不改变原有系统的情况下,直接将统计函数织入系统;
    • 利用Thread.sleep()模拟一段耗时的函数调用,现在Account.operation()本身并不具有计时功能

    • 现在,加入计时统计功能:

TimeStat实现了函数的调用计时,在进入函数时,可以使用start()方法表示函数调用开始,在离开函数时,使用end()方法表示函数调用结束。函数调用结束后,打印出当前正在调用的函数名称以及实际的系统耗时。

  • 现在将统计时间代码织入Account.operation()

6行使用TimeStatClassAdapter类,完成具体的字节码修改工作。修改完成后,第9~12行写入Class文件,覆盖原有的Class文件。
 

  • TimeStatClassAdapter是一个ClassVisitor,这里,需要覆盖它的visitMethod()方法,对operation()方法进行修改;

此段代码在访问方法时,判断是否是operation()方法,如果是,则进行方法字节码的调整,并将这个这个工作委托给TimeStatMethodAdapter完成。

  • TimeStatMethodAdapter的实现

6行的visitCode()在方法Code属性被访问时调用,因此,这里插入对TimeStat.start()方法的调用,表示方法的开始。
12行,覆盖了visitInsn()函数,当访问到xreturn指令时,进行TimeStat.end()函数调用,表示方法即将退出。

  • 从字节码上的指令看多个xreturn指令是连续的,如下所示:

  • 因此在visitInsn()中,简单地通过指令值获得范围,判断是否为xreturn函数返回指令。使用TimeStatWeaveGenerator修改Account.class,将时间统计的字节码进行织入,运行以下代码:

输出结果如下:

 

原创粉丝点击