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!
Dynamically loading a class at the Client side program
You should load compiled class, not the source code. Try to compile the class before uploading it to the server, and then send the produced by compiler *.class
file instead of *.java
file.
You can compile the class using command line compiler:javac MyClass.java
- it will produce the compiled MyClass.class
file in the same directory (or use compile/build command from your IDE and look for the compiled class in the output directory of the IDE - I am using IntelliJ/Eclipse so I am not sure which one it will be in the NetBeans).
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"));
Compile dynamically java class web project
Get it working.
I have to make that change mentioned by @NicolasFilotto, to use parent classloader when using a webapp.
After that, for JavaCompiler works, as I'm using external libraries it's necessary to pass my classpath like mentioned on my last edit.
Basically the code changed to:
public static Class<?> compile(ClassLoader parent, String className, String sourceCodeInText) throws Exception {
SourceCode sourceCode = new SourceCode(className, sourceCodeInText);
CompiledCode compiledCode = new CompiledCode(className);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceCode);
DynamicClassLoader cl = new DynamicClassLoader(parent);
ExtendedStandardJavaFileManager fileManager = new ExtendedStandard(
javac.getStandardFileManager(null, null, null), compiledCode, cl);
// set the classpath
List<String> options = new ArrayList<String>();
options.add("-classpath");
StringBuilder sb = new StringBuilder();
Enumeration<URL> resources = parent.getResources("/");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
sb.append(url.getFile()).append(File.pathSeparator);
}
options.add(sb.toString());
// execute the compiler
Boolean call = javac.getTask(null, fileManager, null, options, null, compilationUnits).call();
if (call) {
return cl.loadClass(className);
}
return null;
}
Using wildfly I had to change URLClassLoader urlClassLoader = (URLClassLoader) parent;
to Enumeration<URL> resources = parent.getResources("/");
Related Topics
Java.Net.Connectexception: Connection Refused
How to Parse Command Line Arguments in Java
Jtextfields on Top of Active Drawing on Jpanel, Threading Problems
How to Convert from Int to String
Why Can't I Do Assignment Outside a Method
Java Runtime.Getruntime(): Getting Output from Executing a Command Line Program
What's the Difference Between @Component, @Repository & @Service Annotations in Spring
How to Initialize an Array in Java
Http Url Address Encoding in Java
Should Java 8 Getters Return Optional Type
Occurrences of Substring in a String
Swing Animation Running Extremely Slow