Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force Java to reload class upon instantiation?

Background:
I have a MainTest class that has many buttons, each of which instantiate a class that I am coding/testing. I want the code/test cycle for these classes to be quick, and see the effect of my changes quickly, a few times a minute. MainTest which is stable takes about 20 seconds to load, which would not be a problem had I not needed to reload it for each change in the classes it instantiates. I want to load MainTest once, and when it instantiates another class, let's call it ChildTest, numerous times (upon button event), it should reload the latest version of ChildTest.

The question in short:
How do you tell the java 'new' command to reload the class from disk and not from jvm cache?


I tried Class.ForName but it didn't make a difference.
I have also tried using a custom classloader (copied from open source), to no avail.

like image 269
nat101 Avatar asked Oct 19 '10 18:10

nat101


People also ask

How do you reload a Java class?

Once a Java class has been loaded by a class loader, it's immutable and will last as long as the class loader itself. The identity is the class name and class loader identity, so to reload an application, you'll need to create a new class loader which in turn will load the latest version of the app classes.

Is it possible to load a class by two ClassLoader?

A class is always identified using its fully qualified name (package. classname). So when a class is loaded into JVM, you have an entry as (package, classname, classloader). Therefore the same class can be loaded twice by two different ClassLoader instances.

Can we create custom class loader?

To create a custom class loader, we will create a class that will extend ClassLoader. There is a method findClass() that must be overridden. Create a method that will load your given class from the class path. In our case we have created the method loadClassData() that will return byte[].

How do you delete a class in ClassLoader?

You can unload a ClassLoader but you cannot unload specific classes. More specifically you cannot unload classes created in a ClassLoader that's not under your control. If possible, I suggest using your own ClassLoader so you can unload.


1 Answers

There's no hope of "overloading" the new operator but you could certainly write a custom class loader that simply reloads the bytecode every time you ask it to load a class. No out-of-the-box classloaders will do what you're looking for because they all assume that the class definition will not change through the life of the JVM.

But here's how you make it happen. Create a class loader called, say, Reloader which overrides the methods loadClass and findClass methods so that they simply reload the class files from disk every time they are called (instead of "caching" them for later use). Then you just have to call new Reloader().loadClass("foo.bar.MyClassName") any time you suspect the class definition has changed (e.g. as part of your testing framework's lifecycle methods).

This article fills in some of the details but misses some important points, especially about using new instances of the classloader for subsequent reloads and delegating to the default classloader when appropriate. Here is a simple working example which repeatedly loads the class MyClass and assumes its class file exists in the relative "./bin" directory:

public class Reloader extends ClassLoader {
    public static void main(String[] args) throws Exception {
        do {
            Object foo = new Reloader().loadClass("MyFoo").newInstance();
            System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect
            System.out.println("Press <ENTER> when MyFoo.class has changed");
            System.in.read();
        } while (true);
    }

    @Override
    public Class<?> loadClass(String s) {
        return findClass(s);
    }

    @Override
    public Class<?> findClass(String s) {
        try {
            byte[] bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException ioe) {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException ignore) { }
            ioe.printStackTrace(System.out);
            return null;
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
}

At each invocation of the "do/while" block in the main method, a new Reloader is instantiated which loads the class from disk and returns it to the caller. So if you overwrite the bin/MyClass.class file to contain a new implementation with a different, overloaded toString method, then you should see the new implementation each time.

like image 127
maerics Avatar answered Oct 03 '22 01:10

maerics