Let’s suppose you have some string containing a source code for a class, something like “public class Test {}”. You want to compile it dynamically in memory and get an instance of that class. How can you accomplish that? We will try to present here a way to do it. We will use the javax.tools package that was added in Java 6, so be sure that you are using Java 6 or later to run the codes.
Although we will try to show the dynamic compilation it in the simplest way possible, we will still need to create four classes. You can get the full version of all the classes in a zipped archive here.
First let’s see the main method to get the feeling of what is going on:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: |
public class DynaCompTest {
public static void main(String[] args) throws Exception {
// Full name of the class that will be compiled.
// If class should be in some package,
// fullName should contain it too
// (ex. "testpackage.DynaClass")
String fullName = "DynaClass";
// Here we specify the source code of the class to be compiled
StringBuilder src = new StringBuilder();
src.append("public class DynaClass {\n");
src.append(" public String toString() {\n");
src.append(" return \"Hello, I am \" + ");
src.append("this.getClass().getSimpleName();\n");
src.append(" }\n");
src.append("}\n");
System.out.println(src);
// We get an instance of JavaCompiler. Then
// we create a file manager
// (our custom implementation of it)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager fileManager = new
ClassFileManager(compiler
.getStandardFileManager(null, null, null));
// Dynamic compiling requires specifying
// a list of "files" to compile. In our case
// this is a list containing one "file" which is in our case
// our own implementation (see details below)
List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
jfiles.add(new CharSequenceJavaFileObject(fullName, src));
// We specify a task to the compiler. Compiler should use our file
// manager and our list of "files".
// Then we run the compilation with call()
compiler.getTask(null, fileManager, null, null,
null, jfiles).call();
// Creating an instance of our compiled class and
// running its toString() method
Object instance = fileManager.getClassLoader(null)
.loadClass(fullName).newInstance();
System.out.println(instance);
}
}
|
As you see the code we want to compile is stored in the variable src. After we define it, we print it to the console, get an instance of the compiler, put the source code into an object representing a source file, create a file manager and a compilation task. The real compilation starts when we call the call() method of the compilation task. Then we get the Class representing our compiled class from the file manager, instantiate our class and print it to the console, using the toString() function that we implemented in the code.
There are three classes used in the code that are not available in the JDK and hence we have to implement them by ourselves – CharSequenceJavaFileObject, JavaClassObject and ClassFileManger. Let’s see them one by one to understand what they are doing.
CharSequenceJavaFileObject implements the SimpleJavaFileObject interface and represents the source code we want to compile. Normally instances of SimpleJavaFileObject would point to a real file in the file system, but in our case we want it to represent a StringBuilder createdy by us dynamically. Let’s see how it goes:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: |
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
/**
* CharSequence representing the source code to be compiled
*/
private CharSequence content;
/**
* This constructor will store the source code in the
* internal "content" variable and register it as a
* source code, using a URI containing the class full name
*
* @param className
* name of the public class in the source code
* @param content
* source code to compile
*/
public CharSequenceJavaFileObject(String className,
CharSequence content) {
super(URI.create("string:///" + className.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.content = content;
}
/**
* Answers the CharSequence to be compiled. It will give
* the source code stored in variable "content"
*/
@Override
public CharSequence getCharContent(
boolean ignoreEncodingErrors) {
return content;
}
}
|
It stores an object of type CharSequence which is an interface implemented by StringBuilder. As you see in the code of our main function, we create an instance of the CharSequenceJavaFile providing it with src variable, which is of type StringBuilder. The constructor stores it in the content private variable. It will be used when the compiler calls the getCharContent() method to get the source code to compile.
Next we must define the class representing the output of the compilation – compiled byte code. It is needed by the ClassFileManager which we will describe later. Compiler takes the source code, compiles it and splits out a sequence of bytes which must be stored somewhere. Normally they would be stored in a .class file but in our case we just want to make a byte array out of it. Here is a class that fulfills our needs:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: |
public class JavaClassObject extends SimpleJavaFileObject {
/**
* Byte code created by the compiler will be stored in this
* ByteArrayOutputStream so that we can later get the
* byte array out of it
* and put it in the memory as an instance of our class.
*/
protected final ByteArrayOutputStream bos =
new ByteArrayOutputStream();
/**
* Registers the compiled class object under URI
* containing the class full name
*
* @param name
* Full name of the compiled class
* @param kind
* Kind of the data. It will be CLASS in our case
*/
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/')
+ kind.extension), kind);
}
/**
* Will be used by our file manager to get the byte code that
* can be put into memory to instantiate our class
*
* @return compiled byte code
*/
public byte[] getBytes() {
return bos.toByteArray();
}
/**
* Will provide the compiler with an output stream that leads
* to our byte array. This way the compiler will write everything
* into the byte array that we will instantiate later
*/
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
|
At some point of the compilation, compiler will call openOutputStream() method of our JavaClassObject class and write there the compiled byte code. Because the openOutputStream() method returns a reference to the bos variable, everything will be written there, so that afterwards we will be able to get the byte code from it.
We will also need something like a “file manager” that will tell the compiler to put the compiled byte code into an instance of our JavaClassObject class instead of putting it to a file. Here it is:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: |
public class ClassFileManager extends ForwardingJavaFileManager |
Function getClassLoader() will be called by us to get a ClassLoader instance for instantiating our compiled class. It returns an instance of SecureClassLoader modified by the function findClass(), which in our case gets the compiled byte code stored in the instance of JavaClassObject, defines a class out of it with the function defineClass() and returns it.
Now that we have all our classes ready, lets compile them and run the program. We should get an output like this:
public class DynaClass {
public String toString() {
return "Hello, I am "+this.getClass().getSimpleName();
}
}
Hello, I am DynaClass
|
As you see, the toString() method of our dynamically compiled class was invoked printing “Hello, I am DynaClass” to the screen. Nice, isn’t it?
One of the questions that appear now is – What is that all good for? Are there any real life uses of it or is it just a nice toy? Well, classes compiled in this way can contain dynamic expressions that are not known until runtime. At the same time those classes benefit from all the optimizations that the Java compiler provides. One pretty straightforward use of this is when the user types in the program an expression like “y=2*(sin(x)+4.0)” and expects to see some output of it – for example a graph. Using dynamic compilation you don’t have to parse it any more by yourself, you could just compile it and get a fast, optimized function representing this expression. You can read about it (and much more about dynamic compilation generally) here.
Some other usage is creating dynamic classes for accessing data stored in JavaBeans. Normally you would have to use reflection for it, but reflection is very slow and its generally better to avoid using it when possible. Dynamic compilation allows you to minimize the use of reflection in a library that handles JavaBeans. How? We will try to show it in one of our next posts, so stay tuned!
22 Comments until now
Hey, that was interesting,
great coding skills, I learned a lot form you today
Thanks for writing about it
interesting!
but about you conclusion, for evaluating math expression is simpler with the javax.script features… Like:
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName(”JavaScript”);
try {
jsEngine.eval(”print(2*2)”);
} catch (ScriptException ex) {
ex.printStackTrace();
}
[...] article was inspired by a comment from Ricky on one of our previous post. [...]
Great article, thanks! Google was returning me a bunch of articles that neglected explaining the ForwardingJavaFileManager subclass for accessing in-memory bytecode, so that was particularly helpful.
Great article. Do you know how to get an existing classloader to define the new class? I don’t want to have to construct a new classloader for every new dynamic class that I am creating.
Should also work for the system classloader…
Heinz
P.S. I have found a way, but want to know if there is a better way
In reply to Heinz Kabutz:
classLoader = MyOtherClass.class.getClassLoader();
if you try to use that in a managed environment with non-default classloader (like j2ee container) you might run into problems, check out my article http://atamur.blogspot.com/2009/10/using-built-in-javacompiler-with-custom.html if that happens to you =)
Interesting post Miron. I was debugging it and found however that I wasn’t able to use reflection to introspect on the runtime class generated. Any idea why this would be the case?
Brill article, very interesting and very well written
Hey interesting post! Thanks for sharing!!
Great example! After doing something similar, I thought it would be useful to mention that the public code in com.sum.script.java.MemoryJavaFileManager does a lot of this, allowing for a simplification of your example:
public class DynaCompTest {
public static void main(String[] args) throws Exception {
// Full name of the class that will be compiled.
// If class should be in some package,
// fullName should contain it too
// (ex. "testpackage.DynaClass")
String fullName = "DynaClass";
// Here we specify the source code of the class to be compiled
StringBuilder src = new StringBuilder();
src.append("public class DynaClass {\n");
src.append(" public String toString() {\n");
src.append(" return \"Hello, I am \" + ");
src.append("this.getClass().getSimpleName();\n");
src.append(" }\n");
src.append("}\n");
System.out.println(src);
// We get an instance of JavaCompiler. Then
// we create a file manager
// (our custom implementation of it)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager fileManager = new MemoryJavaFileManager(compiler.getStandardFileManager(null, null, null));
// Dynamic compiling requires specifying
// a list of "files" to compile. In our case
// this is a list containing one "file" which is in our case
// our own implementation (see details below)
List jfiles = new ArrayList();
jfiles.add(MemoryJavaFileManager.makeStringSource(fullName + ".java", src.toString()));
// We specify a task to the compiler. Compiler should use our file
// manager and our list of "files".
// Then we run the compilation with call()
compiler.getTask(null, fileManager, null, null,
null, jfiles).call();
// Creating an instance of our compiled class and
// running its toString() method
Object instance = fileManager.getClassLoader(null)
.loadClass(fullName).newInstance();
System.out.println(instance);
}
}
The only thing needed is to add a getClassLoader() method to MemoryJavaFileManager, such as this one:
@Override
public ClassLoader getClassLoader(Location location) {
return new java.security.SecureClassLoader() {
@Override
protected Class findClass(String name) throws ClassNotFoundException {
byte[] b = classBytes.get(name);
if (b != null)
return defineClass(name, b, 0, b.length);
return super.findClass(name);
}
};
}
Source for MemoryJavaFileManager is just a google search away.
Thanks again for the great blog post!
Nice work! Did anybody know an Eclipse-plugin that uses this way to compile the java sources. At work I’m really annoyed by an enthusastic virus scanner that I can’t disable and every compiling-task takes a lot of time (even on a SSD)
“Intriguing post – thank you. I thought your post was really intriguing. Thanks over again – I will come back.
Software Development
Really cool. But could you do the same without knowing in advance the class name ?. I mean, generating in memory byte codes only having the source code of the class?
This is a great article, I am using it for my application. However, one issue is not clear. If the class I am compiling depends on other in memory classes, how is that fed to the compiler? Normally, the filemanager would have a StandardLocation.CLASS_OUTPUT where the dependent classes can be found. of course, not true in this approach. w/o these dependent classes, the compilation would fail.
Hi,
What if I want to specify the version of compiler to use. I mean I want to use some compiler which is not the same as the one which through which I am running the demo program written by you above.
Naming Java Files
Great article. You should have a twitter feed to announce blog posts. Would like to follow you like that…
[...] Dynamic in-memory compilation http://www.javablogging.com/dynamic-in-memory-compilation/ [...]
When I try this with dynamic Class A that extends non-dynamic class B and I properly set the class path during compile time, it compiles but I can’t load it.
If I load it with the custom file manager, I get an issue finding class B. If I try it with Class.forName(), I get an issue finding class C.
Thoughts?
Great example.
I’ve tried to modify the code, so the class will belong to a package by changing:
String fullName = “DynaClass”;
to
String fullName = “testpackage.DynaClass”;
As described in the comment. However this results in a error and I can’t quite find out why?
Exception in thread “main” java.lang.NoClassDefFoundError: testpackage/DynaClass (wrong name: DynaClass)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at net.tdc.test.iex2.ClassFileManager$1.findClass(ClassFileManager.java:50)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at net.tdc.test.iex2.DynaCompTest.main(DynaCompTest.java:65)
Java Result: 1
Found solution to question above. Forgot to add:
src.append(”package testpackage; \n”);
Hi! i want to ask how to compile code, when it has some references.
Add your Comment!