Reload used classes at runtime Java
Asked Answered
D

1

18

I'm working on a program that watches a directory and runs all tests in the directory when it sees changes in the directory.

This requires the program to dynamically load the classes, instead of getting the cached copies.

I can dynamically load the test classes. Changes to the tests get detected and used at runtime. However, this isn't the case for the classes tested by the tests.

My code for dynamically loading the classes and returning a list of test classes:

List<Class<?>> classes = new ArrayList<Class<?>>();
    for (File file : classFiles) {
        String fullName = file.getPath();
        String name = fullName.substring(fullName.indexOf("bin")+4)
                .replace('/', '.')
                .replace('\\', '.'); 
        name = name.substring(0, name.length() - 6);

            tempClass = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()).findClass(name)          } catch (ClassNotFoundException e1) {
            // TODO Decide how to handle exception
            e1.printStackTrace();
        }

        boolean cHasTestMethods = false;
        for(Method method: tempClass.getMethods()){
            if(method.isAnnotationPresent(Test.class)){
                cHasTestMethods = true;
                break;
            }
        }
        if (!Modifier.isAbstract(cachedClass.getModifiers()) && cHasTestMethods) {
            classes.add(tempClass);
        }
    }
    return classes;

with DynamicClassLoader being as the Reloader described here How to force Java to reload class upon instantiation?

Any idea how to fix it? I thought all classes would be dynamically loaded. Note however that I don't overwrite loadclass in my DynamicClassLoader because if I do my test classes give init

EDIT: This doesn't work, the class gets loaded but the tests in it aren't detected...

List<Request> requests = new ArrayList<Request>();
    for (File file : classFiles) {
        String fullName = file.getPath();
        String name = fullName.substring(fullName.indexOf("bin")+4)
                .replace('/', '.')
                .replace('\\', '.'); 
        name = name.substring(0, name.length() - 6);
        Class<?> cachedClass = null;
        Class<?> dynamicClass = null;
        try {
            cachedClass = Class.forName(name);


            URL[] urls={ cachedClass.getProtectionDomain().getCodeSource().getLocation() };
            ClassLoader delegateParent = cachedClass .getClassLoader().getParent();
            URLClassLoader cl = new URLClassLoader(urls, delegateParent) ;
            dynamicClass = cl.loadClass(name);
            System.out.println(dynamicClass);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

Edit edit: i detect the test methods like this:

            for(Method method: dynamicClass.getMethods()){
            if(method.isAnnotationPresent(Test.class)){
                requests.add(Request.method(dynamicClass, method.getName()));
            }
        }
Depurative answered 20/11, 2013 at 8:43 Comment(1)
Uhm, your code is one character long?Pokeweed
J
15

If you used the custom ClassLoader exactly like in the linked answer it is not overriding the method protected Class<?> loadClass(String name, boolean resolve). This implies that when the JVM is resolving dependencies it will still delegate to the parent class loader. And, of course, when it was not delegating to the parent ClassLoader it had the risk of missing some required classes.

The easiest solution is to set up the right parent class loader. You are currently passing Thread.currentThread().getContextClassLoader() which is a bit strange as your main intention is that the delegation should not delegate to that loader but load the changed classes. You have to think about which class loaders exist and which to use and which not. E.g. if the class Foo is within the scope of your current code but you want to (re)load it with the new ClassLoader, Foo.class.getClassLoader().getParent() would be the right delegate parent for the new ClassLoader. Note that it might be null but this doesn’t matter as in this case it would use the bootstrap loader which is the correct parent then.

Note that when you set up the right parent ClassLoader matching your intentions you don’t need that custom ClassLoader anymore. The default implementation (see URLClassLoader) already does the right thing. And with current Java versions it is Closeable making it even more suitable for dynamic loading scenarios.

Here is a simple example of a class reloading:

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class ReloadMyClass
{
  public static void main(String[] args)
  throws ClassNotFoundException, IOException {
    Class<?> myClass=ReloadMyClass.class;
    System.out.printf("my class is Class@%x%n", myClass.hashCode());
    System.out.println("reloading");
    URL[] urls={ myClass.getProtectionDomain().getCodeSource().getLocation() };
    ClassLoader delegateParent = myClass.getClassLoader().getParent();
    try(URLClassLoader cl=new URLClassLoader(urls, delegateParent)) {
      Class<?> reloaded=cl.loadClass(myClass.getName());
      System.out.printf("reloaded my class: Class@%x%n", reloaded.hashCode());
      System.out.println("Different classes: "+(myClass!=reloaded));
    }
  }
}
Johiah answered 20/11, 2013 at 11:20 Comment(6)
Okay, I think I understand your answer. I've tried it and this does indeed load a class dynamically. However, I'm still not sure how I can make sure that all classes are reloaded. Basically, I have a test class that uses classes (the ones it tests) and it needs to use the most recent version of all the classes, at run time. So if at run time I run the tests in the class it uses the newest versions at run time. This can dynamically reload the test class, but it doesn't do that for the classes used in that class.Depurative
you must ensure that the code sources (i.e. URLs) of both, test case classes and classes under test are provided to the new classloader and are not given to one of its parent loader chain. All referenced classes are resolved via the new class loader and hence reloaded if the classloader has the proper setup.Johiah
I've tried to adapt your example to mine, but when run, it loads the class but when I try to use the dynamicClass to test, it gives me an initialization error, I assume it isn't correctly loaded.Depurative
My example uses the try with resource construct which will close the ClassLoader at the end. After closing, no classes can be loaded thus resolving dependencies of not yet initialized classes will fail then. So all use of the dynamically loaded class must happen inside that block. If this is not feasible, don’t use this try construct.Johiah
In my example code posted in the edit above, I don't have that try block.Depurative
Maybe some dependencies are missing. Since you are excluding the original class loader of the class to reload, all classes normally loaded via this class loader must be made available to the new class loader. This is relevant if the original class loader has more than one URL and the reloaded class depends on one of the classes loaded through these URLs.Johiah

© 2022 - 2024 — McMap. All rights reserved.