Custom Java classloader not being used to load dependencies?

后端 未结 5 1198
逝去的感伤
逝去的感伤 2021-02-05 10:28

I\'ve been trying to set up a custom classloader that intercepts classes to print out which classes are being loaded into the application. The classloader looks like this

<
5条回答
  •  心在旅途
    2021-02-05 10:46

    Basics of Class Loading

    There are two main places to extend a class loader to change the way classes are loaded:

    • findClass(String name) - You override this method when you want to find a class with the usual parent first delegation.
    • loadClass(String name, boolean resolve) - Override this method when you want to change the way that class loading delegation is done.

    However, classes can only come from the final defineClass(...) methods provided by java.lang.ClassLoader. Since you would like to capture all of the classes that are loaded, we will need to override loadClass( String, boolean ) and use a call to defineClass(...) somewhere in it.

    NOTE: Inside of the defineClass(...) methods, there is a JNI binding to the native side of the JVM. Inside of that code, there is a check for classes in the java.* packages. It will only let those classes be loaded by the system class loader. This prevents you from messing with the internals of Java itself.

    An Example Child First ClassLoader

    This is a very simple implementation of the ClassLoader that you are trying to create. It assumes that all of the classes you need are available to the parent class loader, so it just uses the parent as a source for class bytes. This implementation uses Apache Commons IO for brevity, but it could easily be removed.

    import java.io.IOException;
    import java.io.InputStream;
    
    import static org.apache.commons.io.IOUtils.toByteArray;
    import static org.apache.commons.io.IOUtils.closeQuietly;
    ...
    public class MyClassLoader
      extends ClassLoader {
      MyClassLoaderListener listener;
    
      MyClassLoader(ClassLoader parent, MyClassLoaderListener listener) {
        super(parent);
        this.listener = listener;
      }
    
      @Override
      protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
        // respect the java.* packages.
        if( name.startsWith("java.")) {
          return super.loadClass(name, resolve);
        }
        else {
          // see if we have already loaded the class.
          Class c = findLoadedClass(name);
          if( c != null ) return c;
    
          // the class is not loaded yet.  Since the parent class loader has all of the
          // definitions that we need, we can use it as our source for classes.
          InputStream in = null;
          try {
            // get the input stream, throwing ClassNotFound if there is no resource.
            in = getParent().getResourceAsStream(name.replaceAll("\\.", "/")+".class");
            if( in == null ) throw new ClassNotFoundException("Could not find "+name);
    
            // read all of the bytes and define the class.
            byte[] cBytes = toByteArray(in);
            c = defineClass(name, cBytes, 0, cBytes.length);
            if( resolve ) resolveClass(c);
            if( listener != null ) listener.classLoaded(c);
            return c;
          } catch (IOException e) {
            throw new ClassNotFoundException("Could not load "+name, e);
          }
          finally {
            closeQuietly(in);
          }
        }
      }
    }
    

    And this is a simple listener interface for watching classes load.

    public interface MyClassLoaderListener {
      public void classLoaded( Class c );
    }
    

    You can then create a new instance of MyClassLoader, with the current class loader as the parent, and monitor classes as they are loaded.

    MyClassLoader classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
      public void classLoaded(Class c) {
        System.out.println(c.getName());
      }
    });
    classLoader.loadClass(...);
    

    This will work in the most general case and will allow you to get notified when classes are loaded. However, if any of those classes create their own child first class loaders, then they could bypass the notification code added here.

    More Advanced Class Loading

    To really trap classes being loaded, even when a child class loader overrides loadClass(String, boolean), you have to insert code between the classes you are loading and any of the calls that they may make to ClassLoader.defineClass(...). To do this, you have to start getting into byte code rewriting with a tool like ASM. I have a project called Chlorine on GitHub that uses this method to rewrite java.net.URL constructor calls. If you are curious about messing with classes at load time, I would check that project out.

提交回复
热议问题