------ <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
类加载器分析
简说:类加载器就是加载类的工具。
当出现一个类,要用此类时,那么Java虚拟机首先会将字节码加载进内存,字节码的源文件是放在硬盘上的classpath指定的目录下。
类加载器 的作用:将.class文件中的内容加载进内存进行处理,处理完后为字节码。
默认的类加载器:
JAVA虚拟机中可以安装多个类加载器,系统默认的是三个类加载器,每个类加载器加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader
BootStrap--为顶级类加载器:
类加载器本身就是一个特殊的java类,所以本身也是需要加载器加载,显然必须有第一个类加载不为java类,这个就是BootStrap。她是一个用C++写的一串二进制代码嵌套在java虚拟机内核中,即一启动就出现在虚拟机中。所以不能通过java程序获取到它的名字,获得的只能为null值。
Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化没有类加载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类加载器为其父级加载。
如图:
小例:
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();//向上遍历,父类赋值给子类
}
System.out.println(loader);
输出结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null所以BootStarp为顶层父类,ExtClassLoader是BootStrap类的子类,ExtClassLoader又是AppClassLoader的父类。
我们在安装JDK后,在JRE/lib的目录下有一个rt.jar文件,该jar包是系统通过的。在lib目录下面还有一个ext目录,里面有扩展功能的jar包。ExtClassLoader类加载器加载的是ext目录下的jar包。
在ext目录和classpath目录下都有class文件,优先加载父类的,如果父类中没有再加载子类中的jar文件。这就是传说中的加载器的委托机制。
类加载委托机制
当Java虚拟机加载一个类时,到底是用的那个类加载器去加载雷的呢?
当前线程的类加载器去加载线程的第一个类。
如果A类中引用了B类,Java虚拟机将使用加载A的加载器去加载B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器 加载类时,又先委托给其上级类加载器。
当所有的类加载器没有加载到类,九返回到发起者加载器,还加载不到就会抛出ClassNotFoundExcption,而不会找发起者的子类。
其中有一道面试题,问能否自定义个名为java.lang.System的类?
通过以上可得出总结,一般是不可用的。因为加载器委托机制的原理决定的。为了不让我们写System类,类加载器采用委托机制,这样就可以保证父类优先,也就是总是使用父类能够找到的类,这样总是使用java系统提供的System类。
但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。
自定义加载器
1.自定义加载器必须继承抽象类ClassLoader,要覆盖其中的findClass(String name)方法,而不用腹泻loadClass()方法。
2.覆写findClass(String name)方法的原因:
1)是要保留loadClass()方法中的流程,因为loadClass()中调用了findClass(String name)这个方法,此方法返回的就是去寻找父级的类加载器。
2)在loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(String name)这个方法,而找不到时就会用自己的类加载器,再找不到就报异常了,所以只需要覆写findClass()方法,那么就具有了实现用自定义的类加载器加载类的目的 。
流程:
父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defind().
3.编程步骤:
1)编写一个文件内容进行简单加密的程序
2)编写好了一个自己的类加载器,可实现对加密过来的类进行装在和解密。
3)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可以使用ClassLoader的load方法外,还能使用放置线程的上线文类加载器加载或系统类加载器,然后在使用forName得到字节码文件。
编写对class文件进行加密的工具类
创建一个加密类,加密类中有对文件的加密方法,对加密过的class文件也可以使用该加密方法进行解密:
public class MyClassLoader {public static void main(String[] args) throws IOException {
// 读取 class 文件,加密方法会对该 class 文件进行加密
FileInputStream fis = new FileInputStream( "D:\\Eclipse\\workspace\\Itheima\\bin\\ClassLoaderAttachment.class");
FileOutputStream fos = new FileOutputStream( "D:\\Eclipse\\workspace\\Itheima\\itcastlib\\ClassLoaderAttachment.class" );
cypher(fis, fos);
fis.close();
fos.close();
}
// 加密与解密方法
public static void cypher(InputStream in, OutputStream out) throws IOException {
int b = -1;
while((b = in.read()) != -1) {
// 一个字节数据与 0xFF 进行异或运算 , 也就是将二进制数据取反
out.write(b ^ 0xFF);
}
}
}