ClassLoader对类的级联加载

本文探讨了自定义ClassLoader中类加载机制的问题,特别是不同ClassLoader实例之间的类兼容性问题。通过一个具体的例子,解释了为何在某些情况下类型转换可以成功,而在另一些情况下则失败。
转载请注明来自:http://chillwarmoon.iteye.com
在一个ClassLoader实例中,如果加载某个Class,那么被加载的Class是属于该ClassLoader所定义的namespace之内的。表现为不同的classloader实例虽然加载的Class完全相同,但是不能够相互类型转化,而且不能够通过类型转换成其他classloader加载的类。
但是在自定义的CustomClassLoader中,(1)Test test=(Test)clazz.newInstance()的类型转换是成功的,而且转换的是SystemClassLoader所加载的Test.class;(2)TestSub test2=(TestSub)clazz.newInstance()的类型转换是失败的。
这是为什么呢?

public class CustomClassLoader extends ClassLoader{
public Class load(String arg){
return getClass(arg);
}

public Class getClass(String arg){
String path=System.getProperty("user.dir");
String fullPath=path+"/bin/"+arg.replace(".", File.separator)+".class";
byte[] classCode=null;
File f=new File(fullPath);
if(f.exists()){
try {
FileInputStream fis=new FileInputStream(f);
classCode=new byte[fis.available()];
fis.read(classCode);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(arg,classCode, 0, classCode.length);
}
try {
return findSystemClass(arg);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}

public static void main(String args[]){
Class clazz=new CustomClassLoader().load("com.hzb.classloader.TestSub");
try {
Test test=(Test)clazz.newInstance();
TestSub test2=(TestSub)clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

}

}

答案就在于CustomClassLoader并未覆盖父类的loadClass方法,在该例子中,虽然是通过CustomClassLoader.load方法,来调用defineClass方法加载class,但是该defineClass方法仍然需要调用ClassLoader.loadClass方法来级联加载与之相关的类,包含其父类,接口。因此该例子中仅仅是对字节码文件TestSub.class进行了load,而Test仍然是由SystemClassLoader所加载,因此会出现Test test=(Test)clazz.newInstance()成功执行,而TestSub test2=(TestSub)clazz.newInstance()执行失败的情况。

如果将CustomClassLoader.load方法名称改变为loadClass,则在调用defineClass时会加载Test.class到该classLoader实例,此时再次调用Test test=(Test)clazz.newInstance()会产生类型转换失败的错误。

PS:在调用clazz.newInstance方法时,会使用classLoader的loadClass方法,来加载类的关联类。因此不同的classLoader都有着自己的namespace。
### Java 打破双亲委派机制的实现方式 Java 的加载器采用双亲委派机制,即在加载某个时,会优先让父加载器尝试加载。只有当父加载器无法找到目标时,当前加载器才会在其指定路径中查找并加载[^2]。 为了打破双亲委派机制,可以通过自定义加载器的方式实现。以下是具体实现方法: #### 1. 继承 `java.lang.ClassLoader` 自定义加载器需要继承 `java.lang.ClassLoader` ,并重写其核心方法之一——`findClass` 方法或 `loadClass` 方法。通常情况下,推荐重写 `findClass` 方法,因为它是专门用于自定义加载逻辑的部分[^1]。 #### 2. 重写 `findClass` 方法 `findClass` 是一个空方法,在默认情况下不做任何操作。通过重写此方法,可以手动读取字节码文件并将它们转换为 `byte[]` 数组形式,最后调用 `defineClass` 方法完成的定义过程。 以下是一个简单的例子: ```java public class MyCustomClassLoader extends ClassLoader { private String classPath; public MyCustomClassLoader(String classPath) { this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); // 定义 } } catch (Exception e) { throw new ClassNotFoundException(e.getMessage()); } } private byte[] loadClassData(String className) throws Exception { String path = classPath + "/" + className.replace('.', '/') + ".class"; try (InputStream inputStream = new FileInputStream(path); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { int data; while ((data = inputStream.read()) != -1) { byteArrayOutputStream.write(data); } return byteArrayOutputStream.toByteArray(); // 返回字节数组 } } } ``` 在此代码中,`findClass` 被用来加载特定路径下的 `.class` 文件,并将其转化为字节数组后传递给 `defineClass` 方法[^3]。 #### 3. 重写 `loadClass` 方法(可选) 如果希望更灵活地控制加载行为,则可以选择重写 `loadClass` 方法。例如,可以在其中加入条件判断:对于某些特定包名下的,跳过双亲委派机制;而对于其他则继续遵循原有的规则。 下面展示了一个示例,它针对 `com.example.custompackage` 包中的打破了双亲委派机制: ```java @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First check if the class has already been loaded. Class<?> clazz = findLoadedClass(name); if (clazz == null && name.startsWith("com.example.custompackage")) { // 特定条件下不使用双亲委派 clazz = findClass(name); } else { try { clazz = getParent().loadClass(name); // 使用父加载加载 } catch (ClassNotFoundException ignored) {} if (clazz == null) { clazz = findClass(name); // 如果父加载器未成功加载,则自己尝试加载 } } if (resolve) { resolveClass(clazz); } return clazz; } } ``` 在这个版本中,我们显式指定了哪些型的请求应该绕开标准流程直接进入本地处理阶段[^4]。 --- ### 总结 以上两种途径都可以有效达成破坏传统意义上的父子级联关系的目的。需要注意的是,虽然这种方法提供了极大的灵活性,但也可能带来潜在风险,比如违反沙盒安全性原则或者引发不可预见的行为等问题。因此实际开发过程中应谨慎评估是否真的有必要这样做。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值