I am trying to compile, load and use an inherited class at runtime. Here is the structure of my Java/Maven project. Note there are no .java files under com.mycompany.model.inherited
. This is where generated class files will be placed.
src/main/java
com.mycompany.model.base
BaseClass.java
src/test/java
com.mycompany.model.inherited
BaseClass.java
package com.mycompany.model.base;
public abstract class BaseClass {
public abstract void doWork();
}
At runtime I write the contents of an inherited class to src/test/java/com/mycompany/inherited/SubClass.java
package com.mycompany.model.inherited;
import com.mycompany.model.BaseClass;
public class SubClass extends BaseClass {
@Override
public void doWork() {
System.out.println("SubClass is doing work.");
}
}
And then I execute this code to compile SubClass.java, load it, create an object instance. All of that works fine. But when I try to cast it to a BaseClass I'm getting a ClassCastException
at runtime. Here's the full code.
// prepare the class file contents and write it to disk.
String subClassContents =
"package com.mycompany.model.inherited;\n"
"\n"
"import com.mycompany.model.base.BaseClass;\n"
"\n"
"public class SubClass extends BaseClass {\n"
"\n"
" @Override\n"
" public void doWork() {\n"
" System.out.println(\"SubClass is doing work.\");\n"
" }\n"
"}\n";
File file = new File(System.getProperty("user.dir") "\\src\\test\\java\\com\\mycompany\\model\\inherited\\SubClass.java");
FileWriter writer = new FileWriter(file);
writer.write(subClassContents);
writer.close();
// compile the class using the current classpath.
File[] files = new File[]{file};
List<String> options = new ArrayList<>();
options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager filemanager = compiler.getStandardFileManager(null, null, null);
Iterable fileObjects = filemanager.getJavaFileObjects(files);
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, options, null, fileObjects);
task.call();
// load the class using the current classpath.
String[] paths = System.getProperty("java.class.path").split(";");
List<URL> urls = new ArrayList<>();
urls.add(new File("src/test/java").toURI().toURL());
for (String p : paths) {
urls.add(new File(p).toURI().toURL());
}
URLClassLoader classLoader = URLClassLoader.newInstance(urls.stream().toArray(URL[]::new));
Class<?> cls = Class.forName("com.mycompany.model.inherited.SubClass", true, classLoader);
// use the class.
BaseClass base = (BaseClass) cls.newInstance();
base.doWork();
java.lang.ClassCastException: com.mycompany.model.inherited.SubClass cannot be cast to com.mycompany.model.base.BaseClass
If I include src/test/java/com/mycompany/model/inherited/SubClass.java at compile time it works fine. I.e:
BaseClass base = (BaseClass) SubClass.class.getClassLoader().loadClass("com.mycompany.model.inherited.SubClass").newInstance();
base.doWork();
Hoping someone can point to the issue and help me solve it. Thanks!
CodePudding user response:
The cast failed because the base class of SubClass
is actually considered a different class from the "BaseClass
" that you wrote in your source code. They are considered different because they are loaded by different class loaders. The base class of SubClass
is loaded by the URLClassLoader
you created, whereas the BaseClass
you wrote in the source code is presumably loaded by the system class loader.
To fix this, you can set the parent class loader of the URLClassLoader
to the same class loader as the one that loaded BaseClass
, and only give src/test/java
as the class path. This way, when the URLClassLoader
loads SubClass
, it can't find BaseClass
in src/test/java
and so would use the parent class loader to load it.
URLClassLoader classLoader = URLClassLoader.newInstance(
new URL[] { new File("src/test/java").toURI().toURL() },
BaseClass.class.getClassLoader()
);