初识类加载器
它的作用:它是用来加载类的工具。
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap 用来预先载入jre下的类库。用C++编写的,在Java中看不到它。
ExtClassLoader 用来加载扩展类,即/lib/ext中的类。
AppClassLoader 加载CLASSPATH路径下的类。
看一个例子:
package cn.cast.day2;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//将上面语句的测试类改为System则抛NullPointerException,这两个类存放位置不同
System.out.println(System.class.getClassLoader());
//BootStrap加载java.lang.System类,所以打印的结果为null。
ClassLoader loader=ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader=loader.getParent();
}
}
}
结果:
sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛 ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法。
所以,每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
这是在JDK中相关ClassLoader类的相关资料:
public abstract class ClassLoader
extends Object
每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。
数组类的 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类加载器由 Class.getClassLoader() 返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。
应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
类加载器通常由安全管理器使用,用于指示安全域。
ClassLoader 类使用委托模型来搜索类和资源。每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。虚拟机的内置类加载器(称为 "bootstrap class loader")本身没有父类加载器,但是可以将它用作 ClassLoader 实例的父类加载器。
通常情况下,Java 虚拟机以与平台有关的方式,从本地文件系统中加载类。例如,在 UNIX 系统中,虚拟机从 CLASSPATH 环境变量定义的目录中加载类。
然而,有些类可能并非源自一个文件;它们可能源自其他来源(如网络),也可能是由应用程序构造的。defineClass 方法将一个 byte 数组转换为 Class 类的实例。这种新定义的类的实例可以使用 Class.newInstance 来创建。
类加载器所创建对象的方法和构造方法可以引用其他类。为了确定引用的类,Java 虚拟机将调用最初创建该类的类加载器的 loadClass 方法。
例如,应用程序可以创建一个网络类加载器,从服务器中下载类文件。示例代码如下所示:
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
. . .
网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。示例实现如下:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}
编写一个自己的类加载器
编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译 器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置 线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: E:\workspace\javaenhance\bin\cn\cast\day2\ClassLoaderAttachment.class E:\itcastlib
package cn.cast.day2;
//要进行加密的自定义类
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello,yangcheng!";
}
}
package cn.cast.day2;
//进行加密和解密处理自定义类加载器的类
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader {
public static void main(String[] args)throws Exception{
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 r=-1;
while((r=ips.read())!=-1){
ops.write(r^0xff);
}
}
}
package cn.cast.day2;
//测试运行结果的类
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(new ClassLoaderAttachment().toString());
}
}
再运行ClassLoaderTest.java,结果:
hello,yangcheng!
用加密后的类文件替换bin目录下的.class文件,再执行上一步操作就出问题了, 错误说明是AppClassLoader类装载器装载失败。
运行ClassLoaderTest.java,结果:
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file cn/cast/day2/ClassLoaderAttachment
下来对ClassLoaderAttachment.class解密:
package cn.cast.day2;
//进行加密和解密处理自定义类加载器的类
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
public static void main(String[] args)throws Exception{
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 String classDir;
@Override
//覆写findClass方法
protected java.lang.Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName=classDir+"\\"+name.substring(name.lastIndexOf('.')+1)+".class";
try {
FileInputStream fis=new FileInputStream(classFileName);
ByteArrayOutputStream bos=new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes=bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return super.findClass(name);
};
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir=classDir;
}
//加密拷贝
private static void cypher(InputStream ips,OutputStream ops)throws Exception{
int r=-1;
while((r=ips.read())!=-1){
ops.write(r^0xff);
}
}
}
package cn.cast.day2;
import java.util.Date;
//测试运行结果的类
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
Class clazz=new MyClassLoader("itcastlib").loadClass("cn.cast.day2.ClassLoaderAttachment");
Date d1= (Date)clazz.newInstance();
System.out.println(d1);
}
}
bin目录下ClassLoaderAttachment.class文件还存在的话,结果:
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file cn/cast/day2/ClassLoaderAttachment
删除掉bin目录下ClassLoaderAttachment.class文件
结果:
hello,yangcheng!
一个类加载器的高级问题分析
在Java Web开发中,自定义MySevelet继承HttpServlet类,将MyServelet类打包成jar包放入ext目录,重启tomcat后,出现HttpServlet错误。必须把servelt.jar也要放到ext目录中。
原因是父类集加载器加载的类无法引用只能被子类级即在其加载的类,如图: