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!
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()
);
Related
I have created a class "Class B"
package com.b
public class B
{
public void printMsg()
{
System.out.println("Called");
}
}
I have created a jar file with below class "Class A"
package com.a
import com.b
public class A extends B
{
}
When i tried to load jar dynamically using below code, I am getting error "Class B" "classnotfoundexception"
ClassLoader cl = new URLClassLoader(new URL[] { new URL(jarFullPath) });
Class<?> cla = cl.loadClass(className);
Object obj = cla.newInstance();
I believe that it is because you did not provide any parent ClassLoader to your URLClassLoader such that it can find A but not B, try this instead:
ClassLoader cl = new URLClassLoader(
new URL[] {new URL(jarFullPath)}, Thread.currentThread().getContextClassLoader()
);
This use Thread.currentThread().getContextClassLoader() as parent ClassLoader corresponding to the context ClassLoader.
I've compiled at runtime a java file on file system which is a very simple class:
public class Test {
public static int res(int a, int b) {
return a*b;
}
}
now I just want to call res method from my project (that will be a jar), but my code produce java.lang.ClassNotFoundException: Test
and this is how I've loaded the class:
URL[] urls = new URL[] {new URL("file:///"+UtilOverriding.getFile1().replace("java", "class"))};
URLClassLoader loader = new URLClassLoader(urls);
Class clazz = loader.loadClass("Test");
When you specify a class path you have to provide the directory which is the parent to all your packages. Try instead.
new URL("file:///"+UtilOverriding.getFile1().getParent()}
I need to allow a user to specify the implementation of an interface at runtime via a config file, similar to in this question: Specify which implementation of Java interface to use in command line argument
However, my situation is different in that the implementations are not known at compile time, so I will have to use reflection to instantiate the class. My question is ... how do I structure my application such that my class can see the new implementation's .jar, so that it can load the class when I call:
Class.forName(fileObject.getClassName()).newInstance()
?
The comment is correct; as long as the .jar file is in your classpath, you can load the class.
I have used something like this in the past:
public static MyInterface loadMyInterface( String userClass ) throws Exception
{
// Load the defined class by the user if it implements our interface
if ( MyInterface.class.isAssignableFrom( Class.forName( userClass ) ) )
{
return (MyInterface) Class.forName( userClass ).newInstance();
}
throw new Exception("Class "+userClass+" does not implement "+MyInterface.class.getName() );
}
Where the String userClass was the user-defined classname from a config file.
EDIT
Come to think of it, it is even possible to load the class that the user specifies at runtime (for example, after uploading a new class) using something like this:
public static void addToClassPath(String jarFile) throws IOException
{
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class loaderClass = URLClassLoader.class;
try {
Method method = loaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(classLoader, new Object[]{ new File(jarFile).toURL() });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException( t );
}
}
I remember having found the addURL() invocation using reflection somewhere here on SO (of course).
I get a class file at runtime and I am saving it in a particular location. How can I create an object of this class:
Class.forName(MyDynamicClass);
This does not work. The class does not have any package, so how to instantiate this class ?
I just have the MyDynamicClass.class file which is in my home folder
I tried "Amir Afghani" answer which throws the ClassNotFound Exception.
Steps that you can try to debug:
Check your class path
When you perform Class.forName("MyDynamicClass");
It checks in your classpath for the file MyDynamicClass.class and loads it
When you run your program, run with -verbose argument
This will help you identify how this class is searched and is loaded once you fix the class path issue
If you do not want to add this to your classpath, then you will most definitely have to write your own class loader (See java.net.URLClassLoader which provides an easier way to do this). This will allow you to put custom paths to be loaded
I have tested with and without package and both cases working fine.
Below are the classes
Default Package:
public class MyDynamicClass {
}
Under package com.test
package com.test;
public class MyDynamicClass {
}
package com.test;
public class MyDynamicClassTest {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.test.MyDynamicClass");
Class clazz2 = Class.forName("MyDynamicClass");
System.out.println("clazz :: "+ clazz);
System.out.println("clazz :: "+ clazz2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Output:
clazz :: class com.test.MyDynamicClass
clazz :: class MyDynamicClass
You can call newInstance() to create object once you have class object.
How are you compiling your class? Can you make sure there is .class file exist?
ClassLoader myClassLoader = ClassLoader.getSystemClassLoader();
// Step 2: Define a class to be loaded.
String classNameToBeLoaded = "MyDynamicClass";
// Step 3: Load the class
Class myClass = myClassLoader.loadClass(classNameToBeLoaded);
// Step 4: create a new instance of that class
Object whatInstance = myClass.newInstance();
I'm trying to test that a class can be loaded to the jvm more then once but using different ClassLoader
so my code tries to load a class (class name "Tzvika") twice
first using the default ClassLoader
and in the second try using the URLClassLoader
the problem is that i get the same reference for the URLClassLoader and the default ClassLoader
what i'm doing wrong?
here code:
public static void main(String[] args) {
Tzvika t1 = new Tzvika();
System.out.println("t1 class loader: " + t1.getClass().getClassLoader());
Tzvika t2 = null;
try {
URLClassLoader clsLoader = URLClassLoader.newInstance(new URL[] {new URL("file:/C://data/workspace/ashrait/tests/SampleClassLoader/bin/com/tzvika/sample/")});
// same problem when i do this
//URLClassLoader clsLoader = new URLClassLoader(new URL[] {new URL("file:/C://data/workspace/ashrait/tests/SampleClassLoader/bin/com/tzvika/sample/")});
Class cls = clsLoader.loadClass("com.tzvika.sample.Tzvika");
t2 = (Tzvika)cls.newInstance();
System.out.println("t2 class loader: " + t2.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
here my console output:
t1 class loader: sun.misc.Launcher$AppClassLoader#1a52fdf
t2 class loader: sun.misc.Launcher$AppClassLoader#1a52fdf
Create the URLClassLoader without a parent.
URLClassLoader clsLoader = URLClassLoader.newInstance(new URL[] {new URL("file:/C://data/workspace/ashrait/tests/SampleClassLoader/bin/com/tzvika/sample/")}, null);
Note also that you should specify the path to the root of the classpath, ie.
URLClassLoader clsLoader = URLClassLoader.newInstance(new URL[] {new URL("file:/C://data/workspace/ashrait/tests/SampleClassLoader/bin")});
To better clarify, the constructor of URLClassLoader states
The URLs will be searched in the order specified for classes and
resources after first searching in the specified parent class loader.
So when you try to load a class from this ClassLoader, it will first check the parent ClassLoader, which is the bootstrap classloader, for the existence of that class. Since that parent class loader has already loaded a com.tzvika.sample.Tzvika class, it will return it.
Now, since the classes loaded by the parent class loader and your clsLoader are different, the instances of these classes therefore belong to different classes. So although you have com.tzvika.sample.Tzvika from both ClassLoaders, they are very much different.
That's the whole point of different ClassLoaders.