How to set my ClassLoader as the JVM's ClassLo

2019-08-10 04:03发布

问题:

I am writing a game engine in which I need to analyze every single class that is mentioned in my program. As this is a game engine, it is to be attached to a client's project as a JAR file. From within that JAR file I need to be able to scan every class that is being used by the client.

So I thought that I should create a custom ClassLoader! By overriding ClassLoader I can take a look at every class that is being used as it is loaded.

I started playing around a bit with ClassLoaders. Here is what I did: (I stole this classloader from JavaWorld just to play around with it)

public class SimpleClassLoader extends ClassLoader {
private HashMap<String, Class> classes = new HashMap<String, Class>();

public SimpleClassLoader() {
}

/**
 * This sample function for reading class implementations reads
 * them from the local file system
 */
private byte getClassImplFromDataBase(String className)[] {
    System.out.println("        >>>>>> Fetching the implementation of "+className);
    byte result[];
    try {
        FileInputStream fi = new FileInputStream("store\\"+className+".impl");
        result = new byte[fi.available()];
        fi.read(result);
        return result;
    } catch (Exception e) {

        /*
         * If we caught an exception, either the class wasnt found or it
         * was unreadable by our process.
         */
        return null;
    }
}

/**
 * This is a simple version for external clients since they
 * will always want the class resolved before it is returned
 * to them.
 */
public Class loadClass(String className) throws ClassNotFoundException {
    return (loadClass(className, true));
}

/**
 * This is the required version of loadClass which is called
 * both from loadClass above and from the internal function
 * FindClassFromClass.
 */
public synchronized Class loadClass(String className, boolean resolveIt)
    throws ClassNotFoundException {
    Class result;
    byte  classData[];

    System.out.println(className);

    System.out.println("        >>>>>> Load class : "+className);

    /* Check our local cache of classes */
    result = (Class)classes.get(className);
    if (result != null) {
        System.out.println("        >>>>>> returning cached result.");
        return result;
    }

    /* Check with the primordial class loader */
    try {
        result = super.findSystemClass(className);
        System.out.println("        >>>>>> returning system class (in CLASSPATH).");
        return result;
    } catch (ClassNotFoundException e) {
        System.out.println("        >>>>>> Not a system class.");
    }

    /* Try to load it from our repository */
    classData = getClassImplFromDataBase(className);
    if (classData == null) {
        throw new ClassNotFoundException();
    }

    /* Define it (parse the class file) */
    result = defineClass(classData, 0, classData.length);
    if (result == null) {
        throw new ClassFormatError();
    }

    if (resolveIt) {
        resolveClass(result);
    }

    classes.put(className, result);
    System.out.println("        >>>>>> Returning newly loaded class.");
    return result;
}
}

Then I decided to test it:


public class Test
{
public static void main(String args[]) throws ClassNotFoundException
{
    SimpleClassLoader s = new SimpleClassLoader();
    Thread.currentThread().setContextClassLoader(s);

    Foo myfoo = new Foo();                          //local class
    ArrayList myList = new ArrayList();             //class from JAR file

    //both should have been loaded from my SimpleClassLoader
    System.out.println(s + "\n\tshould be equal to\n" + myfoo.getClass().getClassLoader());
    System.out.println("\tand also to: \n" + myList.getClass().getClassLoader());

    /*
       OUTPUT: 

       SimpleClassLoader@57fee6fc
            should be equal to
       sun.misc.Launcher$AppClassLoader@51f12c4e
            and also to: 
       null

       ***

       bizarre results: why are ArrayList and Foo not being loaded by my classloader?

     */


}
}
  1. Is creating a custom ClassLoader the correct approach to the problem described at the top?

  2. Why is the system class loader being invoked? How do I force the JVM to use MY classloader, for ALL threads? (not just the first thread, I want every new thread created to automatically use my classloader)

  3. It is my understanding that via the "delegation system", the parent ClassLoader gets the first try at loading the class; this may be the reason my ClassLoader isn't loading anything. If I am correct, how to I disable this feature? How do I get MY classloader to do the loading?

回答1:

The instrumentation api would be a natural fit for what I think you're trying to do

http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/Instrumentation.html

It lets you intercept classes as they are loaded by the JVM - normally to modify them, but you are given the byte array to play with so I can see no reason why you couldn't perform any required analysis without actually modifying.

This would let you analyse classes before they are loaded. I'm not entirely clear on what it is you are trying to achieve, but if you can analyse the classes after they are loaded then you could simply open and parse the contents of all jars on the classpath.