我在旧的IBM博客文章中使用this code,关于如何在运行时编译和使用Java类。代码大部分工作得很好(顺便说一句,编写得很好),但不幸的是,对于我来说,它在我的一个用例中不起作用,其中被编译的类引用另一个只能由classLoader提供的类提供给CharSequenceCompiler
(来自博客文章),而不是应用程序classLoader。
更具体地说,我传入CharSequenceCompiler
的ClassLoader是一个OSGi classLoader。
拥有这个classLoader的bundle可以找到并返回一个类,比如Foo
。
class Foo { public static String FOO = "F"; }
我知道如果你做classLoader.findClass("Foo");
,这是有效的,因为当我从调试器调用它时,它的工作原理。
现在,从我在运行时编译的类,比如Dynamic
,我需要使用Foo
...所以我将Foo
包的ClassLoader传递给CharSequenceCompiler
,然后要求它编译Dynamic
:
class Dynamic { public static String D = Foo.FOO; }
这会导致以下错误:
error: cannot find symbol
Foo.FOO;
^
symbol: variable Foo
如果Foo
与CharSequenceCompiler
在同一个项目中,那么它的工作原理......所以从正确的类加载器加载类显然是一个问题。
我已经调试了几天(或晚上,tbh)的代码,并且无法找出为什么我提供给编译器的classLoader甚至不会被问到这个类......
FileManager
被要求list()
每个包中的资源,但即使我使用调试器手动将FileObject
添加到返回的列表,它仍然无法工作。
由于调试器无法在内部渗透javac使用的本机类,因此我无法继续进行...有没有人有编译器的内部知识可以解释发生了什么?
经过长时间的战斗,我发现了这一点。
我发现基于java.tools API的内存中java编译器几乎所有实现的问题是,即使它们允许你传入ClassLoader
来加载你编译的类,classLoader也只用于加载新类,但不是为了获得可以在编译的Java代码中使用的类。
出于这个原因,我的用例(如问题中所解释的)不适用于IBM博客文章(或其他项目,如OpenHFT Java-Runtime-Compiler)中显示的代码。
如果您希望由编译器提供的ClassLoader
加载的类(在应用程序ClassLoader
中不可见)可以被编译的类使用,则需要两件事。
首先,ClassLoader类必须是可枚举的。然后,JavaFileManager
可以使用它来正确实现list()
方法。对于每个包,此方法必须返回ClassLoader
可以根据需要加载的类。
其次,您需要能够从JavaFileObject
资源构建ClassLoader
s,因为这是您必须返回的对象的类型。要做到这一点,你需要向ClassLoader
询问类的字节码流(使用getResourceAsStream("Class.class")
),然后创建一个JavaFileObjectImpl
。基本上,这在伪代码中:
fileObject = new JavaFileObjectImpl( pathMinusDotClass, JavaFileObject.Kind.CLASS );
fileObject.openOutputStream()
.write( classLoader.getInputStream( path ) );
现在,编译器将知道它可以从提供的classLoader加载哪些类,一切正常。
我在我的OSGiaaS project上的一个真正的编译器中实现了这个,它还没有发布但我打算很快就会这样做(2016年7月写)...它在shell中包含一个可以运行任意Java代码的Java命令,这就是为什么我需要让这个工作。
使用真正的java编译器有什么好处。不是字节码生成选项吗?
例如:Byte Buddy或cglib