使用
类加载器
并不会导致类的主动初始化,他只是执行了加载过程中的加载阶段。
1. 三大类加载器
- BootStrap ClassLoader:根类加载器,由 C++ 编写,负责 虚拟机核心类库 的加载,比如整个 java.lang 包都是根加载器加载的,即
JAVA_HOME\jre\lib
目录下的资源 - Extension ClassLoader:扩展类加载器,由 java 语言编写,加载
JAVA_HOME\jre\lb\ext
目录下的资源 - Application ClassLoader:系统类加载器,加载
classpath
路径下的类库资源以及二方三方jar包,他的父加载器是扩展类加载器,同时他也是自定义类加载器的默认父加载器。
【注意】每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
2. 双亲委托机制
也称:父委托机制,当一个类加载器被调用了loadClass之后,它并不会直接将其加载,而是先交给当前类加载器的父加载器尝试加载直到最顶层的父加载器,然后再依次向下进行加载。
3. 自定义类加载器
所有的自定义类加载器都必须直接或间接的继承
ClassLoader抽象类
,同时必须实现findClass()
方法,否则会抛出 Class 找不到的异常。为了保证类加载时不使用根类加载器、扩展类加载器进行类加载,可以采用破坏双亲委托机制的方式,编写自定义类加载器,需要注意重写 loadClass() 方法。
- 直接或间接继承 ClassLoader 抽象类
- 重写 findClass() 方法:否则会报类找不见异常
重写 loadClass() 方法:破坏双亲委托机制
public class BrokerDelegateClassLoader extends ClassLoader {
/** 自定义类加载路径 */
private final static Path DEFAULT_CLASS_DIR = Paths.get("E:", "tmp_dir", "classloader");
/** class文件的存储目录 */
private final Path classDir;
public BrokerDelegateClassLoader() {
super();
this.classDir = DEFAULT_CLASS_DIR;
}
public BrokerDelegateClassLoader(String classDir) {
super();
this.classDir = Paths.get(classDir);
}
public BrokerDelegateClassLoader(String classDir, ClassLoader parent) {
super(parent);
this.classDir = Paths.get(classDir);
}
/** 重写findClass()方法,否则会报类找不见异常 */
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = null;
try {
bytes = this.readClassBytes(name);
if (null == bytes || bytes.length == 0) {
throw new ClassNotFoundException("Can not load this class " + name);
}
} catch (IOException e) {
e.printStackTrace();
}
return this.defineClass(name, bytes, 0, bytes.length);
}
/**
* 将二进制文件读取到内存中
* @param name
* @return
* @throws ClassNotFoundException
*/
private byte[] readClassBytes(String name) throws ClassNotFoundException, IOException {
String classPath = name.replace(".", "/");
Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
if (!classFullPath.toFile().exists()) {
throw new ClassNotFoundException("The class " + name + " not found.");
}
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
Files.copy(classFullPath, stream);
return stream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("load the class " + name + " occur error.", e);
}
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
/** 1.根据类的全路径名称进行加锁,确保每一个类在多线程情况下只被加载一次 */
synchronized (getClassLoadingLock(name)) {
/**
* findLoadedClass 为本地 native 方法
* => 在已加载类的缓存中查看该类是否已经被加载,是则直接返回
*/
Class<?> loadedClass = findLoadedClass(name);
/** 若缓存中没有被加载的类,则需要对其进行首次加载 */
if (loadedClass == null) {
/** 如果类的全路径以java或javax开头,则直接委托给系统类加载器进行加载 */
if (name.startsWith("java.") || name.startsWith("javax")) {
try {
loadedClass = getSystemClassLoader().loadClass(name);
} catch (Exception e) {
// TODO
}
} else {
try {
/** 使用自定义类加载器进行加载 */
loadedClass = this.findClass(name);
} catch (ClassNotFoundException e) {
// TODO
}
/** 若自定义类加载器仍旧没有完成对类的加载,则委托给父类加载器或者系统类加载器加载 */
if (loadedClass == null) {
if (getParent() != null) {
loadedClass = getParent().loadClass(name);
} else {
loadedClass = getSystemClassLoader().loadClass(name);
}
}
}
}
/** 经过一系列尝试之后,还是无法对类进行加载,则抛出无法找到类的异常 */
if (loadedClass == null) {
throw new ClassNotFoundException("The class " + name + " not found.");
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
}
}
4. 实例:热部署
热部署:即在程序运行时进行某个模块功能的升级,甚至在不停止服务的前提下增加新的功能。
【注意】热部署首先要卸载掉加载该模块所有 Class 的类加载器,卸载类加载器会导致所有类的卸载,显然我们无法对JVM三大内置类加载器进行卸载,我们只有通过控制自定义类加载器才能实现该功能。即破坏掉双亲/父委托机制。