-------
1、类加载器(ClassLoader)简介:
Java虚拟机中可以安装多个类加载器,系统默认的三个主要类加载器,每个类负责加载特定位置的类。他们分别是:BootStrap、ExtClassLoader、AppClassLoader 。
类加载器也是java类,因为其他java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这个加载器即:BootStrap 。
类加载器之间的父子关系和管辖范围图:
2、 类加载器的委托机制。
(1) 当java要加载某个类时,到底用哪个加载器加载?
a、首先当前线程的类加载器去加载线程中的第一个类;
b、如果类 A 中引用了类 B,JVM 使用加载类 A 的类加载器来加载类 B;
c、还可以直接调用ClassLoader.loadClass( ) 方法来指定某个类加载器去加载某个类。
(2) 委托机制:每个 ClassLoader 本身只能分别加载特定位置和目录中的类,但它们可以委托给其他的类加载器去加载,这就是类加载器的委托模式。类加载器一级级委托到 BootStrap 类加载器,若 BootStrap 无法加载到当前类时,然后才一级级回退到子孙类加载器去进行加载。当退回到发起者类加载器时,若还是找不到,那么就会抛出 ClassNotFoundException 异常。
问:java中有默认的类名为 java.lang.System 的类,该类被 BootStrap 加载。那么我们能自定义一个类名叫做 java.lang.System 的类吗?
答:通常情况下,是不可能的,即使自己写了,也不会被 BootStrap 加载到,因为在 BootStrap 所加载类的位置有一个默认的 java.lang.System 的类。但是可以自定义一个类加载器来加载自定义的 java.lang.System 类。
3、自定义类加载器
(1) 自定义类加载器的步骤:
a、继承 ClassLoader 和复写 findClass 方法(模版设计模式)。
b、通过findClass 方法找 class 文件,并将其对应的二进制数据传递进 defineClass() 方法中,即可得到对应类的字节码。
(2) 需求:自定义一个类加载器,实现对加密过的文件解密和加载。
思路:首先编写一个对文件内容进行简单加密的程序 cypher 。
其次编写一个自定义类加载器 MyClassLoader ,实现对加密过的类进行加载和解密。
然后在编写一个程序 ClassLoaderTest 调用该类加载器来加载加密类 ClassLoaderAttachment 。注意,在源程序中不能用 ClassLoaderAttachment 类名定义引用变量,因为编译器无法识别被加密过的类。为了方便便把加密程序写在 MyClassLoader 类中。
a、需被加密class文件的代码:
import java.util.Date;
public class ClassLoaderAttachment extends Date {
private static final long serialVersionUID = 5922933470193613822L;
@Override
public String toString(){
return "Hello,ClassLoader!";
}
}
b、自定义类加载器MyClassLoader 中实现加密功能代码:
//文件加/解密
private static void cypher(InputStream in, OutputStream out)throws Exception{
int ch = 0;
while ((ch=in.read())!=-1) {
out.write(ch ^ 0x55);//异或:0101 0101
}
}
类名为 cn.itcast.day2.ClassLoaderAttachment 进行加密,只需通过IO输入流读取其字节码文件ClassLoaderAttachment.class,然后通过输出流重新写入到目标文件中,存放在另外的文件夹下的,目标class文件与原class文件是否同名没有关系,但最好同名,方便操作。
c、自定义类加载器MyClassLoader 中实现类加载和解密功能代码:
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取文件的相对路径
String classPathName = classDir + "/" + name.substring(name.lastIndexOf(".")+1) + ".class";
try {
FileInputStream fis = new FileInputStream(classPathName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cypher(fis,baos);
fis.close();
byte[] buf = baos.toByteArray();
baos.close();
//返回解密后的类名为 name 的class文件
return defineClass(name, buf, 0, buf.length);
} catch (Exception e) {
e.printStackTrace();
}
//返回原目录中的类名为 name 的class文件
return super.findClass(name);
}
自定义类加载器建议复写 findClass 方法,这样可以在加载类的同时时添加一些自定义的操作在其中,比如上诉的解密方案。但网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。然后,将字节输出流中经过解码后的内容存放在字节数组中,返回通过 defineClass 方法创建的Class类实例对象。
d、通过类 ClassLoaderTest 来调用该类加载器:
public static void main(String[] args) throws Exception {
//将加密文件进行解码应用,通过loadClass将加密类的类名传递给findClass,通过类加载器进行解密。
Class<?> clazz = new MyClassLoader("itcastlib/lib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
Date date= (Date)clazz.newInstance();
System.out.println(date.toString());
}
分析:引用 clazz 指向的即是 ClassLoaderAttachment 类解码后的字节码,即Class类的实例对象。这时再通过newInstance 创建 ClassLoaderAttachment 实例对象。若未经解码直接 new ClassLoaderAttachment ().toString() ,将会报 ClassFormatError 类格式化错误,因为现在的 ClassLoaderAttachment.class 中字节码对于编译器来说就是乱码。
总结:类加载器掌握即可,相对用的较少,如果能对最后这个例子能够理解清晰,那么也就懂得了自定义类加载器的原理。