"类加载器"
*类加载器的作用, 把用到的类.class 加载到内存,以二进制码的形式存在
*Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
继承关系: BootStrap--> ExtClassLoader--> AppClassLoader
*类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
显然必须有第一个类加载器不是不是java类,这正是BootStrap。(C++编写)
*Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,
需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器的继承关系及作用范围
1)BootStrap JRE/lib/rt.jar
2)ExtClassLoader JRE/lib/ext//*.jar
3)AppClassLoader classPath指定的所有jar和目录
//MyClassLoader 自定义的目录
优先级 123 ,父类加载了子类就不用加载了
如将classPath下的类打包成jar包放到JRE/lib/ext目录下,该类的类加载器
就从 AppClassLoader变成了ExtClassLoader
不能随意将自己的class文件加入进rt.jar文件中的。
类加载器的"委托机制"
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下
的jar包后,运行结果为ExtClassLoader的原因。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,
这就是类加载器的"委托模式"。类装载器一级级委托到BootStrap类加载器,
当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。
当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
面试题:能不能自己写个类叫java.lang.System ?
答:为了不让我们写System类,类加载采用委托机制,
这样可以保证父类优先,也就是总是使用父类能找到的类,这样总是使用java系统提供的System。
编写"自定义类加载器"
用于加载指定目录下的类,并对数据进行加密
自定义类加载器必须继承 ClassLoader,使用"模板设计模式"
loadClass() 直接继承父类模板中固定内容
findClass() 覆盖父类方法,自定义内容
另一个方法: defineClass()将字节数组转成.class,用于简单的加密
步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。
程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,
然后再使用Class.forName。
类加载器的高级问题分析:
写A和B两个类,A调用了B,把A输出到jre/lib/ext/my.jar包中运行,就可以看到找不到类B的效果。
因为在A调用B时,B需要通过A的类加载器加载
原本A是由AppClassLoader 加载的,A引用了B,B也由AppClassLoader加载
现在A被ExtClassLoader加载,但是ExtClassLoader 却找不到B,所以会导致错误发生
将B也输出到ext目录下即可解决,如果被ExtClassLoader的父类加载也能够解决
"父级类加载器加载的类无法引用只能被子级类加载器加载的类"
代理机制使被引用的类只能存在于当前类的加载器及父类加载器的加载范围内
类加载器委托机制的原理图
实验:自定义一个类加载器了解类加载器的委托机制
编写三个类:
ClassLoaderAttachment 是要被加载的类
MyClassLoader 是自定义类加载器,继承了ClassLoader 类,复写其findClass方法,是该方法具备自定义的解密功能,其加载路径指向到加密文件所在路径
定义一个对class文件进行编译加密的功能,可以对ClassLoaderAttachment 类的字节码进行加密,使AppClassLoader无法加载加密后的字节码,必须使用自定义的类加载器来加载
ClassLoaderTest 在该类中使用类加载器加载 ClassLoaderAttachment
实验步骤:
运行ClassTest, 使用默认的类加载器加载ClassLoaderAttachment,正常运行
将ClassLoaderAttachment类生成的字节码进行加密-->运行MyClassLoader;输出到指定的目录下
将bin目录下的ClassLoaderAttachment类生成的class删除,用加密过的字节码来替换,运行,发现默认的类加载器无法加载;
删除加密的字节码,重启eclipse,发现加载成功,(使用了自定义类加载器到指定目录下加载)
代码实现:
要加载的类
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString(){
return "Hello Java !";
}
}
自定义类加载器
import java.io.*;
public class MyClassLoader extends ClassLoader{
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String srcPath = args[0];
String destDir = "agencylib";
//获取文件名
String destFileName = srcPath.substring(srcPath.lastIndexOf("\\"));
String destPath = destDir + "\\" +destFileName; //目的路径+文件名
FileInputStream fis = new FileInputStream(new File(srcPath));
FileOutputStream fos = new FileOutputStream(new File(destPath));
//加密操作
cypher(fis,fos);
fis.close();
fos.close();
}
//加密功能
private static void cypher(InputStream in, OutputStream out) throws IOException{
int b = -1;
while((b=in.read()) != -1){
out.write(b ^ 0xff);
}
System.out.println("加密...");
}
private String classDir; //要加载的文件目录
@Override//自定义findClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
System.out.println(classFileName);
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos); //解密
System.out.println("解密...");
fis.close();
byte[] by = bos.toByteArray();
return defineClass(by, 0, by.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;//函数覆盖,调用子类
}
public MyClassLoader(){
} //构造函数
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
}
测试
import java.util.Date;
public class ClassLoaderTest {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
System.out.println( //AppClassLoader
ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println( //null,因为BootStrap不是Java类
System.class.getClassLoader());
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader !=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();//AppClassLoader父类为ExtClassLoader
}
System.out.println(loader);
System.out.println(new ClassLoaderAttachment().toString());
System.out.println("aaaaaaaaaaaaaaaaaaa");
Class clazz = new MyClassLoader("agencylib").loadClass("enhance_2.ClassLoaderAttachment");
Date d = (Date) clazz.newInstance();
System.out.println(d);
}
}
运行结果:
1,默认加载器AppClassLoader加载
,
2,使用加密过的class文件替换后,字节码加载异常,默认加载器无法识别
3,删除bin目录下加密过的该class, 使用自定义类加载器加载agencylib目录下的文件