1.双亲委派模型的优点:
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。
2.如果我们不想用双亲委派模型怎么办?
自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法
3.为什么要打破双亲委派模型?哪些地方打破了双亲委派模型
使用线程上下文类加载器
JDBC如何破坏双亲委派模型的?为什么要破坏?
首先,理解一下为什么JDBC需要破坏双亲委派模式,原因是原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文件类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。下面看看JDBC中是怎么去应用的呢
总结就是: 由于jdbc需要不同的数据库实现,而最开始的是用启动类加载器来实现,实现以后要用应用程序类加载器,所以必须重写破坏, 这个时候用的就是线程上下文类加载器
在我们使用JDBC的时候,JDBC有些接口在java核心库中,由启动类加载器去加载,但是接口的实现类却是不同厂商所提供的,例如MySQl和Oracle,这两个jar包被下载下来直接就放入Classpath中使用,这会导致启动类加载器根本找不到实现类jar包,自然也就无法加载了,这就体现出线程上下文类加载器的作用了
父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader( )所指定的classloader加载的类。这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。
在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于spI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加教器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供), Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。
ServiceLoader
ServiceLoader是java.util包里的即是java核心库里的,故其被启动类加载器来加载(输出结果null已经再次证实了),调用load方法给的参数只是一个接口,而输出的driver是在我导入的jar包里的类,该jar包位于Classpath,并不在启动类的查找路径范围,那启动类加载器又是如何找到他并加载的?接下来通过分析来解释。
- ServiceLoader是一个简单地服务提供者(实现类,例如mysql的jar包)的加载设施
- 服务提供者会去实现服务接口,并提供自己的代码逻辑,且实现类中必须有一个无参构造方法用于之后的实例化
- 服务提供者将提供的配置文件放入"META-INF/services/"下,且文件名必须为服务类型的完全限定名,文件里面每一行就是一个服务提供者的相应的类名
- 对第三点进行一下详细说明:

文件名与类型名相同,文件里的内容就是真正的服务提供者类:

与上面被加载的driver输出结果driver:class com.mysql.cj.jdbc.Driver,完全一致,故其实ServiceLoader.load加载不是接口,而是实现类。
(待补充)
打破我说的是线程上下文类加载器,就是引入线程上下文类加载器(通过 Thread 类的setContextClassLoader()方法设置类加载器),通过父类加载器去请求子类加载器来完成类的加载。不过我只记得是叫线程上下文类加载了,忘了是通过设置类加载器来打破,所以面试官就问我说这难道不是一种新的类加载器吗,我说不是,只是在加载的时候可以通过他来改变,面试官表示不解,我表示忘了。于是过🤣。
4.类加载器之间的关系
启动类加载器,由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
应用程序类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
自定义类加载器,父类加载器肯定为AppClassLoader。
总结:
这块之前看一个spring还是哪个源码的时候看到过,一些接口可以通过自己实现,然后放到spring里去加载,而非加载spring自身的接口实现,类加载器真是个神奇的东西,也是有很大发挥空间的一块地方