本文介绍了自定义Java类加载器未用于加载依赖关系?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试设置一个自定义类加载器,拦截类来打印哪些类正在加载到应用程序中。类加载器看起来像这样

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

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Loading: " + name);
        return super.loadClass(name);
    }
}

它只是吐出所有类的名称负载。但是,当我尝试运行一些代码,

It just spits out the name of all the classes it loads. However, when i try to run some code,

import org.python.util.PythonInterpreter;
public class Scripts {
    public String main(){

        PythonInterpreter p = new PythonInterpreter();
        p.exec("print 'Python ' + open('.gitignore').read()");

        return "Success! Nothing broke";
    }
}

通过

MyClassLoader bcl = new MyClassLoader();
Class c = bcl.loadClass("Scripts");

Method m = c.getMethod("main");
String result = (String) m.invoke(c.getConstructor().newInstance());

打印出来

Loading: Scripts
Loading: java.lang.Object
Loading: java.lang.String
Loading: org.python.util.PythonInterpreter
Python build/
.idea/*
*.iml
RESULT: Success! Nothing broke

这似乎很奇怪。 org.python.util.PythonInterpreter 不是一个简单的类,它依赖于 org.python.util 包。这些类显然正在加载,因为 exec 'python代码能够做的东西,读取我的文件。因为某些原因,这些类没有被加载 PythonInterpreter 的类加载器加载。

Which seems rather odd. org.python.util.PythonInterpreter is not a simple class, and it depends on a whole bunch of other classes in the org.python.util package. Those classes are clearly being loaded, for the exec'd python code is able to do stuff and read my file. For some reason, though, those classes are not being loaded by the classloader which loaded PythonInterpreter.

?我的印象是,用于加载类 C 的类加载器将用于加载 C ,但这显然不是在这里发生。这个假设是错误的吗?如果是,我如何设置它,使得 C 的所有传递依赖都由我的类加载器加载?

Why is that? I was under the impression that the classloader used to load a class C would be used to load all the other classes needed by C, but that's clearly not happening here. Is that assumption mistaken? If it is, how do i set it up such that all the transitive dependencies of C are loaded by my classloader?

EDIT:

使用 URLClassLoader 的一些实验建议。我修改了 loadClass()中的委托:

Some experiments with using URLClassLoader, which was suggested. I modified the delegation in loadClass():

try{
    byte[] output = IOUtils.toByteArray(this.getResourceAsStream(name));
    return instrument(defineClass(name, output, 0, output.length));
}catch(Exception e){
    return instrument(super.loadClass(name));
}

以及MyClassLoader子类URLClassLoader而不是纯ClassLoader,

as well as made MyClassLoader subclass URLClassLoader rather than plain ClassLoader, grabbing URLs via:

super(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

但它似乎不是正确的事情。特别是, getResourceAsStream()正在为我请求的所有类,甚至非系统类像这样的Jython lib抛出null。

But it doesn't seem to be the right thing. In particular, getResourceAsStream() is throwing nulls back at me for all the classes I'm requesting, even non-system classes like that Jython lib.

推荐答案

类加载的基础知识



有两个主要的地方来扩展类加载器来改变方法类加载:

Basics of Class Loading

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


  • findClass(String name) - 当您想要
    时覆盖此方法。

  • loadClass(String name,boolean resolve) - 当您要以类加载委托的方式更改
    时,覆盖此方法。

但是,类只能来自java.lang.ClassLoader提供的最终defineClass(...)方法。因为你想捕获所有加载的类,我们需要重写loadClass(String,boolean),并使用defineClass(...)。

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.

注意:在defineClass(...)方法内部,有一个JNI绑定到JVM的本机端。在代码内部,有一个检查java。*包中的类。它将只允许这些类由系统类加载器加载。

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.

这是一个非常简单的实现您尝试创建的ClassLoader。它假设你需要的所有类都可用于父类加载器,因此它只使用父类作为类字节的源。这个实现使用Apache Commons IO来简洁,但它很容易被删除。

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 );
}

然后,您可以创建一个MyClassLoader的新实例,父类和监视器类。

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.

要真正捕获正在加载的类,即使子类加载器覆盖loadClass(String,boolean),也必须在加载的类和它们可能调用的任何调用之间插入代码到ClassLoader.defineClass(...)。为此,您必须使用等工具开始字节码重写。我在GitHub上有一个名为的项目,使用此方法重写java.net.URL构造函数调用。如果你很好奇在加载时解决类,我会检查该项目。

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.

这篇关于自定义Java类加载器未用于加载依赖关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-12 07:58