How to Programmatically Compile and Instantiate a Java Class

How do I programmatically compile and instantiate a Java class?

How do I load a Java Class that is not compiled?

You need to compile it first. This can be done programmatically with the javax.tools API. This only requires the JDK being installed at the local machine on top of JRE.

Here's a basic kickoff example (leaving obvious exception handling aside):

// Prepare source somehow.
String source = "package test; public class Test { static { System.out.println(\"hello\"); } public Test() { System.out.println(\"world\"); } }";

// Save source in .java file.
File root = new File("/java"); // On Windows running on C:\, this is C:\java.
File sourceFile = new File(root, "test/Test.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));

// Compile source file.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());

// Load and instantiate compiled class.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Should print "hello".
Object instance = cls.newInstance(); // Should print "world".
System.out.println(instance); // Should print "test.Test@hashcode".

Which yields like

hello
world
test.Test@ab853b

Further use would be more easy if those classes implements a certain interface which is already in the classpath.

SomeInterface instance = (SomeInterface) cls.newInstance();

Otherwise you need to involve the Reflection API to access and invoke the (unknown) methods/fields.


That said and unrelated to the actual problem:

properties.load(new FileInputStream(new File("ClassName.properties")));

Letting java.io.File rely on current working directory is recipe for portability trouble. Don't do that. Put that file in classpath and use ClassLoader#getResourceAsStream() with a classpath-relative path.

properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties"));

How do you dynamically compile and load external java classes?

Take a look at JavaCompiler

The following is based on the example given in the JavaDocs

This will save a File in the testcompile directory (based on the package name requirements) and the compile the File to a Java class...

package inlinecompiler;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class InlineCompiler {

public static void main(String[] args) {
StringBuilder sb = new StringBuilder(64);
sb.append("package testcompile;\n");
sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");
sb.append(" public void doStuff() {\n");
sb.append(" System.out.println(\"Hello world\");\n");
sb.append(" }\n");
sb.append("}\n");

File helloWorldJava = new File("testcompile/HelloWorld.java");
if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {

try {
Writer writer = null;
try {
writer = new FileWriter(helloWorldJava);
writer.write(sb.toString());
writer.flush();
} finally {
try {
writer.close();
} catch (Exception e) {
}
}

/** Compilation Requirements *********************************************************************************************/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);

// This sets up the class path that the compiler will use.
// I've added the .jar file that contains the DoStuff interface within in it...
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");

Iterable<? extends JavaFileObject> compilationUnit
= fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
/********************************************************************************************* Compilation Requirements **/
if (task.call()) {
/** Load and execute *************************************************************************************************/
System.out.println("Yipe");
// Create a new custom class loader, pointing to the directory that contains the compiled
// classes, this should point to the top of the package structure!
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
// Load the class from the classloader by name....
Class<?> loadedClass = classLoader.loadClass("testcompile.HelloWorld");
// Create a new instance...
Object obj = loadedClass.newInstance();
// Santity check
if (obj instanceof DoStuff) {
// Cast to the DoStuff interface
DoStuff stuffToDo = (DoStuff)obj;
// Run it baby
stuffToDo.doStuff();
}
/************************************************************************************************* Load and execute **/
} else {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s%n",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
}
}
fileManager.close();
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
exp.printStackTrace();
}
}
}

public static interface DoStuff {

public void doStuff();
}

}

Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!

java: compile inheriting class at runtime

I didn't recognized some mistakes, but with LKTN.25 hint I found them.

First: UsersClass had a typo at the import section.
After this mistake was gone another error came up.

Second: My base class didn't have a default constructor and the sub class too. And the subclass didn't overrite the other constructor. Therefore no instance of the subclass could be created.

Apparently "Does someone know what I need to add/change in my method?" wasn't the right question. The mistake was somewhere else.

Update:
I thought I had to insert a constructor in the users class, because I couldn't create an instance at runtime.
The users class doesn't need a constructor at all (Like usual in java). It gets it from the super class.

Apparently the following snippet doesn't work.

    Class<?> cls = new SimulatorClassloader().loadClass(this.simulatorGUI.getStageTitle());
try {
Method method = cls.getMethod("main");
try {
method.invoke(cls.newInstance());
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

But this (slightly modified code) actually does work:

    Class<?> cls = new SimulatorClassloader().loadClass(this.simulatorGUI.getStageTitle());
try {
Method method = cls.getMethod("main");
try {
Object obj = cls.newInstance();
method.invoke(obj);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

programmatically compiling and loading java source files in clojure

I went around this problem with a dynamic loading library

Vinyasa

Here is a blog post about it: Dynamic reloading of java code in emacs/nrepl

Compile java source from String without writing to file

OK, so here is an example of using JavaCompiler to compile from a String input. this file is the core of it.

In this file, I load the class which is compiled. You will notice that I also detect the package and classname using regexes.


As to the output, if you want to do that in memory, it appears that you can do so if you implement your own JavaFileManager; however I have never tried that!

Note that you can debug what happens quite easily by extending ForwardingJavaFileManager



Related Topics



Leave a reply



Submit