1、如何自定义类加载器?
继承java.lang.ClassLoader类,并重写起中的loadClass(String name, boolean resolve)
方法或者findClass(String name)
方法即可实现类的自定义加载。
2、如何打破双亲委派模型?
1)继承ClassLoader类并重写其中的方法
先解释下类加载的流程:
每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用双亲委派模型。
即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的
loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader中。
当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader作为父类加载器。
我们可以看看ClassLoader
类中的两个方法loadClass(String name, boolean resolve)
方法和findClass(String name)
方法的实现:
先看看loadClass(String name, boolean resolve)
方法的实现:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从该方法可以看出,每次loadClass的时候都想会将load交给父类去处理,父类加载不出来时,最终会交给当前的类加载器的findClass(name);
方法去加载。而交给父加载器加载的逻辑在loadClass(String name, boolean resolve)
方法中,所以只要重写loadClass(String name, boolean resolve)
方法就可以破坏双亲委派机制,即改变优先让父加载器加载的这段逻辑。
而重写findClass(String name)
方法依然会优先通过父加载器加载类的,我们通过重写这个方法只是改变了父加载器无法加载的类的加载逻辑。
2)通过SPI机制加载类
解释SPI前先解释两个概念:
1、SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。
2、类加载器命名空间可见性:子类加载器可以见到父类加载器加载的类,而父类加载器看不见子类加载器加载的类
双亲委派模型的这个模型存在一些缺陷,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢?
为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(ThreadContextClassLoader)
。这个类加载器可以通过java.lang.Thread
类的setContextClassLoader()
方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器,父线程使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。
3、自定义类加载器有什么好处吗?应用场景是什么?
1)加密
Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
2)从非标准的来源加载代码
如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
3)以上两种情况在实际中的综合运用
比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。