You can use Java to load and reload classes at run time, although it’s not as simple as one would like. This article explains when and how to load and reload classes in Java.

All classes in a Java application are loaded using some subclass of java.lang.ClassLoader. Therefore, dynamically loading classes must also be done using a java.lang.ClassLoader subclass.

When a class is loaded, all classes it references are loaded as well. This class loading pattern is recursive until all required classes have been loaded. This may not be all classes in your application. Unreferenced classes are loaded only when referenced.

Class loaders in Java are organized into a hierarchy. When you create a new standard Java ClassLoader, you must provide a ClassLoader for the parent class. If a ClassLoader is asked to load a class, it asks its parent ClassLoader to load it. If the parent class loader cannot find the class, the subclass loader will try to load it itself.

The steps used by a given classloader to load a class are as follows:

  1. Check that the class is loaded.
  2. If not, the parent class loader is requested to load the class.
  3. If the parent class loader cannot load the class, try to load it into the class loader. When you implement a classloader that reloads classes, you will need to deviate from this sequence. The parent class loader should not request class loads to be loaded. See Java’s ClassLoader for details.

Loading classes dynamically is easy. All you need to do is get a ClassLoader and call its loadClass () method.

public class MainClass {

  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = "+ aClass.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); }}Copy the code

Dynamic class reloading is a little more challenging. Java’s built-in Class loader always checks to see if a Class is already loaded before loading. Therefore, built-in class loaders using Java cannot reload classes. To reload a class, you will have to implement your own ClassLoader subclasses.

Even using a custom subclass of ClassLoader is a challenge. Every loaded class needs a link. This is done using the classLoader.resolve () method. This method is final and therefore cannot be overridden in your ClassLoader subclass. The resolve () method will not allow any given ClassLoader instance to connect to the same class twice. Therefore, every time you want to reload a class, you must use a new instance of the ClassLoader subclass. This is not impossible, but needs to be known when designing a class reload.

As mentioned earlier, you cannot reload a class using a ClassLoader that has already loaded the class. Therefore, you will have to reload the class with a different ClassLoader instance. But it brings new challenges.

Each class loaded into a Java application is identified by its fully qualified name (package name + class name) and the ClassLoader instance that loaded it. This means that the MyObject class loaded by classloader A is not the same class as the MyObject class loaded by classloader B. Take a look at the following code:

MyObject object = (MyObject)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
Copy the code

Notice how the MyObject class is referenced in the code as a type for an object variable. This causes the MyObject class to be loaded by the same classloader that loaded the class in which the code was created.

If the myClassReloadingFactory object factory reloads the MyObject class using a different class loader than the one in which the code above resides, you cannot convert the reloaded instance of the MyObject class to the MyObject type of the object variable. Because two MyObject classes are loaded with different class loaders, they are treated as different classes even if they have the same fully qualified class name. Attempting to convert an object of one class to a reference of another class causes a ClassCastException.

It is possible to work around this limitation, but you will have to change your code in one of two ways:

  1. Use an interface as the variable type and reload the implementation class.
  2. Use the parent class as the variable type and reload a subclass. Here is an example of code:
MyObjectInterface object =(MyObjectInterface)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
Copy the code
MyObjectSuperclass object = (MyObjectSuperclass)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
Copy the code

Both methods work if the type of the variable (interface or superclass) is not overloaded when the implementation class or subclass is overridden.

In order for this to work, you of course need to implement your classloader that lets the parent class load the interface or parent class. When your classloader is asked to load the MyObject class, it will also be asked to load the MyObjectInterface or MyObjectSuperclass classes, since these classes are referenced from the MyObject class. Your classloader must delegate the loading of these classes to the same classloader that loads classes that contain interfaces or supertype variables. ### Class loader loading/reloading examples The text above covers many topics. Let’s look at a simple example. Here is an example of a simple ClassLoader subclass. Notice how it delegates class loading to its parent class, except for classes it can reload. If the loading of this class is delegated to the parent class loader, it cannot be reloaded later. Remember that a class can only be loaded once by the same ClassLoader instance.

As mentioned earlier, this is just an example to show you the basics of ClassLoader behavior. Your own classloader should probably not be limited to one class, but rather a collection of classes that you need to reload. In addition, you should not hardcode the classpath.

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();

            while(data ! = -1){ buffer.write(data); data = input.read(); } input.close(); byte[] classData = buffer.toByteArray();return defineClass("reflection.MyObject",
                    classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        returnnull; }}Copy the code

###### Here is an example using MyClassLoader.

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject"); AnInterface2 object1 = (AnInterface2) myObjectClass.newInstance(); MyObjectSuperClass object2 = (MyObjectSuperClass) myObjectClass.newInstance(); ClassLoader = new MyClassLoader(parentClassLoader); myObjectClass = classLoader.loadClass("reflection.MyObject");

    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();

}
Copy the code

This is the Reflection.MyObject class loaded with the class loader. Notice how it extends a superclass and implements an interface. This is just for this example. In your own code, you only need one of these – extensions or implementations.

public class MyObject extends MyObjectSuperClass implements AnInterface2{ //... Override parent methods // or implement interface methods}Copy the code