这题目起的哗众取宠,其实只是想介绍下怎么查看Clojure动态生成的字节码,这对分析Clojure的内部实现很重要。
第一步,下载最新的
Clojure 1.1.0源码并解压,并导入到你喜欢的IDE。
其次,下载
asm 3.0的源码并解压。
第三,删除Clojure 1.1.0源码中的clojure.asm包。clojure并不是引用asm的jar包,而是将asm的源码合并到clojure中,并且删除一些只会在调试阶段用到的package和class,保留使用asm的最小源码集合,这可能是处于防止asm不同版本的jar包冲突以及缩小clojure大小的考虑。
第四,将asm 3.0源码拷入clojure的源码中,并将包org.objectweb.asm包括子包整体重名名为clojure.asm。
第五步,修改Clojure源码,加入TraceClassVisitor的适配器,用于跟踪字节码生成,这需要修改clojure.lang.Compiler类中的两个compile方法,找到类似
ClassWriter cw
=
new
ClassWriter(ClassWriter.COMPUTE_MAXS);
//
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv
=
cw;
这样的代码,将cv修改为TraceClassVisitor:
ClassVisitor cv
=
new
TraceClassVisitor(
new
CheckClassAdapter(cw),
new
PrintWriter(System.out));
TraceClassVisitor的第二个参数指定将跟踪到的字节码输出到哪里,这里简单地输出到标准输出方便查看。
第六步,接下来可以尝试下我们修改过的clojure怎么动态生成字节码,启动REPL,
java clojure.main
启动阶段就会输出一些字节码信息,主要预先加载的一些标准库函数,如clojure.core中的函数等,REPL启动完毕,随便输入一个表达式都将看到生成的字节码
user
=>
(
+
1
2
)
输出类似
compile
1
//
class version 49.0 (49)
//
access flags 33
public
class
user$eval__4346
extends
clojure
/
lang
/
AFunction {
//
compiled from: NO_SOURCE_FILE
//
debug info: SMAP
eval__4346.java Clojure
*
S Clojure
*
F
+
1
NO_SOURCE_FILE NO_SOURCE_PATH
*
L
0
#
1
,
1
:
0
*
E
//
access flags 25
public
final
static
Lclojure
/
lang
/
Var; const__0
//
access flags 25
public
final
static
Ljava
/
lang
/
Object; const__1
//
access flags 25
public
final
static
Ljava
/
lang
/
Object; const__2
//
access flags 9
public static <clinit>()V
L0 LINENUMBER
2
L0 LDC
"
clojure.core
"
LDC
"
+
"
INVOKESTATIC clojure
/
lang
/
RT.var (Ljava
/
lang
/
String;Ljava
/
lang
/
String;)Lclojure
/
lang
/
Var; CHECKCAST clojure
/
lang
/
Var PUTSTATIC user$eval__4346.const__0 : Lclojure
/
lang
/
Var; ICONST_1 INVOKESTATIC java
/
lang
/
Integer.valueOf (I)Ljava
/
lang
/
Integer; PUTSTATIC user$eval__4346.const__1 : Ljava
/
lang
/
Object; ICONST_2 INVOKESTATIC java
/
lang
/
Integer.valueOf (I)Ljava
/
lang
/
Integer; PUTSTATIC user$eval__4346.const__2 : Ljava
/
lang
/
Object; RETURN MAXSTACK
=
0
MAXLOCALS
=
0
//
access flags 1
public <init>()V
L0 LINENUMBER
2
L0 L1 ALOAD
0
INVOKESPECIAL clojure
/
lang
/
AFunction.
<
init
>
()V L2 RETURN MAXSTACK
=
0
MAXLOCALS
=
0
//
access flags 1
public invoke()Ljava/lang/Object; throws java/lang/Exception
L0 LINENUMBER
2
L0 L1 LINENUMBER
2
L1 GETSTATIC user$eval__4346.const__1 : Ljava/lang/Object; GETSTATIC user$eval__4346.const__2 : Ljava/lang/Object; INVOKESTATIC clojure/lang/Numbers.add (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;
L2 LOCALVARIABLE
this
Ljava
/
lang
/
Object; L0 L2
0
ARETURN MAXSTACK
=
0
MAXLOCALS
=
0
}
3
3就是表达式的结果。可以看到,一个表达式生成了一个class。其中<clinit>是静态初始化块,主要是初始化表达式中的字面常量;<init>不用说,默认的构造函数;invoke是核心方法,表达式生成的class,new一个实例后调用的就是invoke方法,执行实际的代码,高亮部分加载了两个常量,并执行Number.add方法。
文章转自庄周梦蝶 ,原文发布时间2010-07-11
相关资源:敏捷开发V1.0.pptx