I'm generating .java
class files at the runtime and need to utilize those classes inside the code instantly.
So I compile the .java
classes using Compiler API to make .class
files:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StandardJavaFileManager manager = compiler.getStandardFileManager(diagnostics, null, null);
File file = new File("path to file");
Iterable<? extends JavaFileObject> sources = manager.getJavaFileObjectsFromFiles(Arrays.asList(file));
CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, sources);
task.call();
manager.close();
Then I need to get references to those compiled classes using Class.forName()
, But if I just call Class.forName("com.foo.Bar")
it throws ClassNotFoundException
, Assuming it's because the new .class
files aren't added to classpath
I looked for the methods of adding classes to the classpath
at runtime. I encountered some ambiguities related to this concept:
1. Is this approach (of compiling the .java
file first, using compiler API, and add it to class loader at the second step) correct? To be able utilizing the class in the code instantly.
2. AFAIK, There are 2 methods to dynamically load classes into classpath at runtime: one is using a custom ClassLoader like this: (which I had error to compile as it complained that BuiltinClassLoader
doesn't have addURL
method):
// Get the ClassLoader class
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> clazz = cl.getClass();
// Get the protected addURL method from the parent URLClassLoader class
Method method = clazz.getSuperclass().getDeclaredMethod("addURL", new Class[] { URL.class });
// Run projected addURL method to add JAR to classpath
method.setAccessible(true);
method.invoke(cl, new Object[] { cls });
Another method is using Class.forName(name, instantiation, classLoader)
to add a class to the classpath (which gives the class reference at the same too). The first method I wasn't able to apply since I got a compiler error (Java 11) as mentioned above.
Regarding the second method, Would Class.forName(name, instantiation, classLoader)
attach the new classes to the classpath
if we call the default class loader like this? :
Class.forName("com.foo.Bar",true, ClassLoader.getSystemClassLoader());
// or:
Class.forName("com.foo.Bar",true, ApiHandler.class.getClassLoader());
It doesn't work for me. Which variation of the above classLoader arguments
are correct and why do these don't work? Is it mandotary to create a custom classloader and pass it to the Class.forName()
?
3. I'm making the .java
files inside the com.foo
package in src
folder of the eclipse project. Their compiled .class
files are also generated at the same folder (using compiler API). When I refresh project using eclipse (right-click on the project -> Refresh) the related .class
files would be generated in the target/classes
folder and that's when the classes could be accessed through the code (e.g using Class.forName("com.foo.Bar)
. May it be that if I produce .class
files (by compiler API) in the target/classes
folder, The classes would be recognizable without the need of introducing them to the classpath?
UPDATE:
I was able to use the compiled classes in my code, By saving the respected .class
files in the target/classes
folder, mentioned in the 3rd question above) of the project. (By adding -d
option to the compiler's getTask()
method:
Iterable<String> options = Arrays.asList( new String[] { "-d", System.getProperty("user.dir") + "/target/classes/"} );
.
.
.
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources);
This way, It seems the classes are not even required to be added to the classpath using classLoader; as the class is accessible using a simple Class.forName()
. How Do you explain this?
Class<?> cls1 = Class.forName("com.foo.Bar");
And also with through the ClassLoader way, of course:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> cls = classLoader.loadClass("com.foo.Bar");