一、类加载器的概述
1、ClassLoader的定义:加载类的工具
2、作用:当Java程序需要应用一个类时,JVM需要把class文件从硬盘上加载到内存中,把文件处理成二进制的字节码,这整个过程就是类加载顺的工作;
3、类加载器之间的父子关系和管辖范围如图所示:
二、类加载器的委托机制
1、每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加装载器去加载类,这就是类加载器的委托机制;
2、委托流程:类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行真正的加载。当回退到最初的类加载器时,如果它自己也不能完成类的加载,那就会报告ClassNotFoundException异常。此时不会再找发起者类加载器的子类,因为极有可能具有多个子类加载器,找哪一个都不合适啊!
三、类加载器的特性
1、类加载器也是类(除BootStrap),JVM中的所有类加载器都具有父子关系,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器;
//类加载器的父子关系
package blog.itheima;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass().getName());
//打印结果:sun.misc.Launcher$AppClassLoader,其加载classPath目录下的类
System.out.println(System.class.getClassLoader());
//打印结果:null,说明System是由BootStrap加载的,它不是一个java类
//定义一个类加载器的类
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
//ClassLoader getParent() 返回委托的父类加载器。
}
System.out.println(loader);
/*打印结果如下,得到所有的父类加载器:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null*/
}
}
2、程序运行时,由当前线程的类加载器去加载线程中的第一个类;如果类A引用或实现了类B,JVM将使用加载类A的类加载器来加载类B;还可以直接调用Class<?> ClassLoader.loadClass(String name)方法来指定某个类加载器去加载某个类;
有一道面试题:能不能自己写一个类叫java.lang.System?
回复:通常不可以,原因是类加载器委托机制一级级委托到BootStrap,会直接在rt.jar包中找到该类,并不会加载我们编写的System类。要想加载该类,可以通过自定义加载类的方式,绕过委托机制。
3、类的初始化
static Class<?> forName(String className):返回与带有给定字符串名的类或接口相关联的Class对象,此方法会对该类的静态属性进行初始化,调用此方法等效于:Class.forName(className, true, currentLoader)
static Class<?> forName(String name, booleaninitialize, ClassLoader loader):使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象,如果为false,则不会初始化该类;
/*类的初始化:
1、通过Class.forName(String name)方法动态加载
2、通过ClassLoader.loadClass()方法动态加载*/
package blog.itheima;
class Test1 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = Test1.class.getClassLoader();
System.out.println(loader);
//打印结果:sun.misc.Launcher$AppClassLoader@1372a1a
//使用ClassLoader.loadClass()来加载类,不会执行初始化块
//loader.loadClass("blog.itheima.Test2");
//使用Class.forName()来加载类,默认会执行初始化块
//Class.forName("blog.itheima.Test2");
/*使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块,
如果是true,就会初始化静态代码块*/
Class.forName("blog.itheima.Test2",false, loader);
}
}
class Test2 {
static {
System.out.println("静态代码块执行了");
}
}
四、自定义类加载器
//自定义被类加载器加载的类
package blog.itheima;
import java.util.Date;
/*该类继承Date,是为了在后续调用时,如果用ClassLoaderAttachment创建对象,
会加载出Class文件的乱码,因为加密过,正常类加载器无法正常加载*/
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello,类加载器";
}
}
//自定义类加载器,并对输入进来的文件数据进行加密和解密
package blog.itheima;
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 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
protected Class<?> findClass(String name)throws ClassNotFoundException{
//获取.class文件的路径名
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("我的类加载器运行了");
byte[] bytes = bos.toByteArray();
/*Class<?> defineClass(String name,byte[] b,int off,int len)
将一个 byte 数组转换为 Class 类的实例*/
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
//类加载器的运行类
package blog.itheima;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
//有包名的类,不能调用无包名的类,用loadClass方法将类保存为Class文件
Class clazz = new MyClassLoader("mylib").
loadClass("blog.itheima.ClassLoaderAttachment");
//实例化对象
Date d = (Date)clazz.newInstance();
System.out.println(d);
}
}
输出是:hello,类加载器