Java Reflection: dynamic class loading and overloading

Keywords: Java Programming Back-end Programmer

Content index

Class loader
Class loading system
Class loading
Dynamic class loading
Dynamic class overloading
Custom class overload
Class load / reload example


Java allows you to dynamically load and reload classes at run time, but this function is not as simple and direct as people want. This article will explain how to load and overload classes in Java.
You may question why the Java dynamic class loading feature is part of the java reflection mechanism rather than part of the Java core platform. Anyway, this article has been put into the java reflection series, and there is no better series to include it.

Class loader

All classes in Java applications are loaded by a series of subclasses of the java.lang.ClassLoader class. Therefore, if you want to load classes dynamically, you must also use the subclass of java.lang.ClassLoader.

Once a class is loaded, all classes referenced by this class will be loaded at the same time. The class loading process is a recursive pattern, and all related classes will be loaded. However, not all classes in an application will be loaded. Classes unrelated to the reference chain of the loaded class will not be loaded until there is a reference relationship.

Class loading system

Class loading is an orderly system in Java. When you create a new standard Java class loader, you must provide its parent loader. When a class loader is called to load a class, it will first call the parent loader of the loader to load. If the parent loader cannot find the class, the loader will try to load the class.

Class loading

The order in which the class loader loads classes is as follows:
1. Check whether the class has been loaded.
2. If it is not loaded, the parent loader is called first.
3. If the parent loader cannot load the class, try loading the class.

When you implement a class loader with overloaded class function, its order will be somewhat different from the above. Class overloading does not request its parent loader to load. It will be explained in the following paragraphs.

Dynamic class loading

Dynamically loading a class is very simple. All you have to do is to get a class loader and call its loadClass() method. Here is an example:

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();

    } 

}

Dynamic class overloading

Dynamic class overloading is a little complicated. Java's built-in class loader checks whether a class has been loaded before loading it. Therefore, overloading a class cannot use Java's built-in class loader. If you want to overload a class, you need to inherit ClassLoader manually.

After you customize the subclasses of ClassLoader, you still have some things to do. All loaded classes need to be linked. This process is done through the ClassLoader.resolve() method. Because this is a final method, this method cannot be overridden in subclasses of ClassLoader. The resolve() method does not allow a given ClassLoader instance to link a class twice. So whenever you want to overload a class, you need to use a new subclass of ClassLoader. This is a necessary condition when you design class overload functions.

Custom class overload

As mentioned earlier, you cannot overload a class with a class loader that has already loaded a class. Therefore, you need other ClassLoader instances to overload this class. But this brings some new challenges.

All classes loaded into Java applications use the full name of the class (package name + class name) as a unique identifier for the ClassLoader instance to load. This means that class MyObject is loaded by class loader A. if class loader B loads the MyObject class, the classes loaded by the two loaders are different. Look at the following code:

MyObject object = (MyObject)

    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

The MyObject class is referenced in the above code, and its variable name is object. This causes the class MyObject to be loaded by the class loader of the class where this code is located.

If the myclassreloading factory object overloads the MyObject class with a different class loader, you cannot cast the instance of the overloaded MyObject class to an object variable of type MyObject. Once the MyObject class is loaded by two class loaders, it will be considered as two different classes, even though their full class names are exactly the same. If you try to convert the instances of these two classes, you will report ClassCastException.
You can solve this limitation, but you need to modify your code in the following two aspects:
1. Mark this variable type as an interface, and then overload only the implementation class of this interface.
2. Mark the variable type as a superclass, and then overload only the subclasses of the superclass.

Take the following two examples:

MyObjectInterface object = (MyObjectInterface)

    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

 MyObjectSuperclass object = (MyObjectSuperclass)

    myClassReloadingFactory.newInstance("com.jenkov.MyObject");

As long as the variable type is a superclass or interface, the two methods can operate normally. When their subclasses or implementation classes are overloaded, the superclass and interface will not be overloaded.

To ensure that this method works, you need to manually implement the class loader so that these interfaces or superclasses can be loaded by its parent loader. When your class loader loads the MyObject class, the superclass MyObjectSuperclass or the interface MyObjectSuperclass will also be loaded because they are the dependencies of MyObject. Your class loader must load these classes into the same class loader. This class loader loads this class including interfaces or superclasses.

Class load / reload example

Just talking, not practicing fake moves. Let's look at a simple example. The following example is a subclass of a class loader. Notice how it proxies the loading of a class to its parent loader when the class does not want to be overloaded. If a class is loaded by its parent loader, the class will not be overloaded in the future. Remember that a class can only be loaded once by the same ClassLoader instance.
As I said before, this is just a simple example, which will show you the basic behavior of class loader. This is not a template that you can use directly to design class loaders in your project. There should be more than one class loader designed by yourself. If you want to overload classes, you may design many loaders. And you won't hardcode the path of the class to be loaded into your code like this.

//java learning and exchange: 737251827 enter to receive learning resources and ask questions about leaders with ten years of development experience for free!
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();

        } 

        return null;
    }

}

The following is an example of 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(); 

   //create new class loader so classes can be reloaded.
   classLoader = new MyClassLoader(parentClassLoader);

    myObjectClass = classLoader.loadClass("reflection.MyObject");
 

    object1 = (AnInterface2)       myObjectClass.newInstance();

    object2 = (MyObjectSuperClass) myObjectClass.newInstance(); 

}

The following is the loaded reflection.MyObject class. Note that it both inherits a superclass and implements an interface. This is only done to demonstrate this feature through examples. In your custom case, you may implement only one class or inherit one or two interfaces.

view sourceprint?

public class MyObject extends MyObjectSuperClass implements AnInterface2{

    //... body of class ... override superclass methods

    //    or implement interface methods

}

Posted by semlabs on Fri, 26 Nov 2021 20:05:29 -0800