保护私人版权,尊重他人版权。转载请注明出处并附带页面链接
概念
双亲委派模型是指当类加载器收到类加载任务时,会先交由自己的父类加载器去尝试加载,最终会传递到BootstrapClassLoader,只有当父类加载器无法加载任务时才会尝试自己加载。
常见类加载器
BootstrapClassLoader(启动类加载器):负责核心类库的加载,rt.jar,java.lang.*,JVM_HOME/lib目录下的,构造ExtClassLoader和AppClassLoader。
ExtClassLoader(拓展类加载器):负责加载jre/lib/ext的jar。
AppClassLoader(系统类加载器):加载应用程序的主函数类。
双亲委派模型的打破
Java SPI机制:SPI全称Service Provider Interface,用于接入产商自定义组件,例如JDBC。SPI的思想是系统抽象的各个模块有多种实现方案,例如JDBC,日志模块等。面向对象中,我们倾向于基于接口编程,模块直接不对实现类进行硬编码,否则会破坏开闭原则。为了能在模块装配的时候不需要在程序中显示声明,需要服务发现机制,SPI就是提供这样一个服务。
SPI的约定:当服务提供者提供服务接口后,在jar包下的META-INF/services/目录同时创建一个以服务接口命名的具体实现类文件。当外部程序装配模块的时候,能够以META-INF/services/里的配置文件找到具体的实现类并实例化。jdk中提供了java.util.ServiceLoader进行实现。
以JDBC为例,我们通常用以下方式获取数据库连接
1 | String url = "jdbc:mysql://localhost:3306/testdb"; |
getConnection()继续调用了重载的getConnection方法
1 | public static Connection getConnection(String url, |
重载的方法,省略若干代码,看注释可以知道此时得到的callerCL为null,那么就会执行Thread.currentThread().getContextClassLoader()并赋值给callerCL,而且后面调用isDriverAllowed(aDriver.driver, callerCL)就是使用此时获取的类加载器进行类的加载。于是重点就是停留在Thread.currentThread().getContextClassLoader()。
1 | private static Connection getConnection( |
看一下getContextClassLoader方法,发现仅仅只是简单的校验就获取了。我们回到一开始的java.sql.DriverManager.getConnection(url, “name”, “password”),这里的DriverManager,我们看一下它的初始化
1 | private DriverManager(){} |
构造方法被private修饰阻止了实例化,那么看一下静态代码块的loadInitialDrivers方法,省略若干代码,我们看一下ClassLoader.getSystemClassLoader(),发现了initSystemClassLoader(),继续往下看
1 | private static synchronized void initSystemClassLoader() { |
这里就是实现的关键,注意initSystemClassLoader的以下代码段
1 | sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); |
看一下getLauncher()
1 | public Launcher() { |
发现这里先获取了当前的系统加载器,然后将系统加载器,然后将系统加载器作为线程上下文加载器的父类,相当于构造链表一样。我们再看一下new SystemClassLoaderAction(scl),发现这个类居然有个run方法,而且是使用java.system.class.loader作为实现的线程上下文加载器。
1 | SystemClassLoaderAction(ClassLoader parent) { |
到这里我们可以得出结论:对于SPI,通过配置文件java.system.class.loader的方式实现了额外的线程上下文加载器并用于加载SPI组件,于是就打破了双亲委派模型。其实我们平时实现自定义类加载器的方式也是打破双亲委派模型,我们通过重写findClass(1.2之前是通过重写loadClass)阻止使用双亲委派模型。
ps:为什么要使用自定义类加载器?因为系统类加载器只会加载指定目录下的class文件,想加载自己的文件就可以通过自定义ClassLoader,可以用于文件改动后不想重启Java程序的热加载。