自定义Java类加载器不使用加载的依赖?(Custom Java classloader not b

2019-08-02 13:27发布

我一直在试图建立一个截取类打印出哪些类被加​​载到应用程序的自定义类加载器。 类装入器看起来是这样的

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

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

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 “d Python代码是能够做到的东西,读我的文件。 出于某种原因,不过,这些类不被通过加载的类加载器加载PythonInterpreter

这是为什么? 我是用来加载类类加载器的印象C将被用来加载所有所需的其他类C ,但显然这里没有发生。 是假设弄错了? 如果是,我该如何设置它使得所有的传递依赖C由我的类加载器加载?

编辑:

一些实验用URLClassLoader ,它建议。 我修改了代表团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的,而不是普通的类加载器,通过敛网址:

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

但它似乎并没有被正确的事情。 尤其getResourceAsStream()是在我扔空回所有我请求类,甚至非系统类这样的Jython库。

Answer 1:

类加载的基本知识

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

  • 的findClass(字符串名称) - 当你想找到与通常的父母第一次组团一类覆盖此方法。
  • 的loadClass(字符串名称,布尔解析) - 当你想更改类加载代表团做的方式重写此方法。

但是,类只能来自于由java.lang.ClassLoader中提供了最终的defineClass(...)方法。 既然你想捕捉所有已加载的类,我们需要重写的loadClass(字符串,布尔值)和使用的defineClass(...)的调用它的地方。

:里面的defineClass(...)方法中,有一种JNI绑定到JVM的本机端。 里面的代码,有在Java类的检查。*包。 它只会让那些类由系统类加载器加载。 这可以防止您与Java本身的内部搞乱。

为例儿童第一类加载器

这是一个非常简单的实现,你要创建的类加载器的。 它假定你需要的所有类都可以在父类加载器,所以它只是使用父为类字节的源。 此实现使用Apache下议院IO为简洁,但它可以很容易地被移除。

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

这是看的类加载一个简单的监听器接口。

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

然后,您可以创建MyClassLoader的新实例,与当前的类加载器作为父母,当他们被加载监控类。

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

这将在最一般的情况下工作,将允许类被加载时,你得到通知。 但是,如果其中任何类创建他们自己的孩子第一个类加载器,那么他们可以绕过这里添加的通知代码。

更高级的类加载

要真正陷阱类加载,即使在子类加载器覆盖的loadClass(字符串,布尔),你必须要加载任何的呼叫,他们可以做出ClassLoader.defineClass类之间插入代码(...) 。 要做到这一点,你必须开始进入字节码中包含的工具重写ASM 。 我有一个项目叫氯 GitHub上使用此方法重写的java.net.URL的构造函数调用。 如果您想了解在加载时使用类搞乱,我会检查该项目的。



Answer 2:

如果你这样做

    System.out.println( p.getClass().getClassLoader() );

你会看到p的类加载器是不是你MyClassLoader bcl 。 它实际上是由加载bcl的母公司,系统类加载器。

PythonInterpreter加载其依赖类,它会使用其实际的类加载器,系统类加载器,而不是你bcl ,所以没有达到你的拦截。

为了解决这个问题,你的类加载器不能委托给其母公司,它有自身实际加载的类。

对于你也可以继承URLClassLoader (盗取系统类加载器的URL)。



Answer 3:

如果你想,因为他们被加载到打印类,如何在冗长的切换:在JVM类选项?

java -verbose:class your.class.name.here

要回答你直接的问题:

这是为什么? 我是用来加载类C类加载器将用于加载所有被C所需要的其他类的印象,但是这显然这里没有发生。 是假设弄错了? 如果是,我该如何设置它使得所有C的传递性依赖被我的类加载器加载?

搜寻类加载器,搜索从叶类加载器的根目录进行的,当Java的作品了一个新的类必须加载,它是从类加载器树的执行回落到启动级分辨率的叶子。

为什么? 试想,如果你的自定义类要加载从Java标准库的东西。 正确的答案是,这应该由系统类加载器加载,使类可以最大限度地共享。 尤其是当你考虑到的类加载会随后可能加载了一大堆更多的类。

这也解决了潜在的,你可以在不同的类加载器加载多个系统类的实例结束的问题 - 每个具有相同的完全限定名。 编辑类会在正确的类加载器来解决。 但是这里有两个问题。

  1. 比方说,我们有两个字符串的情况下, aba.getClass().isInstance(b)a.getClass() == b.getClass()如果不为真ab在不同类加载器被实例化。 这将导致可怕的问题。
  2. 单身:他们不会是单身 - 你可以有一个每一个classloader。

编辑完

另外一个观察:就像你已经设定了一个ClassLoader专门从加载类,翻译经常自己创造成其加载解释环境和脚本类加载器实例。 这样一来,如果脚本改变,类加载器可以被丢弃(与它的脚本),并在一个新的ClassLoader重新加载。 EJB和Servlet也用这一招。



Answer 4:

如果你忽略了其他的loadClass()方法?

protected Class<?> loadClass(String name, boolean resolve)


Answer 5:

您可以使用PySystemState对象实例化PythonInterpreter之前指定一个自定义的类装载器。

PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);

http://wiki.python.org/jython/LearningJython



文章来源: Custom Java classloader not being used to load dependencies?