概念:类加载器就是加载类的工具
java中的类加载器:java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader,AppClassLoader类加载器也是一个java类,因此类加载器也需要被加载,所以必须有第一个类加载器不是java类,这个类就是BootStrap,它是用C++
语言写的一个二进制代码。
AppClassLoader:负责加载普通classpath下的类
一个类如果类加载器为null,不是没有类加载器,而是它的类加载器为BootStrap,因为BootStrap是C++写的,所以
通过getClassLoader()是无法获取到它的。下面是打印一个类中的所有类加载器名称的代码:
ClassLoader classLoader = JavaClassLoader.class.getClassLoader();
while(classLoader != null){
System.out.println(classLoader.getClass().getName());
classLoader = classLoader.getParent();
}
System.out.println(classLoader);
ExtClassLoader:负责加载JRE/lib/ext/*.jar,如果自己的一个类需要被ExtClassLoader加载,打个jar包丢到这个目录下就可以了
BootStrap:负责加载JRE/lib/rt.jar
Java中类加载器的关系图如下:
一个类的加载顺序:如果一个类交给AppClassLoader去加载,它不会去加载,而是交给它的父亲ExtClassLoader,ExtClassLoader也
不会去加载,而是交给爷爷BootStrap,爷爷是最顶层的类加载器了,于是开始处理,处理时会先找曾经有没有加载过这个类,加载过
就直接给你了,没加载过就开始去找这个类,找到了就处理,没找到就交给ExtClassLoader,
ExtClassLoader也是看有没有原来就加载过了,加载过就直接拿出来用不会重新加载,没加载过就开始找这个类了,又没找到,
又交给AppClassLoader,如果找到了就处理,没找到呢?咋办?抛异常,抛出
ClassNotFoundException,那为什么它不交给下级去找了呢?因为它是发起者,最先是交给它处理的,所以如果到了他这一级还没找到,
就抛异常了。这就是java的委托机制,最顶层的爷爷类BootStrap是很累的,每个类的加载都要经过他的手,但是这样有个好处,就是
集中管理。
有个问题需要注意:如果项目中有两个类,一个在JRE/lib/ext/*.jar的jar包中,一个在classpath下(也就是你自己编写的一个类),
那么肯定是先加载JRE/lib/ext/*.jar下的
模版方法设计模式:有一个方法loadClass,子类跟父类大部分代码都一样,但是方法中有点小细节不一样,这时候,可以把这些小细节定义成一个抽象方法findClass,
让子类去实现这个抽象方法,子类实现这个findClass的抽象方法之后,就具有了loadClass的功能了,而且还是具有自己各色的loadClass功能,因为loadClass里面
调用了findClass,而findClass方法是子类自己实现的。
如何定义自己的类加载器呢?
实现自定义类加载器只需要继承ClassLoader类就可以了,然后覆盖findClass方法,在findClass方法
里实现你自己想要对字节码文件的操作。这里有点疑问了,
为什么不是覆盖loadClass方法呢?
这个本来是需要覆盖loadClass(String name)方法的,这个方法底层是先去找内存
中是不是已经加载过这个类,如果加载了这个类就给你这个类,没有加载过就去找父类,
一级一级往上找,父类加载器能找到这个类就给父类处理,如果父类都找不到那么最后回到
发起者来了,发起者在loadClass方法里调用findClass方法加载类,找到了这个类就处理,
没找到就报异常了
如果我自己要写一个类加载器是不是要覆盖loadClass方法呢?不用了,因为我们自己写
类加载器就是想用我们自己的加载器去加载类,找父类的流程都是一样的,没有必要重写,loadClass中
已经有了找父类的流程代码,只是具体的找到字节码文件后的操作细节是不一样的,需要我们实现,
所以直接覆盖findClass方法就可以了。
什么时候子类需要覆盖父类的loadClass方法呢?当我们不需要找父类了,比如说我自己写个System类,这个
类是在rt.jar里面的,而BootStrap类是负责加载rt.jar的,意思就是说我写的类一般来说是不会被自己
的类加载器加载的,因为他每次都要找父类,而父类又能够处理这个Sytem类,这时候,如果我不想让父类处理我
自己写的这个System类,而是用我自己的类加载器来处理,那么我就重写loadClass方法,覆盖掉原来ClassLoader中的找父类
的流程代码就行了。
public class MainClass {
/**
* 需求:
* 编写一个对文件内容进行简单加密的程序
* 编写一个自己的类加载器,可以对加密过的类进行加载和解密
* 编写一个程序调用自己编写的这个类加载器来加载类
*
* 思路:
* 编写一个测试类ClassLoaderTest.java
* 定义一个用于加密解密字节码文件的类Cypher.java,提供加密解密的方法
* 定义一个类加载器MyClassLoader.java,继承ClassLoader并重写findClass方法,在findClass方法中对字节码文件进行解密
* 定义测试类MainClass,用于测试结果
*
* @throws Exception
*/
public static void main(String[] args) throws Exception {
String inClassDir = "F:\\Soft Installer\\Eclipse_Workspace\\exam\\bin\\com\\javaenhance\\classloader\\ClassLoaderTest.class";
String outClassDir = "F:\\Soft Installer\\Eclipse_Workspace\\exam\\temp\\ClassLoaderTest.class";
Cypher cy = new Cypher(inClassDir, outClassDir);
cy.decodeAndEncodeClass();
//需要加载的字节码文件的所在路径
MyClassLoader loader = new MyClassLoader("F:\\Soft Installer\\Eclipse_Workspace\\exam\\bin\\com\\javaenhance\\classloader");
//用自定义的类加载器加载字节码文件
Class clazz = loader.loadClass("ClassLoaderTest");
//此处没有用ClassLoaderTest接收是因为该类已经被加密过,如果直接用其接收,编译肯定有问题了
//可以让其继承一个父类,然后用起父类接收
Object obj = clazz.newInstance();
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 加密解密字节码文件
* */
public class Cypher {
//需要加密的字节码文件的路径
private String inClassDir;
//加密后的字节码文件的存放路径
private String outClassDir;
public Cypher(){}
public Cypher(String inClassDir, String outClassDir){
this.inClassDir = inClassDir;
this.outClassDir = outClassDir;
}
public void decodeAndEncodeClass() throws Exception{
//将字节码文件加密,加密后就只有自己的这个类加载器能够加载我的class文件了
FileInputStream fis = new FileInputStream(inClassDir);
FileOutputStream fos = new FileOutputStream(outClassDir);
cypher(fis, fos);
fis.close();
fos.close();
}
/**
* 加密解密方法:
* @param is 需要加密或解密的输入流
* @param os 加密后解密后的输出流
* @throws IOException
* */
public static void cypher(InputStream is, OutputStream os) throws IOException{
int len = 0;
while((len=is.read()) != -1){
os.write(len ^ 0xff);
}
}
}
public class ClassLoaderTest {
public ClassLoaderTest(){
System.out.println("我是ClassLoaderTest构造方法!");
}
public void show(){
System.out.println("哈哈,I'm ClassLoaderTest's show method!");
}
}
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader{
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = classDir + "\\" + name + ".class";
FileInputStream is;
ByteArrayOutputStream os;
byte[] b = null;
try {
is = new FileInputStream(fileName);
os = new ByteArrayOutputStream();
Cypher.cypher(is,os);
b = os.toByteArray();
is.close();
os.close();
return defineClass(null, b, 0, b.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(fileName);
}
}