1

I am trying to load classes from a dynamically loaded jar file. Currently I can load a class that doesn't extend to a class other than the root class Object, but I can't load a class that extends a custom class. The detail description of my problem is as follows.

In project A, there is a class A like:

class A {
}

In project B, there are a class B1 and a class B2 like:

class B1 {
}

class B2 extends A {
}

I exported the project A to a.jar, and the project B to b.jar. The testing code is:

File f = new File("Z:\\Jars\\b.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() },
        System.class.getClassLoader());

Class<?> classB1 = (Class<?>) urlClassLoader.loadClass("b.B1");
System.out.println("B1 is loaded");

Class<?> classB2 = (Class<?>) urlClassLoader.loadClass("b.B2");
System.out.println("B2 is loaded");

The class B1 is loaded successfully, but the class B2 is not loaded due to class a.A is not found exception:

java -cp a.jar;b.jar; a.Main
B1 is loaded
Exception in thread "main" java.lang.NoClassDefFoundError: a/A
Caused by: java.lang.ClassNotFoundException: a.A
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 12 more

How can I resolve this issue? Thanks.

PS. With the inclusion of a.jar, the class B2 can be loaded but fails the following test:

Class<A> classB2 = (Class<A>) urlClassLoader.loadClass("b.B2");
A ab2 = classB2.newInstance();

The first line is fine but the second line triggered the following exception:

Exception in thread "main" java.lang.ClassCastException: b.B2 cannot be cast to a.A
    at a.Main.main(Main.java:22)

B2 is supposed to be an A instance, any idea why this happened?

  • How will you build jar b.jar without including class A? Are you manually removing A.class from b.jar? – Mahesh Oct 16 '16 at 09:15
  • I was using Eclipse, I set the project B to depend on the project A, so the project B can be built and be able to export a jar file. For above demo case my problem was resolved by including a.jar, the class B2 can be loaded and can be cast to A. – DeepParticle Oct 16 '16 at 10:20
  • 1
    The first answer in http://stackoverflow.com/questions/2591779/cast-across-classloader said that the classes for the same class loaded by different classloaders can not be cast into each other. – DeepParticle Oct 16 '16 at 12:00
  • @DeepParticle correct, see my answer as to what you can do about it. – Peter Lawrey Oct 16 '16 at 14:12

1 Answers1

0

You have two classes called a.A one loaded by the default class loader of your thread and another loaded by your custom class loader.

This is where the message is confusing because you effectively have two classes which print a.A but from different class loaders you can't exchange one for the other. What you need to do is

Class classB2 = urlClassLoader.loadClass("b.B2")
Object ab2 = classB2.newInstance();

This is because the a.A which your thread will load is from the default class loader and you can't use this even though it happens to have the same name.

Another way around this is to have only one class loader load the a.A with

File f = new File("Z:\\Jars\\b.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() });

This will use the ClassLoader.getSystemClassLoader() to load a.A so there is only one class of that name.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • thanks for your clear explanation. The context class loader you proposed is a new concept for me, I will see if I can make it work. – DeepParticle Oct 16 '16 at 14:57
  • @DeepParticle it's the one the current thread uses to load classes. – Peter Lawrey Oct 16 '16 at 14:59
  • 1
    it would be better just to get A's classloader than using the context classloader – the8472 Oct 16 '16 at 16:56
  • @the8472 they should be the same most of the time. Using `A.class.getClassLoader()` would be more restrictive which might be best but it also might be less likely to work. – Peter Lawrey Oct 16 '16 at 16:59
  • 1
    Please, be careful with such statements. The concept of the context loader might have been something along “*the one the current thread uses to load classes*” when this feature was introduced, but threads don’t load classes. So it’s a pure convention to use that class loader when loading classes dynamically or when constructing new class loaders, but actually, this context loader is irrelevant to the JVM. It defaults to be the same as `ClassLoader.getSystemClassLoader()`, this is why it works here. But it would also work by removing the `ClassLoader` parameter completely. – Holger Oct 17 '16 at 15:00
  • 1
    I.e. using `URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { f.toURL() });` would work. It’s the `System.class.getClassLoader()` parameter in the original code which explicitly opted out the application class loader as `System.class` is loaded by the bootstrap loader. `System.class.getClassLoader()` *should not be confused with* `ClassLoader.getSystemClassLoader()` – Holger Oct 17 '16 at 15:03
  • @Holger your suggestion also has the advantage of being simpler. +1 – Peter Lawrey Oct 17 '16 at 16:52