Java虚拟机会建立三类ClassLoader,分别以下html
名称 | 加载 | 加载路径 | 父加载器 | 实现 |
---|---|---|---|---|
BootStrap | 虚拟机的核心类库 | sun.boot.class.path | 无 | 系统 |
Extension | 扩展类库 | java.ext.dirs、jre/lib/ext | BootStrap | Java |
System | 应用类库 | classpath、java.class.path | Extension | Java |
注:父子加载器并不是继承关系,也就是说子加载器不必定是继承了父加载器
java
其实我以为把「双亲委托模式」称为「父加载委托模式」更好理解,「双」字把我给弄混了。mysql
「双亲委托模式」指的就是某个特定的类加载器在接到加载类的请求时,
首先将加载任务委托给父类加载器
,若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,本身才去加载。android
下面是一段ClassLoader的源码,很容易能够看出上述规则:sql
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ // 首先检查该name指定的class是否有被加载 Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //若是parent不为null,则调用parent的loadClass进行加载 c = parent.loadClass(name, false); }else{ //parent为null,则调用BootstrapClassLoader进行加载 c = findBootstrapClass0(name); } }catch(ClassNotFoundException e) { //若是仍然没法加载成功,则调用自身的findClass进行加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
委托永远是子加载器去请求父加载器,是单向的,即上层的类加载器没法访问下层的类加载器所加载的类
:数据库
举个例子,假设「BootStrap」中提供了一个接口,及一个建立其实例的工厂方法,可是该接口的实现类在「System」中,那么就会出现工厂方法没法建立在「System」加载的类的实例的问题。拥有这样问题的组件有不少,好比JDBC、Xml parser等。安全
如今引入一个新的名词「SPI」。框架
「SPI」 全称为 (Service Provider Interface) ,是JDK内置的一种
服务提供发现机制
。 目前有很多框架用它来作服务的扩展发现, 简单来讲,它就是一种动态替换发现的机制。ide
JDBC自己是Java链接数据库的一个标准,是进行数据库链接的抽象层,由Java编写的一组类和接口组成,接口的实现由各个数据库厂商来完成,不一样厂商能够针对同一接口作出不一样的实现,MySQL和PostgreSQL都有不一样的实现提供给用户,而Java的「SPI」机制能够为某个接口寻找服务实现。函数
下面以JDBC为例,介绍「SPI」机制。
在JDBC4.0以前,咱们开发有链接数据库的时候,一般会用Class.forName("com.mysql.jdbc.Driver")这句先加载数据库相关的驱动,而后再进行获取链接等的操做。而JDBC4.0以后不须要用Class.forName("com.mysql.jdbc.Driver")来加载驱动,直接获取链接就能够了,如今这种方式就是使用了Java的「SPI」扩展机制来实现。
JDBC在java.sql.Driver
只定义了接口。
这里以MySQL为例,在mysql-connector-java-6.0.6.jar
包里的META-INF/services
目录下能够找到一个java.sql.Driver
文件,文件内容是一个类名,这个名叫com.mysql.cj.jdbc.Driver
的类就是MySQL针对JDBC中定义的接口的实现。
在咱们的应用里面,咱们就能够直接链接MySQL了。
Connection conn = DriverManager.getConnection(url,username,password);
显然语句并无加载实现类,这里就涉及到使用「SPI」扩展机制来查找相关驱动了,接下来,咱们结合源码探究一下这是如何实现的。
关于驱动的查找其实都在DriverManager中,DriverManager位于java.sql
包里,用来获取数据库链接,在DriverManager中有一个静态代码块以下:
loadInitialDrivers
方法用于实例化驱动,由3部分构成:
两个比较关键的地方是ServiceLoader.load
, 还有loadedDrivers.iterator
,下面结合源码介绍一下:
ServiceLoader
封装了一个自定义加载器loader
,还应留意一下下面2个成员,以后会用到:
PREFIX
lookupIterator
ServiceLoader.load(Driver.class)
最后会调用构造函数,返回ServiceLoader
实例
每个线程都有本身的ContextClassLoader,默认以
SystemClassLoader
为ContextClassLoader。经过Thread.currentThread().getContextClassLoader()
,能够把一个ClassLoader置于一个线程的实例之中,使该ClassLoader成为一个相对共享的实例,这样即便是启动类加载器中的代码也能够经过这种方式访问应用类加载器中的类了。

多个加载器经过上下文加载器共享
loadedDrivers.iterator
方法返回一个迭代器,这个迭代器是「SPI」机制加载实现类的关键,迭代器在iterator()
方法内定义:
「SPI」加载代码的是这样的:
执行driversIterator.hasNext
时,会调用lookupIterator.hasNext
去找的实现类的名字。
接着会调用lookupIterator.next()
去加载这个类:
至此,已经将实现类成功加载。
如今就能够根据第1步获取到的驱动列表来加载实现类了:
「SPI」经过循环加载实现类,显而易见,它会把全部的类一同加载,不管有没有用到,这形成了必定的资源浪费:
android classloader双亲委托模式
dubbo源码解析-spi(一)
Java中SPI机制深刻及源码解析
走出ClassLoader的迷宫