黑马程序员 Java高新技术 类加载器总结

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


什么是类加载器(classloader)
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),
然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。


三个主要的类加载器
JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现的,
它负责加载核心JavaClass(即所有java.*开头的类).
另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由BootstrapClassLoader加载;
Extension ClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类),
ApplicationClassLoader负责加载应用程序自身的类。







一般加载流程
当运行一个程序时,JVM启动,运行bootstrapclassloader来加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),
然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。



类加载器的委托机制
横向委托:
当JVM要加载一个类时,首先派当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,JVM将使用加载类A的类加载器来加载类B。
纵向委托:
每个类加载器加载类时,会先委托给其上级类加载器。当所有父类加载器都没有加载到类,回到发起者类加载器,
还加载不了,则抛ClassNotFoundException,不会再去找发起者类加载器的子类。


其他知识点
JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类。 
可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
数组类的Class对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类加载器
由Class.getClassLoader()返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。 


模版方法设计模式
模版方法模式需要抽象类和具体子类协调完成,抽象类负责给出一个算法的轮廓和骨架,
具体子类负责给出这个算法的各个逻辑步骤既具体子类负责填充这个轮廓和骨架,不同的子类有不同的填充方法;
而将这些基本方法总汇起来的方法叫做模版方法,这个模板方法是在抽象类中以具体方法定义的。


loadClass(String name);
使用指定的二进制名称来加载类。此方法的默认实现将按以下顺序搜索类: 
1.调用 findLoadedClass(String) 来检查是否已经加载类。
2.在父类加载器上调用 loadClass 方法。如果父类加载器为 null,则使用虚拟机的内置类加载器。 
3.调用 findClass(String) 方法查找类。
findClass(String name);
使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。
在通过父类加载器检查所请求的类后,此方法将被loadClass方法调用。
defineClass(String name,byte[] b,int off,int len);
将一个 byte 数组转换为 Class 类的实例。必须分析Class,然后才能使用它。


编写自定义类加载器
自定义的类加载器必须继承ClassLoader,不要重写loadClass方法,否则会破坏委托机制,
父类加载器都没成功,则调用findClass方法,需要重写findClass方法,指定自定义的加载路径,
例如Class文件被加密,需要在findClass中进行解密转换成byte[],将byte[]传递给defindClass方法。


自定义类加载器编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,
因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,
还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。


//编写自己的类加载器
public class MyClassLoader extends ClassLoader{


	public static void main(String[] args) throws Exception {


		//配置读取和输出文件的路径
		String srcPath = args[0];
		String destDir = args[1];
		FileInputStream fis = new FileInputStream(srcPath);
		String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
		String destPath = destDir + "\\" + destFileName;
		FileOutputStream fos = new FileOutputStream(destPath);
		cypher(fis,fos);
		fis.close();
		fos.close();
	}
	
	//对文件内容进行简单加密
	private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
		int b = -1;
		while((b=ips.read())!=-1){
			ops.write(b ^ 0xff);
		}
	}
	private String classDir;


	//重写findClass方法,将被加密的Class文件进行解密转换成byte[],通过defindClass方法将byte[]转成Class对象。
	protected Class findClass(String name) throws ClassNotFoundException {
		
		String classFileName = classDir + "\\"  + name.substring(name.lastIndexOf('.')+1) + ".class";
		try {
			FileInputStream fis = new FileInputStream(classFileName);
			ByteArrayOutputStream  bos = new ByteArrayOutputStream();
			cypher(fis,bos);
			fis.close();
			System.out.println("aaa");
			byte[] bytes = bos.toByteArray();
			return defineClass(bytes, 0, bytes.length);
		} catch (Exception e) {
			
			e.printStackTrace();
		}
		return null;
	}	
	public MyClassLoader(){
		
	}
	public MyClassLoader(String classDir){
		this.classDir = classDir;
	}
}


//编写一个测试程序调用类加载器
public class ClassLoaderTest {


	public static void main(String[] args) throws Exception {
		
		Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
		Date d1 =  (Date)clazz.newInstance();
		System.out.println(d1);
	}


}


//编写被加载的Class类
public class ClassLoaderAttachment extends Date {
	public String toString(){
		return "hello,itcast";
	} 
}


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





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




你可能感兴趣的:(黑马程序员 Java高新技术 类加载器总结)