Search code examples
javaclassloaderbytecode

Using a custom ClassLoader to override existing implementations of classes


First off, I know this question has been asked before, but none of the solutions i have seen worked for me!

In order to be able to override existing implementations of classes with new bytecode I want a class loader(set as default) where all calls for any class(excluding java packages like "java.") go through my loadClass/findClass overridden methods. From there i can check a static registry to see if there is a override for that class. I am willing to try to do this differently, but so far this looks like the fastest and easiest solution.

My issue is that for some reason even when I set my loader as the default in VM args(-Djava.system.class.loader=my.package.path.ProxyClassLoader) It throws the loading(of a class) to the parent which then, I assume sets the classloader to itself. This means all calls to find or load classes now go through the parent instead of my classloader.

Additionally, i would like to use JUnit to test this, but i couldnt get it to use a custom classloader(Using the JUnit API with the JUnit Engine in gradle) so i just went old school and used a main(where i can modifier vm arguments).

Things i have tried:

  1. Setting the classloader to the thread context loader.
  2. overriding loadclass to try to not throw the loading up to the parent, this just caused a ton more issues than it solved.
  3. Trying to do reflection to change the classloader of a class(Its specifically not allowed by Java + it would be incredibly slow and not worthwhile)

Here is my classloader as is:

public class ProxyClassLoader extends ClassLoader {
    public ProxyClassLoader(ClassLoader parent) {
        super(parent);
        Thread.currentThread().setContextClassLoader(this);

    }

    //Didn't look like i need to override anything so 
    //this is just a simplicity thing for me(i call it through other classes)
    public Class<?> defineClass(byte[] b, String name) {
        return super.defineClass(name, b, 0, b.length, defaultDomain);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (ClassManager.hasOverload(name)) return ClassManager.retrieve(name);
        return super.loadClass(name, resolve);

    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return this.loadClass(name, true);
    }
}

Thanks!


Solution

  • Don't skip super class loaders completely. Try something like this?

     @Override protected Class<?> loadClass(String name, boolean resolve) throws 
         ClassNotFoundException {
                if(name belongs to java classes) {
                     return super.loadClass(name, resolve);
                }
                byte[] b = ..// read bytes from your .class files. or use getResourceAsStream(name)
                Class<?> c = defineClass(b, name);
                if(resolve) resolveClass(c);
                return c;
            }