------- android培训、java培训、期待与您交流! ----------
类加载器
一、作用:在调用某个类时,需要类加载器将该类从硬盘中的class文件加载到内存中的二进制字节码。
二、种类:java虚拟机中装有三个类加载器,每个类负责加载特定位置的类。
1、BootStrap 类加载器也是一个类,也需要被加载,所以需要有一个不是类的加载器来操作加载器。
该加载器是嵌套在java虚拟机内核中的,用来加载其他类加载器。
管辖范围:JRE/lib/rt.jar。这里提供的是我们常用的一些包,比如java.util.*;
2、ExtClassLoader 管辖范围:JRE/lib/ext/*.jar。
3、AppClassLoader 管辖范围:ClassPath指定的所有jar或目录。
4、自定义加载器。指定需要继承的父加载器。
//验证三个类加载器的关系
public class ClassLoaderTest {
public static void main(String[] args) {
//获得类加载器对象
ClassLoader cl = ClassLoaderTest.class.getClassLoader();
//如果该类加载器对象为空,说明是BootStrap加载器直接加载的
while(cl != null){
//获取该类加载器的名字并打印
System.out.println(cl.getClass().getName());
//获取该加载器的父加载器
cl = cl.getParent();
}
System.out.println(cl);
}
}
/*运行结果为:sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
说明:java中所有类加载器都采用具有父子关系的树状结构进行组织,在实例化每个类加载器对象时,
需要为其指定一个父级类加载器来加载对象或者默认采用系统类加载器为其加载。
三个类加载器的关系为 BootStrap(父)--> ExtClassLoader(子)--> AppClassLoader(孙)
三、类加载器的委托机制
1、程序运行时类加载器的加载情况
1)首先会用当前线程的类加载器去加载线程中的第一个类A;
2)如果A类引用了另一个类B,虚拟机会用加载A的加载器去加载B;
3)也可以直接用ClassLoader.class.loadClass()方法来指定某个加载器去加载。
2、委托机制
当一个类需要被加载时,子类加载器会先委托父类去加载,而自己不去加载,当父类加载不到该类时,才会委托给子类去加载。
这样做得好处就是可以集中管理,避免加载重复的类。
四、自定义类加载器
1、原理: 自定义类加载器必须先继承ClassLoader这个抽象类,该类有个loadClass(String name)方法,将需要加载的类名传入就会返回Class对象。
但是该方法会先委托给父类去查找,父类没有找到后会交给子类,而子类的loadClass方法就会调用findClass()方法去执行自己的加载方式。
这样做是为了让子类既能够保留这种委托机制,又可以避免每次去重复写这些代码。这样的设计模式称为模板方法设计模式。
所以我们只需要复写其中的findClass()方法就行。
2、用findClass这个方法会得到需要加载的class二进制文件,再调用definClass方法将该二进制文件传入获得字节码文件。
3、实例需求:自定义一个类加载器,对一个加密的类文件加载
//测试类,将要被加密操作的类
public class Test extends Date {
public String toString (){
return "hello world!";
}
public static void main(String[] args) {}
}
//主类,对加密前和后分别进行操作的类
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//该段代码用来对传入的类进行加密
//获取传入的源路径和目标路径
String srcPath = args[0];//绝对路径
String destDir = args[1];//相对路径
//创建输入流
InputStream in = new FileInputStream(srcPath);
//将传入的源路径的最后一个"\\"后面的字符串返回作为目标文件名,
String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
//获取目标的绝对路径
String destPath = destDir + "\\" + destFileName;
//创建输出流,将加密后的文件存入目标路径下
OutputStream out = new FileOutputStream(destPath);
//调用自定义的加密方法
cypher(in,out);
in.close();
out.close();
System.out.println(new Test().toString());
//该段代码用来对加密后的类文件进行操作
//需要用到自定义的类加载器
Class c = new MyClassLoader("lannlib").loadClass("lann.Day3.Test");
Date d = (Date)c.newInstance();
System.out.println(d);
}
//定义一个加密方法,该方法比较简单,因为减密也是用该方法
public static void cypher(InputStream in , OutputStream out) throws Exception{
int len = 0;
//反码加密
while((len=in.read()) != -1){
out.write(len ^ 0xff);
}
}
}
//定义一个加载器
class MyClassLoader extends ClassLoader{
MyClassLoader(){}
//有参的构造方法,用来初始化传入的路径
private String classDir;
MyClassLoader(String classDir){
this.classDir = classDir;
}
//复写findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException{
//接收的参数为带包名的类文件名,所以需要将包名去掉。
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
try {
InputStream in = new FileInputStream(classFileName);
//用字节数组输出流是因为下面的defineClass需要字节数组作为参数
ByteArrayOutputStream out = new ByteArrayOutputStream();
//减密操作
ClassLoaderTest.cypher(in,out);
//将流中的数据转到数组中
byte [] bt = out.toByteArray();
//调用defineClass方法返回该类
return defineClass(bt,0,bt.length);
} catch (Exception e) {
e.printStackTrace();
}
//如果找不到可以返回其父类,一般用不着
return super.findClass(name);
}
}
------- android培训、java培训、期待与您交流! ----------