Reputation: 13690
I am using this code from an old IBM blog post about how to compile and use Java classes at runtime. The code mostly works great (and is quite well written, by the way), but unfortunately for me, it won't work in one of my use cases where the class being compiled refers to another class that can only be provided by the classLoader provided to the CharSequenceCompiler
(from the blog post), not by the application classLoader.
To be more specific, The ClassLoader I pass into CharSequenceCompiler
is a OSGi classLoader.
The bundle that owns this classLoader can find and return a class, say Foo
.
class Foo { public static String FOO = "F"; }
I know this works if you do classLoader.findClass("Foo");
because when I call this from the debugger it works.
Now, from the class I compile at runtime, say Dynamic
, I need to use Foo
... so I pass the Foo
bundle's ClassLoader to CharSequenceCompiler
, then ask it to compile Dynamic
:
class Dynamic { public static String D = Foo.FOO; }
This causes the following error:
error: cannot find symbol
Foo.FOO;
^
symbol: variable Foo
If Foo
is in the same project as CharSequenceCompiler
, then it works... so it's clearly a problem loading the class from the right class loader.
I have debugged this code for days (or evenings, tbh) and can't find out why the classLoader I provide to the compiler never even gets asked about this class...
The FileManager
is asked to list()
the resources in each package, but even when I use the debugger to manually add the FileObject
to the returned list, it still won't work.
As the debugger cannot penetrate the native classes used by javac internally, I cannot progress anymore on this... does anyone have inside knowledge of the compiler that could explain what's going on?
Upvotes: 2
Views: 1416
Reputation: 13690
I have figured this out after a long battle.
The problem with pretty much all implementations I found of in-memory java compilers based on the java.tools API is that, even though they let you pass in a ClassLoader
to load the classes you compile, the classLoader is used only to load new classes, but not to obtain classes that can be used in the Java code being compiled.
For this reason, my use case (as explained in the question) would not work with the code shown in the IBM blog post (or with other projects like OpenHFT Java-Runtime-Compiler).
If you want the classes loaded by the ClassLoader
you give to the compiler (which are not visible in the application ClassLoader
) to be usable by the class being compiled, you need two things.
First, the ClassLoader classes must be enumerable. This can then be used by the JavaFileManager
to implement the list()
method properly. For each package, this method must return which classes the ClassLoader
could load if required.
Secondly, you need to be able to build JavaFileObject
s from the ClassLoader
resources, because this is the type of the objects you must return. To do this, you need to ask the ClassLoader
for the class's bytecode stream (use getResourceAsStream("Class.class")
) and then just create a JavaFileObjectImpl
. Basically, this in pseudo-code:
fileObject = new JavaFileObjectImpl( pathMinusDotClass, JavaFileObject.Kind.CLASS );
fileObject.openOutputStream()
.write( classLoader.getInputStream( path ) );
Now, the compiler will know which classes it can load from the provided classLoader and everything works.
I implemented this in a real compiler on my OSGiaaS project, which is not released yet but I plan to do it soon (writing in July 2016)... it includes a Java command in its shell which can run arbitrary Java code, and that's why I needed to get this working.
Upvotes: 3
Reputation: 538
What is the benefits of using real java compiler. Isn't byte code generation option for you ?
In example: Byte Buddy or cglib
Upvotes: 0