概述
-
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行,织入了对SecurityChecker。checkSecurity()的调用。如果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()方法的调用,表示方法的开始。 |
-
从字节码上的指令看多个xreturn指令是连续的,如下所示:
- 因此在visitInsn()中,简单地通过指令值获得范围,判断是否为xreturn函数返回指令。使用TimeStatWeaveGenerator修改Account.class,将时间统计的字节码进行织入,运行以下代码:
输出结果如下: |
最后
以上就是俊逸手机为你收集整理的Java虚拟机--ASM(十八)的全部内容,希望文章能够帮你解决Java虚拟机--ASM(十八)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复