----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------
一、什么是类加载器?它的作用是什么?
1、类加载器就是在java中用于加载类的工具,它也是一个类。
2、类加载器作用:把.class加载到硬盘来,再对它进行一些处理。比如说,可以对class文件进行加密,然后只有通过自定义的类加载器才能获取到原来的字节码。
3、系统默认有三个主要的类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader。
类加载器也是java类,因为其他java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap,它是由C++编写的一段二进制代码。
Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。
示意图如下:
/*打印出来是AppClassLoader,
class文件放入jdk文件夹中的ext文件夹后,类加载器就变为ExtClassLoader了.*/
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//打印出来是null。
System.out.println(System.class.getClassLoader());
打印System类的类加载器,发现它为null,但是每个java类都是有一个类加载器的,所以可以推断,System类的类加载器为BootStrap,它不是一个java类,所以打印出来为null。
将class文件放入jdk文件夹中的ext文件夹后,类加载器就变为ExtClassLoader了呢?这个时候存在两份class文件,那为什么是由ExtClassLoader加载的呢?
这是因为类加载器的委托机制,每个类加载器加载类时,又委托给其上级类加载器,一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;当所有上级加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者加载器的儿子,因为没有getChild方法。
这样的好处可以集中管理,委托给上级,上级加载过所要求的字节码,则不用重新加载直接可以用。如果每个类直接加载,那么内存中会存在多份字节码。
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。
3)还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。
三、自定义类加载器
1、每一个自定义类加载器都必须继承抽象类ClassLoader,复写它的findClass方法。
这里复写findClass方法而不是loadClass方法的原因是:
这里要保留loadClass方法中的流程,因为loadClass方法中调用了findClass这个方法,返回父级的类加载器。所以我们只需自己写findClass这个方法就行了。
而当将得到class文件的内容转换为字节码,LoadClass中定义了一个defineClass方法,这个方法可以将二进制数据转为Class类。
这里使用的是模板设计模式:
在这里模板设计模式体现为父类里面有loadClass方法,但每个子类自己干的代码不一样,但是他们找父类的方法findClass是一样的。父类中已经将流程确定清楚,只是具体的实现代码要子类自己写。所以就可以使用模板设计模式。
2、自定义一个加密的类加载器的具体实现步骤:
1)编写一个对文件内容进行简单加密的程序
2)编写好了一个自己的类加载器,可实现对加密过的类进行装载和解密。
3)编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可使用ClassLoader的load方法外,还能使用设置线程的上下文类加载器加载或系统类加载器,然后在Class的forName方法得到字节码文件。
具体代码如下:(注意:小知识点:有包名的类不能调用无包名的类!)
package cn.itcast.day2;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
//继承ClassLoader类使得MyClassLoader成为一个类加载器
public class MyClassLoader extends ClassLoader{
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String srcPath = args[0];//定义一个源
String destDir = args[1];//定义一个目标的目录
FileInputStream fis = new FileInputStream(srcPath);
//取源的文件名作为目标的文件名
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
//拼成一个目标路径
String destPath = destDir + "\\" + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
//定义一个加密方法
private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
int b = -1;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);
}
}
private String classDir;
@Override//复写findClass方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
//定义一个字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
System.out.println("aaa");
//得到字节数组
byte[] bytes = bos.toByteArray();
//把一个字节数组生成一个Class文件
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
四、测试和调用自定义加载器
1、编写一个测试类ClassLoaderAttachment
代码如下:
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello,itcast";
}
}
2、在ClassLoaderTest类中进行测试
代码如下:
package cn.itcast.day2;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
//这里不能使用ClassLoaderAttachment是不可以的,因为这个类不能被很好地加载,所以要使用父类Date
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
}
}
如果直接进行编译,那么可以正常打印出“hello,itcast”;但是如果将生成的class文件放进jdk文件夹的ext中,则会发生异常。
这个时候父类的加载器ExtClassLoader能够加载到这个类,它就使用父类的加载器,但是系统里面的ExtClassLoader是无法对我们加密过的字节码文件进行解密的,所以无法打印出“hello,itcast”;
----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------
详情请查看: