Mathias
Mathias

Reputation: 317

How to load classes from jar file in correct order

I have written a Java Classloader to load classes from a jar file.

Enumeration<JarEntry> entries = jarFile.entries();
    while (entries.hasMoreElements()) {
        JarEntry element = entries.nextElement();
        if (element.getName().endsWith(".class")) {
               //Class Manipulation via ASM
               //Loading class with MyClassloader
        }
    }

The Problem is: When I load a Class, which Sublcass from a Class in the same Jar and the subclass isn't already loaded, I get a ClassNotFoundException.

Example:

class A extends B{}
Class B{}

Because of the alphabetical order, Class A is loaded first. I Get a ClassNotFoundException for Class B. At this time, Class B isn't loaded.

Upvotes: 3

Views: 2366

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 43972

I assume that your class loading is performed as some sort of pushing class files. You should however rather pull them. To explain what I mean by this, let us look at a short example of normal Java class loading:

class Main {
  public static void main(String[] args) {
    new B();
  }
}
class B extends A { }
class A { }

When creating a new B(), the class loader of Main basically executes classLoader.loadClass("B"). At this point, B's super class A is not yet loaded. At the same time, the class loader cannot know that B has A as its super class. Thus, the class loader takes responsibility for loading the class by asking itself to classLoader.loadClass("A") before the class loading of B is completed.

Let us assume that the class loader did not know about either A or B but it had a method to explicitly load classes it is receives by an external entity with classLoader.inject(String, byte[]). This calling sequence would then not compute:

classLoader.inject("B", bBytes);
classLoader.inject("A", aBytes);

because while loading B, the class loader would not yet know about A.

What you need to do when implementing your own class loader is to store the classes in some sort of map and to implement the class loader's class loading method something like:

protected Class<?> findClass(String name) throws ClassNotFoundException {
  byte[] bytes = map.get(name);
  if (bytes != null) {
    return defineClass(name, bytes, 0, bytes.length);
  } else {
    throw new ClassNotFoundException(name);
  }
}

By allowing the class loader to determine the loading order, you avoid this problem altogether.

To be even more precise, you need to do manipulation and loading in two steps where a pseudo algorithm would look something like this:

Enumeration<JarEntry> entries = jarFile.entries();
MyClassLoader classLoader = new MyClassLoader();
// First we generate ALL classes that the class loader is supposed to load.
// We then make these classes accessible to the class loader.
while (entries.hasMoreElements()) {
  JarEntry element = entries.nextElement();
  if (element.getName().endsWith(".class")) {
     // Class Manipulation via ASM
     classLoader.addClass( ... );
  }
}
// Now that the class loader knows about all classes that are to be loaded
// we trigger the loading process. That way, the class loader can query
// itself about ANY class that it should know.
while (entries.hasMoreElements()) {
  JarEntry element = entries.nextElement();
  if (element.getName().endsWith(".class")) {
     classLoader.loadClass( ... );
  }
}

Upvotes: 4

Related Questions