我一直在试图建立一个截取类打印出哪些类被加载到应用程序的自定义类加载器。 类装入器看起来是这样的
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库。
类加载的基本知识
有扩展类加载器来改变类装载的方式两个主要的地方:
- 的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的构造函数调用。 如果您想了解在加载时使用类搞乱,我会检查该项目的。
如果你这样做
System.out.println( p.getClass().getClassLoader() );
你会看到p
的类加载器是不是你MyClassLoader bcl
。 它实际上是由加载bcl
的母公司,系统类加载器。
当PythonInterpreter
加载其依赖类,它会使用其实际的类加载器,系统类加载器,而不是你bcl
,所以没有达到你的拦截。
为了解决这个问题,你的类加载器不能委托给其母公司,它有自身实际加载的类。
对于你也可以继承URLClassLoader
(盗取系统类加载器的URL)。
如果你想,因为他们被加载到打印类,如何在冗长的切换:在JVM类选项?
java -verbose:class your.class.name.here
要回答你直接的问题:
这是为什么? 我是用来加载类C类加载器将用于加载所有被C所需要的其他类的印象,但是这显然这里没有发生。 是假设弄错了? 如果是,我该如何设置它使得所有C的传递性依赖被我的类加载器加载?
当搜寻类加载器,搜索从叶类加载器的根目录进行的,当Java的作品了一个新的类必须加载,它是从类加载器树的根执行回落到启动级分辨率的叶子。
为什么? 试想,如果你的自定义类要加载从Java标准库的东西。 正确的答案是,这应该由系统类加载器加载,使类可以最大限度地共享。 尤其是当你考虑到的类加载会随后可能加载了一大堆更多的类。
这也解决了潜在的,你可以在不同的类加载器加载多个系统类的实例结束的问题 - 每个具有相同的完全限定名。 编辑类会在正确的类加载器来解决。 但是这里有两个问题。
- 比方说,我们有两个字符串的情况下,
a
和b
。 a.getClass().isInstance(b)
和a.getClass() == b.getClass()
如果不为真a
和b
在不同类加载器被实例化。 这将导致可怕的问题。 - 单身:他们不会是单身 - 你可以有一个每一个classloader。
编辑完
另外一个观察:就像你已经设定了一个ClassLoader专门从加载类,翻译经常自己创造成其加载解释环境和脚本类加载器实例。 这样一来,如果脚本改变,类加载器可以被丢弃(与它的脚本),并在一个新的ClassLoader重新加载。 EJB和Servlet也用这一招。
如果你忽略了其他的loadClass()方法?
protected Class<?> loadClass(String name, boolean resolve)
您可以使用PySystemState
对象实例化PythonInterpreter之前指定一个自定义的类装载器。
PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);
http://wiki.python.org/jython/LearningJython