本文共 3438 字,大约阅读时间需要 11 分钟。
欢迎访问陈同学
在前几天的译文 中有部分关于ContextClassLoader的内容,涉及到SPI机制,本文将学习下相关知识。
SPI全称为 Service Provider Interface,直译为 服务提供者接口,翻译成中文后比较拗口,难以理解。
简单来说,SPI通过将服务的接口与实现分离以实现解耦,提高程序拓展性的机制,达到插拔式的效果。相同的标准,各服务厂商可以提供不同的实现。这尤其适合于面对未知的实现或者对拓展开放的系统,可以先行制定标准,服务提供者根据标准提供实现即可。
Java中使用SPI机制的例子很多,例举几个:
稍微延伸一下,其实不仅仅是Java,像计算机行业的各种规范、协议也是类似的。甚至生活中的例子,如:
扯的有点远,下面以一个简单例子演示下。
首先,了解下SPI机制的约定(约定优于配置理念):
META-INF/services/
目录下创建一个以 接口全限定名 命名的文件,文件内容为 实现类的全限定名 META-INF/services/
下的实现类假设森林动物园举行歌唱比赛,各参赛动物选手需高歌一曲。我们定义一个接口 Animal
,标准为 sing()
唱歌。
创建一个普通maven项目,创建以下对象。
// Animal接口, 制定了 sing() 标准public interface Animal { void sing();}
三位参赛选手,分别实现了sing()
标准
Cat.java
public class Cat implements Animal { public void sing() { System.out.println("喵~"); }}
Cuckoo.java
public class Cuckoo implements Animal { public void sing() { System.out.println("布谷~"); }}
Dog.java
public class Dog implements Animal { public void sing() { System.out.println("汪~"); }}
在resource下创建META-INF/services
目录,下面创建以接口全限定名org.utopiavip.spi.Animal
命名的文件,内容为三位实现者:
org.utopiavip.spi.Catorg.utopiavip.spi.Dogorg.utopiavip.spi.Cuckoo
将项目打成jar包。
在另一个项目中引入该jar包,测试类如下:
public class SpiDemo { public ServiceLoaderserviceLoader = ServiceLoader.load(Animal.class); public static void main(String args[]) { SpiDemo spiDemo = new SpiDemo(); spiDemo.sing(); } public void sing() { for (Animal singer : serviceLoader) { singer.sing(); } }}
运行后输出如下:
喵~汪~布谷~
小例子就完成了。
Mysql驱动包中对 java.sql.Driver 的实现类为 com.mysql.fabric.jdbc.FabricMySQLDriver
再看看接口和实现类的ClassLoader。
System.out.println(java.sql.Driver.class.getClassLoader());System.out.println(com.mysql.fabric.jdbc.FabricMySQLDriver.class.getClassLoader());
输出结果如下:
nullsun.misc.Launcher$AppClassLoader@135fbaa4
null表示Bootstrap class loader(SPI的接口都由Bootstrap class loader加载),而实现类是由AppClassLoader加载的。
类加载规则中有这么一点:一个类中所关联的其他类都由当前类的加载器进行加载。
仍然以Driver为例,Java中使用DriverManager来获取JDBC连接,DriverManager 位于 rt.jar 中,由Bootstrap class loader负责加载。
java.sql.DriverManager.getConnection("url", "user", "pwd")
在getConnection()的调用过程中,需要加载 java.sql.Driver 的实现类 com.mysql.fabric.jdbc.FabricMySQLDriver,可Bootstrap class loader无法找到该实现类,因为FabricMySQLDriver由System class loader加载。
这是由于类加载的委派原则及可见性制约,Bootstrap class loader将无法获取子加载器System class loader中加载的FabricMySQLDriver类。
为了解决这个问题,提出了 ContextClassLoader 概念,绕开委派原则,既然当前的加载器是Bootstrap class loader,导致无法加载FabricMySQLDriver类,那就变更当前的class loader,想加载谁就加载谁!虽然有点流氓派头,但确实是这么干的。规则是人定的,变更规则成本太高,就搞点特殊化。
java.lang.Thread
有个NB的方法 setContextClassLoader()
,用来变更当前线程的class loader。
public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl;}
contextClassLoader 取名也很有趣,当前线程的 context ClassLoader。特殊化也就搞一小会,不大范围搞。
/* The context ClassLoader for this thread */private ClassLoader contextClassLoader;
BB这么多,SPI其实非常简单:大佬们定规矩(规范),兄弟们实现后放到约定的地方(META-INF/service/
),包装上写好是啥东西(接口全限定名),包装里写清楚东西放哪儿了(实现类全限定名)。
欢迎关注陈同学的公众号,一起学习,一起成长
转载地址:http://nedja.baihongyu.com/