Java 遵守“双亲委托机制”的自定义类加载器

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_26486347/article/details/102599791
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()方法自动构造的。
	 */
	
}

猜你喜欢

转载自blog.csdn.net/qq_26486347/article/details/102599791