import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 我的遵守“双亲委托机制”的自定义类加载器
* 描述1:用于加载一个(或多个)指定目录(包括各级子目录)下的.jar及.class文件
* 描述2:加载.jar最终也是加载它里面的.class文件
* 描述3:当前自定义类加载器将把所有的.class放入变量 classMap 中,以供调用
* 描述4:此类遵守“双亲委托机制”(其实我觉得叫父类委托机制貌似更好理解,不然这个“双亲”解释的不是很到位)
*
* 开场白:ClassLoader是用来加载.class文件到JVM,以供程序使用的。
* java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的.
*
* zkh
* 2019年4月20日 下午7:14:07
*/
public class ObeyClassLoader extends ClassLoader {
// 静态属性 obeyClassLoader 只能被初始化一次,重新赋值无效
private static ObeyClassLoader obeyClassLoader = null;
/**
* 这个classMap在这里代表当前我们的自定义类加载器的缓存区,
* 接下来我们将把.class文件加载(或者说是放入)到这个 classMap 中等待调用
*/
private Map<String, byte[]> classMap = new ConcurrentHashMap<>();
public ObeyClassLoader() {}
/**
* 加载一个目录下的.jar及.class文件
*/
public ObeyClassLoader(String classPath) {
classMap = new LoadJarClass(classPath).getClassMap();
}
/**
* 加载一个目录下的.jar或者.class文件
*/
public ObeyClassLoader(String classPath, String classOrJar) {
classMap = new LoadJarClass(classPath, classOrJar).getClassMap();
}
/**
* 加载多个目录下的.jar及.class文件
*/
public ObeyClassLoader(String[] classPaths) {
classMap = new LoadJarClass(classPaths).getClassMap();
}
/**
* 加载多个目录下的.jar或者.class文件
*/
public ObeyClassLoader(String[] classPaths, String classOrJar) {
classMap = new LoadJarClass(classPaths, classOrJar).getClassMap();
}
/**
* 将.class文件放入classMap
* 描述:负责将.class文件加载进 classMap,也就是放入当前这个类加载器中,以便使用
*/
public boolean addClass(String className, byte[] byteCode) {
if (!classMap.containsKey(className)) {
classMap.put(className, byteCode);
return true;
}
return false;
}
/**
* 从Map中删除指定class文件(虚拟机中的 class文件的卸载是不可控的,需要其不存在引用等条件)
*/
public boolean removeClass(String className) {
if (classMap.containsKey(className)) {
classMap.remove(className);
return true;
}
return false;
}
public static ObeyClassLoader getObeyClassLoader() {
return ObeyClassLoader.obeyClassLoader;
}
/**
* 保证静态属性 obeyClassLoader 只能被初始化一次
*/
public static void setObeyClassLoader(ObeyClassLoader obeyClassLoader) {
if(ObeyClassLoader.obeyClassLoader == null) { ObeyClassLoader.obeyClassLoader = obeyClassLoader; }
}
/**
* 查找名称为 className 的类,返回的结果是 java.lang.Class 类的实例
* 描述:当前自定义类加载器遵守双亲委托机制
*/
@Override
public Class<?> findClass(String className) throws ClassNotFoundException {
try {
byte[] result = getClass(className);
if (result == null) {
// 如果没有找到class文件则抛出 ClassNotFoundException 异常
throw new ClassNotFoundException();
} else {
// 把字节数组 result 中的内容转换成 Java 类,返回的结果是 java.lang.Class(详解看下面的注释)类的实例。这个方法被声明为 final 的。
return defineClass(className, result, 0, result.length);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// (上面方法的子方法)从当前自定义加载器的缓存 classMap 中获取名称为 className 的类的字节数组
private byte[] getClass(String className) {
if (classMap.containsKey(className)) {
return classMap.get(className);
} else {
return null;
}
}
/**
* 注释1:
* |||(正规说法)双亲委托机制的工作流程如下|||
* 前序:每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
* 1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
* 2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,
* 首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader。
* 3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中, 以便下次有加载请求的时候直接返回。
*
* |||(形象说法)双亲委托机制的工作原理如下|||
* 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
* 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,
* 请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,
* 就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,
* 这就是双亲委托机制,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,
* 儿子自己想办法去完成,这就是传说中的双亲委托机制。
*
* |||双亲委托机制的好处|||
* 1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
* 2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,
* 那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,
* 这样会存在非常大的安全隐患,而双亲委托机制,就可以避免这种情况,因为String已经在启动时被加载,
* 所以用户自定义类是无法替代java核心api中的类的。
*
* 双亲委托机制 只是Java推荐的机制,并不是强制的机制
* 我们可以继承java.lang.ClassLoader类,实现自己的类加载器。
* 如果想保持双亲委托机制,就应该重写findClass(name)方法;
* 如果想破坏双亲委托机制,可以重写loadClass(name)方法。
*
* ******当前自定义类加载器遵守双亲委托机制******
*/
/**
* 注释2:
* |||ClassLoader 中与加载类相关的方法|||
* getParent() 返回该类加载器的父类加载器
* loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例
* findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例
* findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例
* resolveClass(Class<?> c) 链接指定的 Java 类
* defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的
*/
/**
* 注释3:
* JVM中都有一个对应的java.lang.Class对象,提供了类结构信息的描述。
* 数组,枚举及基本数据类型,甚至void都拥有对应的Class对象。
* Class类没有public的构造方法,Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。
*/
}