一、 类加载器
1、 何为类加载器?
类加载器---就是在用到该类时,将该类的class文件从硬盘加载到内存中,变成字节码存储在内存中。
JVM中默认三个主要的类加载器:BootStrap,ExtClassLoader,AppClassLoader 。在类中,可以通过方法getClassLoader()获得该类的类加载器。
每个类加载器都负责加载存储在特定位置中的类,三个类加载器之间的父子关系及负责的位置如图:
其中,BootStrap是第一个类加载器,它嵌入到java虚拟机内核中,JVM内核一启动它就已经在里面。是由C++语言编写的二进制代码。其他的类加载器都是java类,也需要被加载。
2、 类加载器的委托机制。
(1)当java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。在Thread类中,有set和get类加载器的方法,可以设置该线程的类加载器。
如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
(2)每个类加载器加载类时,又先委托给其上级类加载器:比如一个类需要AppClassLoader这个类加载器来加载,它不会先找classpath下有没有该类,而是先委托给上级目录ExtClassLoader,而ExtClassLoader又会紧接着委托给它的上级BootStrap类来加载,那么BootStrap开始找jre/lib/rt.jar包中有没有该类,有就加载,那么子类加载器就不用管了。如果没有,再向下级类加载器找,一直找到发出问题的类加载器,比如这里就是AppClassLoader,还没找到就抛出异常:ClassNotFoundException。不会再去找发起者的儿子类。
3、自定义类加载器。
(1)自定义类加载器编写原理:继承抽象类ClassLoader,使自定义的类加载器挂载到子父类结构中,覆盖findClass方法,使用defineClass,完成内存加载。
为什么覆盖findClass而不是loadClass?因为loadClass会先去找父类,父类如果不加载再返回到自己,然后调用自身的findClass。覆盖findClass保留loadClass正好是保留了类加载器的委托机制,又能达到自身加载的目的。
(2)使用自定义类加载器:调用自定义类加载器的loadClass方法,将要加载的类名字作为参数传入。注意,这时,父类加载器管辖的文件目录下不应该再有自定义类加载器要加载的类了!
(3)练习:对文件加密,并使用自定义类加载器解密加载。
自定义类加载器的代码示例:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception{
//对源文件进行加密操作
String srcPath = args[0];//运行时传入参数,第一个参数为要加密的文件路径
String destDir = args[1];//运行时传入的第二个参数为加密后文件的存放目录,
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);//获取要加密的文件名字
String destPath = destDir+'\\'+destFileName;//拼接成加密后文件路径
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
copyH(fis,fos);//对读出的文件进行加密,然后写入输出流,此时会看到,运行时传入的第二个参数路径下有一个加密后的文件
fis.close();
fos.close();
}
//对文件的加密方法,即,将读出的文件异或255之后再存入文件
public static void copyH(InputStream ins,OutputStream ous) throws IOException{
int b = -1;
while((b=ins.read())!=-1){
ous.write(b^255);
}
}
private String classDir;//自定义加载类的管辖路径:这个路径下的所有类默认被该自定义类加载器加载
public MyClassLoader() {
}
public MyClassLoader(String classDir) {//构造函数初始化
this.classDir = classDir;
}
//复写父类findClass方法,解密加载
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
String classPath = classDir + '\\' +name + ".class";//要加载的文件路径
try {
FileInputStream fis = new FileInputStream(classPath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copyH(fis, baos);//对源文件解密,即再异或一遍255,又回到了原来的样子
fis.close();
byte[] bytes = baos.toByteArray();//将字节数组流写入到字节数组中
return defineClass(bytes, 0, bytes.length);//返回字节码文件。加载完毕
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
}
要加密的文件代码示例:
import java.util.Date;
public class DataFile extends Date{
@Override
public String toString() {
return "Hello World!!";
}
}
测试自定义类加载器的代码示例:
package cn.itheima.classloader;
import java.util.Date;
public class MyLosderTest {
//注意这里Myloader是该项目下自己新建的一个文件夹,运行时别忘了删除classpath 下的DataFile.class文件,否则父类就加载了
public static void main(String[] args) throws Exception {
Class clazz = new MyClassLoader("Myloader").loadClass("DataFile");
Date d = (Date)clazz.newInstance();//这里不能使用DataFile类来转型,这是因为,一用到DataFile类JVM就会加载它,未
System.out.println(d);
}
}
4、父级类加载器加载的类无法引用只能被子级类加载器加载的类,如下图: