SPI(Service Provider Interface,服务提供者接口)机制的实现有很多,除Java SPI外还有Spring boot SPI、Dubbo SPI
提示
通过SPI机制可以动态发现接口的实现类并实例化
- 接口
- 实现类
- 配置文件:每个jar包中META-INF/services文件夹下的文件,文件名为接口全路径名,文件中的每一个行都是该接口的实现类的全路径名
- ServiceLoader类
优点
解耦
缺点
会将接口的所有实现类都加载(见仁见智)
案例分析
-
JDBC中的
Driver
与DriverManager
java.sql.DriverManager#ensureDriversInitialized
,方法注释:通过两种方式发现并加载JDBC驱动,一是系统属性配置,另一个就是通过Java SPI机制来发现和加载
private static void ensureDriversInitialized() { // 省略部分代码... // 从系统属性获取JDBC驱动类名,之后会使用Class.forName来加载和初始化驱动类 String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty(JDBC_DRIVERS_PROPERTY); } }); } catch (Exception ex) { drivers = null; } // 使用Java SPI机制发现和初始化JDBC驱动 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // 发现:查找META-INF/services/java.sql.Driver中的内容 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try { while (driversIterator.hasNext()) { /* * 其中使用Class#forName(driverName, false, ClassLoader)获取Class的对象, * initialize为false(加载类但是不初始化类) * * 然后通过反射获取无参构造函数,执行Constructor#newInstance创建Driver实例(会触发驱动类的初始化) */ driversIterator.next(); } } catch (Throwable t) { // Do nothing } return null; } }); // 对系统配置中的驱动类进行加载和初始化 if (drivers != null && !drivers.equals("")) { String[] driversList = drivers.split(":"); println("number of Drivers:" + driversList.length); for (String aDriver : driversList) { try { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } } // ... }
-
Slf4j利用SPI机制加载不同提供商的日志实现
slf4j-api在新版本之后不再使用静态绑定(不需要StaticLoggerBinder),而是使用ServiceLoader绑定日志实现
org.slf4j.LoggerFactory#getLogger(java.lang.String)
扩展
有传言:Java SPI机制破坏了双亲委派机制
-
对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立它在JVM中的唯一性
-
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器首先并不会直接去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此。因此所有的类加载请求最终都会传送到启动类加载器。只有当父类加载器在其搜索范围内无法找到所需的类,并将结果反馈给子类加载器,子类加载器会尝试自己去加载
-
每个类加载器都有自己的加载范围
-
Java提供的三个类加载器
-
根据类加载机制,当被加载的类引用了另外一个类的时候,虚拟机就会使用加载第一个类的类加载器来加载被引用的类
Java SPI机制中使用了线程上下文的类加载器(默认是
AppClassLoader
) -
ServiceLoader
在rt.jar中,由启动类加载器加载(jdk8中成立,jdk9开始去掉了rt.jar)。ServiceLoader
触发了接口实现类的加载,但没有使用加载ServiceLoader的类加载器(启动类加载器)去加载接口实现类,而使用AppClassLoader去加载
以上内容似乎支撑了“Java SPI机制破坏了双亲委派机制”的说法,个人不置可否