现在的位置: 首页 > 综合 > 正文

黑马程序员 – java高级特性 – 类加载器

2014年08月31日 ⁄ 综合 ⁄ 共 6630字 ⁄ 字号 评论关闭

-------android培训java培训、java基础学习技术博客、期待与您交流!
----------

6.1 类加载器

1. 类加载器
类加载器主要是用于加载类的,Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
根加载器 BootStrap JRE/lib/rt.jar
扩展类加载器 ExtClassLoader JRE/lib/ext/*.jar
系统类加载器 AppClassLoader CLASSPATH指定的所有jar或目录
自定义类加载器 MyClassLoader ItcastClassLoader 传智播客指定的特殊目录

2. 类加载器之前的关系
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

6.2 类加载器的委托机制

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。具体的过程如下:
1.Java虚拟机加载类的原则
(1)首先当前线程的类加载器去加载线程中的第一个类。
(2)如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
(3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
2.Java虚拟机委托加载机制
(1)每个类加载器加载类时,又先委托给其上级类加载器。
(2)类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。
(3)当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。而不会再去找发起者类加载器的子类加载器。
3.委托加载机制示例 
(1)现象
用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,再在eclipse中运行这个类,运行结果显示为ExtClassLoader。
(2)分析
因为,此时classpath目录有ClassLoaderTest.class,jre/lib/ext/itcast.jar包中也有ClassLoaderTest.class,由于ExtClassLoader加载到了该类,根据委托加载机制,父类加载器加载到类之后,不会在用子类加载器加载,所以运行的结果是ExtClassLoader。

6.3 自定义类加载器

由于类加载器委托机制的存在,要实现一个自定义的类加载器只需要继承ClassLoader类,然后重写其中的findClass方法,在findClass方法中通过调用自定义的loadClassData方法得到需要加载的类的字节码,然后通过defineClass创建类实例并且返回。由于自定义ClassLoader复写了父类的查找类的方法,因此可以加载自定义的类。不过具体的加载方式是由java虚拟机来完成的。

1.类加载器中的方法
(1)defineClass
protected final Class<?> defineClass(String name,byte[] b,int off,int len);
作用:将一个byte数组转换成Class类的实例。
(2)findClass
protected Class<?> findClass(String name);
作用:使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来
加载类。
(3) loadClass
Class<?> loadClass(String name);
作用:使用指定的二进制名称来加载类,Java 虚拟机调用它来分析类引用。因此,程序员在开发过程中
不需要使用该方法。
(4)loadClassData(自定义的方法)
private byte[] loadClassData(String name);
自定义加载器类中定义的内部成员方法,用于根据类名,加载自定义的字节码文件。

2.自定义类加载实现过程
(1)自定义类加载器类继承ClassLoader,并复写其中的findClass方法。
(2)在自定义加载器类中建立私有方法loadClassData,用来根据名称获取到指定的类的字节码。
(3)在自定义加载器类中通过调用loadClassData方法获取字节码,然后通过defineClass方法根据字节码获取Class的实例对象并且返回。

3.示例代码

 class NetworkClassLoader extends ClassLoader {
   String host;
   int port;

   public Class findClass(String name) {
       byte[] b = loadClassData(name);
       return defineClass(name, b, 0, b.length);
   }

   private byte[] loadClassData(String name) {
       // load the class data from the connection
        . . .
   }
}

6.4 自定义类加载器的应用示例

1.需求:
(1)对文件进行加密,存放到解密类加载器的路径下。
(2)自定义类加载器,对指定路径下的文件进行解密。
2.实现过程
(1)定义一个类ClassLoaderAttachment,需要加密的类。
(2)定义一个加密工具类CypherUtils,加密类中有加密Encrypt和解密算法Decrypt。
(3)定义一个加密文件类CypherFile,利用加密算法Encrypt,将ClassLoaderAttachment经加密后存放到指定的路径。
(4)定义类加载器MyClassLoader,利用解密算法Decrypt,在加载该类的过程中进行解密。
(5)定义一个类MyClassLoaderTest,对以上的方法进行测试。
3.实验步骤
(1)对"F:\JavaCode\javaBase\javabaseEnhance\bin\cn\itheima\enhance\chapter4\classloader\ClassLoaderAttachment.class"文件进行加密,加密结果存放到另外一个目录itcastlib中。
(2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader
(3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是 AppClassLoader类装载器装载失败。
(4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
4.示例代码

//(1)需要加密的类
public class ClassLoaderAttachment extends Date {
	public String toString() {
		return "hello,itcast";
	}	
}
//(2)加密工具类:用于对在对文件的读写过程中,进行加密和解密
public class CypherUtils {	
	private CypherUtils() {}
	
	// 加密
	public static void Encrypt(InputStream ips, OutputStream ops) throws Exception{
		int b = -1;
		while((b=ips.read())!=-1){
			ops.write(b^0xff);
		}
	}
		
	//解密
	public static void Decrypt(InputStream ips, OutputStream ops) throws Exception{
		int b = -1;
		while((b=ips.read())!=-1){
			ops.write(b^0xff);
		}
	}
}
//(3)对文件进行加密解密
public class CypherFile {	
	static String srcPath = "F:\\JavaCode\\javaBase\\javabaseEnhance\\bin\\cn\\itheima\\enhance\\chapter4\\classloader\\ClassLoaderAttachment.class";//F:\JavaCode\javaBase\javabaseEnhance\bin\cn\itheima\enhance\chapter4\classloader\ClassLoaderAttachment.class
	static String destDir = "itcastlib";//itcastlib
	private CypherFile() {}

	//对文件进行加密
	public static void encryptFile(){
		readWriteFile(srcPath, destDir);		
	}
	//对文件进行解密
	public static void decryptFile(){
		readWriteFile(srcPath, destDir);		
	}
	//将指定的文件从源位置拷贝到目的位置
	private static void readWriteFile(String srcPath, String destDir) {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream(srcPath);
			String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
			String destPath = destDir + "\\" + destFileName;
			fos = new FileOutputStream(destPath);
			CypherUtils.Encrypt(fis, fos);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}finally{
			try {
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}	
//(4)自定义类加载器测试
public class ClassLoaderTest {

	public static void main(String[] args) throws Exception {		
		//1. 使用系统类加载器进行加载
		//System.out.println(new ClassLoaderAttachment().toString());
		//2. 使用自定义类加载器进行加载
		Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itheima.enhance.chapter4.classloader.ClassLoaderAttachment");
		Date d1 = (Date) clazz.newInstance();
		System.out.println(d1);
	}
}

6.5 Servlet类加载器的原理

1.Servlet类加载器测试

(1)Servlet示例代码

public class MyServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		ClassLoader loader = this.getClass().getClassLoader();
		while(loader!=null){
			out.println(loader.getClass().getName()+"</br>");
			loader = loader.getParent();
		}
		out.close();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}
}

(2)在浏览器端通过get方式进行访问
在地址栏输入:
http://localhost:8080/itcastweb/servlet/MyServlet
(3)结果
org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

2.实验过程
(1)编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,
正常发布后,看到打印结果为WebAppClassloader。
(2)把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
(3)把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
(4)父级类加载器加载的类无法引用只能被子级类加载器加载的类。

总结:
类加载器主要是用于加载类的,Java虚拟机中可以安装多个类加载器。
(1)系统默认三个主要类加载器,每个类负责加载特定位置的类:
根加载器 BootStrap JRE/lib/rt.jar
扩展类加载器 ExtClassLoader JRE/lib/ext/*.jar
系统类加载器 AppClassLoader CLASSPATH指定的所有jar或目录
自定义类加载器 MyClassLoader ItcastClassLoader 传智播客指定的特殊目录
(2)类加载器的委托机制
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。具体的过程如下:
首先当前线程的类加载器去加载线程中的第一个类,如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。每个类加载器加载类时,又先委托给其上级类加载器。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。而不会再去找发起者类加载器的子类加载器。
(3)自定义类加载实现过程
第一、自定义类加载器类继承ClassLoader,并复写其中的findClass方法。
第二、在自定义加载器类中建立私有方法loadClassData,用来根据名称获取到指定的类的字节码。
第三、在自定义加载器类中通过调用loadClassData方法获取字节码,然后通过defineClass方法根据字节码获取Class的实例对象并且返回。
(4)Tomcat中的类加载器
Tomcat中的Servlet自定义了两个类加载器WebappClassLoader,StandardClassLoader类加载器的,具体的实现原理也是根据类加载器的委托机制,只需要实现自定义的根据类名加载字节码的方法。具体加载类的方式委托给父类类加载即可。

------- android培训java培训、java基础学习技术博客、期待与您交流!
----------

抱歉!评论已关闭.