首页 > Java > 高新技术(七) 类加载器
2014
06-06

高新技术(七) 类加载器

① 类加载器简介

类加载器负责将.class文件加载到内存中,并为之生成相应的java.lang.Class对象。

当JVM启动时,会形成由三个类加载器(BootStrap、ExtClassLoader、AppClassLoader)组成的初始类加载器层次结构,如下图:

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

three ClassLoaders

其中,BootStrap由JVM加载,ExtClassLoader和AppClassLoader由BootStrap加载。

第一个例子:用程序去证明上图中对应的类加载器的父子关系(非继承中的父子关系)。

public class ClassLoaderTest {
	public static void main(String[] args){
		ClassLoader loader = ClassLoaderTest.class.getClassLoader();
		while(loader != null){
			System.out.println(loader.getClass().getName());
			loader = loader.getParent();
		}
		System.out.println(loader);
	}
}

问题:当程序中用到一个类时,使用哪个类加载器加载该类?

|— 当前线程的类加载器( Thread.getContextClassLoader() )去加载线程中的第一个类。

|— 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B(全盘负责)。

|— 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

注意:每个类加载器加载类时,先委托给其上级类加载器(父类委托)。

|— 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException。(不去找发起者的子加载器,因为ClassLoader没有getChild方法,即使有,多个子loader,去找哪个?)

② 自定义类加载器

问题:自定义一个类加载器MyClassLoader,加载范围(即“管辖范围”)为工程下的loaddir目录,且加载时会对该目录下的.class文件加密(所有二进制位取反)。并编写一个测试类ClassLoaderTest用自定义类加载LoadMe类。

public class MyClassLoader extends ClassLoader{
	//定义所要加载的类的位置。即前面说的类加载器的“管辖范围”
	private String path;

	public MyClassLoader(String path){
		this.path = path;
	}

	/**
	 * 使用传入的完整类名查找类,返回该类对应的java.lang.Class对象。
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		String classFileName = path + "\\" + name.substring(name.lastIndexOf(".")+1) + ".class";

		try(FileInputStream fis = new FileInputStream(classFileName);
			ByteArrayOutputStream bos = new ByteArrayOutputStream()
		){
			cipher(fis,bos);
			byte[] bytes = bos.toByteArray();
			return defineClass(bytes, 0, bytes.length);
		}catch(Exception e){
			e.printStackTrace();
		}

		return null;
	}

	public static void main(String[] args) throws Exception{
		//此main()方法的作用是将需要加载的类(LoadMe)加密,并移动到自己的管辖范围
		String srcPath = args[0];
		String destPath ="loaderdir\\" + srcPath.substring(srcPath.lastIndexOf("\\")+1);

		//System.out.println(destPath);
		FileInputStream fis = new FileInputStream(srcPath);
		FileOutputStream fos = new FileOutputStream(destPath);

		cipher(fis,fos);
	}

	/**
	 * cipher()加密方法用于将二进制文件中的二进制位取反
	 */
	public static void cipher(InputStream is,OutputStream os) throws IOException{
		int i = -1;
		while((i = is.read()) != -1){
			os.write(i ^ 0xff);
		}
	}

}
------------------------------------------------
/**
 * 测试类
 */
public class ClassLoaderTest {
	public static void main(String[] args) throws Exception{
		Class clazz = new MyClassLoader("loaderdir").findClass("org.flyne.classloader.LoadMe");
		Date lm = (Date)clazz.newInstance();
		System.out.println(lm);
	}
}
----------------------------------------------
public class LoadMe extends Date {
	@Override
	public String toString() {
		return "yes,u had loaded me!";
	}
}

执行次序:

A. 执行java MyClassLoader D:\myjava\studydemo\bin\org\flyne\classloader\LoadMe.class先对LoadMe.class文件加密,并将加密后的LoadMe.class移动到自定义类加载器的“管辖范围”。

B. 执行java ClassLoaderTest查看结果。此时调用MyClassLoader加载loaddir\LoadMe.class,利用该字节码创建一份对象并打印。(如果对此有疑问,可以将D:\myjava\studydemo\bin\org\flyne\classloader\LoadMe.class删除再执行)

在类加载器的最后,张孝祥老师引入了tomcat中的类加载器,写了一个Servlet,并打印出了该Servelet的类加载器以及该类加载器的所有上级加载器。最后引入了一个问题:

如果将该Servlet打包放到jre/lib/ext目录下,打印该Servlet类加载器时却报找不到HttpServlet的错误,这是由于类加载机制决定的(A引用B,则A的类加载器也要加载B),此时ExtClassLoader在jre/lib/ext目录下找不到HttpServlet所在的jar包。将tomcat下的servlet-api.jar复制到jre/lib/ext目录下,再次打印该Servlet的类加载器,为ExtClassLoader。


留下一个回复

你的email不会被公开。