java类加载器加载机制

类加载器(class loader)用来加载 Java 字节码到 java虚拟机中,即类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。

在java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器:

BookStrap,  ExtClassLoader,  AppClassLoader

类加载器也是java类,所以java类加载器本身也要被其它类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap.

 

 

java虚拟机中所有的类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定父级类加载器

 

类加载器之间的父子关系和管辖范围

java类加载器加载机制

 

可以通过下面的测试程序验证类加载器的委托机制:

public class ClassLoderTest1 {

	public static void main(String[] args) {
		
		ClassLoader loder = ClassLoderTest1.class.getClassLoader();
		while (loder != null) {
			System.out.println(loder.getClass().getName());
			loder = loder.getParent();
		}
	}
}
/*
 * sun.misc.Launcher$AppClassLoader
 * sun.misc.Launcher$ExtClassLoader
 */ 
从结果可以看出,首先AppClassLoader会加载ClassLoderTest1.class,由于委派机制会先使用其父加载器ExtClassLoader去加载,这就是为什么打印出上面的结果。

 

 

下面介绍下ClassLoader

java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等

ClassLoader 中与加载类相关的方法

 

方法

说明

getParent()

返回该类加载器的父类加载器。

loadClass(String name)

加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。

 

findClass(String name)

查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。

 

findLoadedClass(String name)

查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。

 

defineClass(String name, byte[] b, int off, int len)

把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。

 

resolveClass(Class<?> c)

链接指定的 Java 类。

 

当我们自定义类加载器时会用到上面的方法。

下面我们来自定义加载器实现加载特定目录下面的.class文件。

这里我们来加载lib目录下面的字节码文件。

首先我们来定义一个目标文件

public class ClassLoaderDemo extends Date {

	public String toString() {
		return "这是被类加载器加载的哦";
	}	
}

 

让其实现Date类是便于后面的测试

 

然后我们开始编写自定义的类加载器了

public class CustomClassLoader extends ClassLoader {

	/**
	 * 复写findClass()方法
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		byte[] data = getClassData(name);
		if (data == null) {
			throw new ClassNotFoundException();
		}
		//通过byte[]数组得到Class
		return defineClass(null, data, 0, data.length);
	}

	/**
	 * 从lib目录中得到字节码文件,并转成byte[]数组
	 * @param name
	 * @return
	 */
	private byte[] getClassData(String name) {
		
		String classSrc = "lib" + File.separator + getClassName(name) + ".class";
		try {
			FileInputStream in = new FileInputStream(classSrc);
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024*4]; 
			int len = 0;
			while ((len = in.read(buffer)) != -1) {
				out.write(buffer, 0, len);
			}
			return out.toByteArray();
		} catch (FileNotFoundException e) {

			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 
	 * @param classNamePath 形如:cn/zcl/lib/ClassLoaderDemo.class
	 * @return ClassLoaderDemo.class
	 */
	private String getClassName(String classNamePath) {
		
		return classNamePath.substring(classNamePath.lastIndexOf(".") + 1);
	}
	
}

 

其实实现类加载器只需继承ClassLoader,并覆写里面的findClass()方法。

 

最后测试一个测试类来测试我们写的类加载器是否成功

public class ClassLoaderTest2 {

	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {

		Date date = (Date)new CustomClassLoader().loadClass("cn.zcl.classLoaderDemo").newInstance();
		System.out.println(date);
	}
}

 

打印结果:

这是被类加载器加载的哦 
这就表明我们写得自定义加载器已经成功。

 

下面总结下:

编写自定义的类加载器只需继承ClassLoader,并覆写里面的findClass()方法,将字节码文件转成Class实例,当然这里面使用到了defineClass()将字节数组转成Class实例。

扩展:

当在做开发中,出现类转换异常时,除了一般的转换外,还得注意是否一个类被两个类加载器加载,若一个类被两个类加载器加载,这两个字节码在内存中是不相等的,这点非常关键。 

你可能感兴趣的:(java,sun)