场景:给定两个文件
src/dispatch/core.clj
(ns dispatch.core (:require [dispatch.dispatch]))
src/dispatch/dispatch.clj
(ns dispatch.dispatch) (deftype T []) (def t (->T)) (println "T = (class t):" (= T (class t)))
首先编译 core,然后再编译 dispatch
java -cp src:target/classes:clojure.jar -Dclojure.compile.path=target/classes clojure.main user=> (compile 'dispatch.core) T = (class t): true dispatch.core user=> (compile 'dispatch.dispatch) T = (class t): false ;; 期望为 true dispatch.dispatch
此场景在带有 {{:aot :all}} 的 leiningen 项目中更为常见。文件按字母顺序编译。在这种情况下,首先编译 dispatch.core,然后编译 dispatch.dispatch。
原因
(compile 'dispatch.core)
- 递归编译 dispatch.dispatch
- 将.class文件写入到 compile-path(它在类路径上)
- 断言通过
(compile 'dispatch.dispatch)
- 由于之前的编译,通过appclassloader加载了dispatch.dispatch__init
- ->T构造函数将使用新的字节码来实例化一个T实例 - 这使用appclassloader,从磁盘上的编译好的T加载
- 然而,T类字面量通过RT.classForName解析,它检查动态类加载器缓存,因此使用旧的运行时T版本,而不是磁盘上的版本
在1.6中,RT.classForName()没有检查动态类加载器缓存,因此像实例一样从磁盘加载T。这是在CLJ-979中更改的,以支持其他重定义和AOT混合使用。
方法
1) 以逆依赖顺序编译,以避免编译两次。
在第一个示例中交换编译顺序或指定project.clj中的顺序
:aot [dispatch.dispatch dispatch.core]
这是一个短期解决方案。
2) 将deftype放入与它所使用的不同命名空间中,以便在第二次编译时不会重新定义它。这是另一个短期解决方案。
3) 不要将编译路径放在类路径上(这违反了当前期望,但可以避免加载dispatch__init)
(set! *compile-path* "foo") (compile 'dispatch.core) (compile 'dispatch.dispatch)
目前通过Leiningen设置起来有些困难。
4) 使用独立的Clojure运行时编译每个文件 - 避免在DCL中使用缓存的类字面量。
现在在Leiningen或其它方式下实现可能太麻烦了。
5) 使编译非递归。这类似于CLJ-322,这是一个另一只“大茶壶”(can of worms)。但这也可能是我们应该前往的方向。
筛选: 我认为提出的补丁不是一个好主意 - 它只是掩盖了症状,而没有解决根本原因。我认为我们需要重新评估与编译路径 (#3) 和传递性 (CLJ-322) (#5) 的编译方式,但我觉得我们应该在1.7之后进行这些工作 - Alex
另请参阅:CLJ-1650