------- android培训、java培训、期待与您交流! ----------
类加载器:就是加载类的工具。
当出现一个类,用到此类的时候,Java虚拟机首先将类字节码加载进内存,通常字节码的原始信息放在硬盘上的classpath指定的目录下。
2、类加载器作用:将.class文件中的内容加载进内存进行处理,处理完后的结果就是字节码。
3、默认类加载器:
1)Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader
2)BootStrap--顶级类加载器:
类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。
4、Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。
类加载器的委托机制:
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
获取某个类的加载器对象代码示例:
public static void main(String[] args) throws Exception {
//获取本类字节码,通过字节码获取本类的加载器对象,通过本类加载器对象获取加载器所属类,再获取该类的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
.getName());
//System类的加载器为BootStrap,这个类加载器很特殊。它不是java类不需要被别的类加载,他是嵌套
//在虚拟机内核里面的,java虚拟机内核启动的时候,它就已经在里面了。它可以加载别的类,也包括类加载器
System.out.println(System.class.getClassLoader());
//获取本类字节码,通过字节码获取本类的加载器对象
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//如果加载器不为空的话
while (loader != null) {
//就循环打印出加载器对象所在类的name
System.out.println(loader.getClass().getName());
//然后把该加载器的父类赋给该加载器
loader = loader.getParent();
}
//打印出最终的加载器对象
System.out.println(loader);
System.out.println("------------------------------------");
}
编写自己的类加载器:
知识讲解:
自定义的类加载器的必须继承ClassLoader
loadClass方法(直接继承,省去委托机制的编写)与findClass方法(覆盖这个就行了)
defineClass方法
需求:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
代码实现:
1.编写一个简单的需要被加密的小程序:
import java.util.Date;
//定义一个需要被加密处理的类
public class ClassLoaderAttachment extends Date {
public String toString() {
return "itheima";
}
public static void main(String[] args) {
System.out.println("itheima!");
}
}
2.编写自己的类加载器,该类加载器可以对加密过的类进行加载和解密。此类在运行时,需要对main方法进行传参。传两个参数,第一个为需要加密的.class文件路径,第二个是需要把加密后的.class文件存放的目录:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
//此类在运行时,需要对main方法进行传参,传两个参数,第一个为需要加密的.class文件路径
//第二个是需要把加密后的.class文件存放的目录
public static void main(String[] args) throws Exception {
//获取main函数第一个参数,该参数是需要被加密的.class文件的完整路径
String srcPath = args[0];
//获取main函数的第二个参数,该参数是加密后的.class文件所要存放的目录
String destDir = args[1];
//用String类的lastIndexOf方法获取需要加密的.class文件的最后的一个 "\" 的位置,
//然后用String类的subString字符串截取方法从最后一个 "\" 的位置开始截取至末尾,
//截取出的子字符串就是需要被加密的.class文件的全名称,用destFileName变量记录下来
String destFileName = srcPath.substring(srcPath.lastIndexOf("\\"));
//把main函数传值的第二个参数和上面截取出的.class文件名加起来就形成一个被加密后的.class文件所需要
//存放的完整路径
String destFilePath = destDir+destFileName;
//创建一个文件输入流对象,关联要读取的文件,并加入了缓冲技术
BufferedInputStream bufis =
new BufferedInputStream(new FileInputStream(srcPath));
//创建一个文件输出流对象,指定文件要存放的路径,并加入了缓冲技术
BufferedOutputStream bufos =
new BufferedOutputStream(new FileOutputStream(destFilePath));
//调用cypher方法,传入参数输入流和输出流
cypher(bufis, bufos);
//关闭输入流资源
bufis.close();
//关闭输出流资源
bufos.close();
}
//cypher方法,该方法包含了循环的对输入流中的数据进行读取,把读取到的数据进行异或加密操作然后写入指定的文件
public static void cypher(InputStream ips, OutputStream ops) throws Exception {
//y变量用来记录读取到的每个数据
int by = 0;
//循环读取每个数据,存入by变量中
while ((by = ips.read()) != -1) {
//把读取到的数据进行异或加密操作后,写入输出流
ops.write(by ^ 0xff);
}
}
//这是一个类加载器的findClass方法,该方法需要调用者传入需要加载的加密过的.class文件的名字
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//需要加载的加密过的.class文件的目录加上它的名称就组成了需要加载的被加密过的.class文件的完整路径
String classFileName = classDir + "\\" + name +".class";
try {
//创建了一个文件输入流对象,用于读取.class文件,加入了缓冲技术
BufferedInputStream bufis =
new BufferedInputStream(new FileInputStream(classFileName));
//创建一个字节数组的输出流,该流具有字节缓冲区,用来存放从加密过的.class文件中读取到的数据
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
//调用cypher方法,把读取到的加密过的.class文件的每个字节进行异或操作后再存入字节数组输出流的缓冲区
cypher(bufis, baos);
//关闭读取的流资源
bufis.close();
//把字节数组输出流缓冲区中的数据全部存入到一个字节数组中
byte[] buf = baos.toByteArray();
//将一个 byte 数组转换为 Class 类的实例。并返回给调用者,必须分析 Class,然后才能使用它。
return defineClass(null, buf, 0, buf.length);
//处理异常
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
//构造函数,本类是一个类加载器类,在实例化本类时,需要传入需要加载的加密过的.class文件的目录。
private String classDir;
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
}
3.编写测试类:
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//创建自定义的类加载器的对象,传入要加载的加密过的.class文件的目录。通过该对象,调用该类加载器的loadClass方法
//就得到了一个要加载的加密过.class文件的字节码
Class classjm =
new MyClassLoader("itheimalib").loadClass("ClassLoaderAttachment");
//通过得到的字节码就可以实例化出该字节码对应的该类的一个实例化对象
Date date = (Date) classjm.newInstance();
//调用该类的toString方法,打印结果
System.out.println(date.toString());
}
}
如果直接运行被加密后的ClassLoaderAttachment.class的话,就会出现乱码,因为系统默认的AppClassLoader加载器没有对该程序进行解密的操作。
必须要用到我们自己定义的类加载器MyClassLoader去加载,因为自己定义的这个类加载器里面有对文件进行解密的操作,只有通过自己定义的类加载器对加密后的.class文件进行解密后ClassLoaderAttachment.class才会正常运行,不会再是乱码。
------- android培训、 java培训、期待与您交流! ----------