Java类加载方式采取树形结构的双亲委托机制。主要分为4种类加载器:Bootstrap、ExtClassLoader、AppClassLoader、自定义类加载器
,如下图:
Bootstrap
:加载rt.jar中所有的类,C/C++编写,Java中不存在该类
ExtClassLoader
:加载ext目录下所有的扩展类
AppClassLoader
:加载应用类,一般为我们编写的字节码
MyClassLoader
:自定义类加载器
类加载器的具体规则,看下图:
如上图,加载类的时候,先看自身的ClassLoader是否已经加载过该类,加载过就直接获取;如果未加载过,则看父加载器是否已经加载过该类......以此类推。
另外需要注意,被当前类引用的类的加载应该由加载当前类的ClassLoader或者父加载器加载,否则会抛异常。
比如:如果一个类是用AppClassLoader加载的,我们把该类转移到ext目录下,该类继承的类本来也是AppClassLoader加载,由于我们把该类交由给ExtClassLoader加载了,按照加载规则,ExtClassLoader未加载到继承的类,抛异常ClassNotFoundException。
看代码示例:
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader.getClass().getName());
System.out.println(int.class.getClassLoader());
System.out.println(String.class.getClassLoader());
System.out.println(System.class.getClassLoader());
while (classLoader != null){
System.out.println(classLoader.getClass().getName());
classLoader = classLoader.getParent();
}
}
}
打印结果:
sun.misc.Launcher$AppClassLoader
null
null
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
需要注意的是,类的加载器的父亲并不是其真正的父类,看JDK源码得知,AppClassLoader和ExtClassLoader都属于Launcher类的静态内部类,都继承于URLClassLoader,继承关系如下图:
源代码简略如下:
public class Launcher {
static class AppClassLoader extends URLClassLoader {
...
}
static class ExtClassLoader extends URLClassLoader {
...
}
}
那么如何自定义一个类加载器呢?
很简单,只需要继承ClassLoader
类,并复写findClass()
方法即可。
注意:自定义类加载器的默认父加载器是AppClassLoader。
代码如下:
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
/**
* @param name 类的全路径(包名+类名),比如cn.tl.domain.Employee
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("MyClassLoader findClass execute...");
String filePath = path + name.replace('.', File.separatorChar) + ".class";
try (
FileInputStream fis = new FileInputStream(filePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream()
) {
int len;
while ((len = fis.read()) != -1) {
bos.write(len);
}
byte[] bytes = bos.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
测试类:
public class MyClassLoaderTest {
public static void main(String[] args) throws Exception{
Class<?> aClass = new MyClassLoader("F:\\").loadClass("cn.tl.domain.Employee");
System.out.println(aClass.getClassLoader().getClass().getName());
Object o = aClass.newInstance();
System.out.println(o);
}
}
打印结果:
sun.misc.Launcher$AppClassLoader
Employee [name=null, age=0]
有人可能会发现控制台并没有打印出MyClassLoader findClass execute...
,类也是AppClassLoader
加载的,我们自定义的加载器根本没有起作用。请删除工作空间编译后的class文件!!!,否则AppClassLoader
加载到该类,就终止加载流程了,根本就没MyClassLoader
的事了。
删除工作空间的class文件后的打印结果:
MyClassLoader findClass execute...
cn.tl.classloader.MyClassLoader
Employee [name=null, age=0]
到这里,再思考一个问题:为什么类加载是双亲委托机制呢?
因为如果不委托,我们自定义跟JDK一样的类,再自定义一个类加载器加载,内存中就会存在两份同样的字节码了,可以防止JDK核心类被篡改。