自定义类加载器加载类的问题

本贴最后更新于 2698 天前,其中的信息可能已经事过景迁

一、创建自定义类加载器

  自定义了一个类加载器 MyClassLoader,其中实现是通过读取 class 文件为字节数组,然后通过 ClassLoader 中的 defineClass(String name,byte[],int off,int len)把字节数组中的内容转换成 Java 类,返回的是 Class 类的实例,代码如下:

package priv.leopold.tool;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyClassLoader extends ClassLoader {
	public Class<?> load(File classFile) throws ClassNotFoundException {
		byte[] classData = getClassData(classFile);
		if (classData == null) {
			throw new ClassNotFoundException();
		} else {
			return defineClass(null, classData, 0, classData.length);
		}
	}
	private byte[] getClassData(File classFile) {
		try {
			InputStream ins = new FileInputStream(classFile);
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int bufferSize = 4096;
			byte[] buffer = new byte[bufferSize];
			int bytesNumRead = 0;
			while ((bytesNumRead = ins.read(buffer)) != -1) {
				baos.write(buffer, 0, bytesNumRead);
			}
			ins.close();
			return baos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}

二、测试自定义类加载器

先创建两个 MyClassLoader 实例:

	MyClassLoader  loader1 = new MyClassLoader();
	MyClassLoader  loader2 = new MyClassLoader(); 

然后通过这两个类加载器的实例加载同一个类,并通过 Class 对象创建实例:

	Class<?> class1 = loader1.load(classFile);		//第一个类加载器加载类
	Object obj1 = class1.newInstance();
	Object obj2 = class1.newInstance();
			
	Class<?> class2 = loader2.load(classFile);		//第二个类加载器加载类
	Object obj3 = class2.newInstance();

接下来执行类中的方法:

	Method method = class1.getMethod("setInstance", Object.class);
	method.invoke(obj1, obj2);
	method.invoke(obj1, obj3);

其中测试的类为 Sample,代码如下:

	public class Sample {
		private Sample instance;
		public Sample getInstance() {
			return instance;
		}
		public void setInstance(Object instance) {
			this.instance = (Sample) instance;
		}
	}

执行上面的代码后会抛出异常:

	Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at test.Test.main(Test.java:28)
	Caused by: java.lang.ClassCastException: bean.Sample cannot be cast to bean.Sample
	at bean.Sample.setInstance(Sample.java:12)
	... 5 more

    引起上面异常的是这句代码:this.instance = (Sample) instance;
虽然是用同一个类文件(这里用的是 Sample.class 文件)加载出来的 Class 对象,但通过上面的这句代码抛出的异常可以发现,它们创建的实例属于不同类型(即使这里都叫做 bean.Sample)。

三、分析原因

    对于某个特定的类加载器来说,一个 Java 类只能被载入一次,也就是说在 Java 虚拟机中,类的完整标识是(classLoader,package,className)。
另外一个类可以被不同的类加载器加载,但是,同一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同 Class 实例。
    如果用的类加载器没有重写 ClassLoader 中的 getSystemClassLoader 方法,那么用的都是 AppClassLoader 的同一个实例进行类的加载的,所以加载同一个类在内存中只有一个类,即所有通过正常双亲委派模式的类加载器加载的 classpath 下的和 ext 下的所有类在方法区都是同一个类,堆中的 Class 实例也是同一个。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3186 引用 • 8212 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...