Java中ASM框架详解
什么是asm呢?asm是assembly的缩写,是汇编的称号,对于java⽽⾔,asm就是字节码级别的编程。
⽽这⾥说到的asm是指objectweb asm,⼀种.class的代码⽣成器的开源项⽬.
ASM是⼀套java字节码⽣成架构,它可以动态⽣成⼆进制格式的stub类或其它代理类,
或者在类被java虚拟机装⼊内存之前,动态修改类。
现在挺多流⾏的框架都使⽤到了asm.所以从aop追溯来到了这。
1.什么是ObjectWeb ASM
ObjectWeb ASM是轻量级的Java字节码处理框架。它可以动态⽣成⼆进制格式的stub类或其他代理类,或者在类被JAVA虚拟机装⼊内存之前,动态修改类。
ASM 提供了与 BCEL和SERP相似的功能,只有22K的⼤⼩,⽐起350K的BCEL和150K的SERP来说,是相当⼩巧的,并且它有更⾼的执⾏效率,
是BCEL 的7倍,SERP的11倍以上。
在我看来,ObjectWeb ASM具有如下⼏个⾮常诱⼈的特点
* ⼩巧、⾼效
* 源代码实现⾮常简洁⽽⼜优雅,简直就是Gof的《设计模式》⾮常棒的注解
* 字节码级的控制,能够更⾼效地实现字节码的控制
ObjectWeb ASM有2组接⼝:
* 基于事件驱动的接⼝,类似于xml的SAX接⼝,visitor模式,在访问到类定义某个部分的时候进⾏回调,实现上⽐tree接⼝⾼效,占⽤内存更⼩
* 基于tree的接⼝,类似于xml的DOM接⼝,将类定义解析成tree
这⾥我们将使⽤ObjectWeb ASM的事件驱动接⼝
2.⽬标
我们将对已有的字节码进⾏增强,收集进⼊⽅法和退出⽅法的信息,这⾥主要解决Method Monitor的字节码增强部分,
不对收集后的数据处理做更深⼊地研究,出于演⽰的⽬的,我们定义了如下的收集⽅法的访问信息处理,
在实际应⽤中,我们可能会使⽤更好的格式收集更多的数据、使⽤异步处理提⾼性能、使⽤批量处理提⾼处理能⼒、使⽤友好的UI显⽰信息等等,
此处不对这部分进⾏探讨
1. itor;
2. public class MonitorUtil
3. {
4. public final static String CLASS_NAME = Name()
5. .replaceAll("\\.", "/");
6. public final static String ENTRY_METHOD = "entryMethod";
7. public final static String EXIT_METHOD = "exitMethod";
8. public final static String METHOD = "(Ljava/lang/String;Ljava/lang/String;)V";
9.
10. public static void entryMethod(String className, String methodName)
11. {
12. System.out.println("entry : " + className + "." + methodName);
13. }
14.
15. public static void exitMethod(String className, String methodName)
16. {
17. System.out.println("exit : " + className + "." + methodName);
18. }
19. }
3.从字节码开始
实际上,对于被监控制的代码,我们所需要实现的功能如下,红⾊部分的代码是我们需要在动态期插到字节码中间的
public xxx method(…)
{
try
{
methodEntry(…)
methodCode
}
finally
{
methodExit(…)
}
}
这个问题看起来简单,实际则没有那么容易,因为在JVM的字节码设计中,字节码并不直接⽀持finally语句,⽽是使⽤try…catch来模拟的,我们先来看⼀个例⼦
Java代码
1. st;
2.
3. public class Test
4. {
5. public void sayHello() throws Exception
6. {
7. try
java虚拟机缩写8. {
9. System.out.println("hi");
10. } catch (Exception e)
11. {
12. System.out.println("exception");
13. return;
14. } finally
15. {
16. System.out.println("finally");
17. }
18. }
19. }
我们看看字节码是如何处理finally语句的
⾸先看看异常表,异常是在JVM级别上直接⽀持的,下⾯异常表的意思是,在执⾏0-8语句的时候,如果有异常java.lang.Exception抛出,则进⼊第11语句,
在执⾏0-20语句的时候,有任何异常抛出,都进⼊29语句。实际上JVM是这样实现finally语句的:
* 在任何return语句之前,都会增加finally语句中的字节码
* 定义⼀个捕获所有异常的语句,增加finally语句中的字节码,如果finally中没有return语句,则会将异常再次抛出去(处理⽅法以抛出异常的⽅式结束)
Exceptions:
[0-8): 11 - java.lang.Exception
[0-20): 29
我们再看看字节码具体是如何做的
0 getstatic java.lang.System.out
3 ldc "hi" (java.lang.String)
5 invokevirtual println
8 goto 40
// System.out.println("hi");,执⾏完之后执⾏返回(goto 40)
11 astore_1
12 getstatic java.lang.System.out
15 ldc "exception" (java.lang.String)
17 invokevirtual println
/
/ System.out.println("exception");
20 getstatic java.lang.System.out
23 ldc "finally" (java.lang.String)
25 invokevirtual println
// return语句之前插⼊finally部分字节码
// System.out.println("finally");
28 return
29 astore_2
30 getstatic java.lang.System.out
33 ldc "finally" (java.lang.String)
35 invokevirtual println
38 aload_2
39 athrow
//当在执⾏0-29语句中,如果有异常抛出,则执⾏这段finally语句
//此处的astore_2(将栈顶值——即exception的地址——设给第2个local变量)和aload_2(将第2个local变量的值⼊栈)这两个字节码实际是不必
要的,
//但需要注意的是,如果这2段代码去掉的话,要考虑增⼤操作栈(max stack)以容纳这个exception地址
//System.out.println("finally");
40 getstatic java.lang.System.out
43 ldc "finally" (java.lang.String)
45 invokevirtual println
// return语句之前插⼊finally部分字节码
// System.out.println("finally");
48 return
实际上,我们需要做的就是
* 在⽅法进⼊时插⼊⽅法进⼊代码(需要注意,对于构造函数不允许做这种处理,构造函数第⼀步必须调⽤⽗类的构造函数。 * 在每个return操作(包括return、ireturn、freturn等)之前,插⼊⽅法退出代码
* 定义⼀个捕获所有异常的处理,在处理中,插⼊⽅法退出代码(即⽅法以抛异常的⽅式终⽌执⾏)
4.实现
我们看看使⽤ObjectWeb ASM如何实现我们上⾯描述的功能
1)ObjectWeb ASM的字节码修改
1. ClassReader cr = new ClassReader(byteArray); //使⽤字节码构监⼀个reader
2. ClassWriter cw = new ClassWriter(cr, 0);//writer将基于已有的字节码进⾏修改
3. MonitorClassVisitor ca = new MonitorClassVisitor(cw);//修改处理回调类
4. cr.accept(ca, 0);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论