一、SPI是什么?
SPI 的全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制。比如JAVA中定义了jdbc的规范,然后由不同的厂商去落地实现该规范(也就是服务),然后可以通过ServiceLoader去加载这些服务。通过SPI机制可以实现模块插拔,比如当前使用的数据库是oracle,如果想切换为MySQL的话只需要将对应的jar包切换为MySQL的即可。(当然对应的sql语句可能会出问题,除非使用的是Hibernate这种不依赖具体关系型数据库的框架)
二、自己实现一个SPI
1.定义接口(规范):
package com.xiezy.spi;
public interface SPITest { public String say(String str); }
2.实现接口(服务)
package com.xiezy.spi;
public class SPITestImpl implements SPITest { @Override public String say(String str) { return "SPI---" + str; } }
3.在服务的META-INF/Services目录下创建一个名称为接口全限定名,内容为实现类的全限定名。在本例中名称为:com.xiezy.spi.SPITest
com.xiezy.spi.SPITestImpl
4.测试类
package com.xiezy.spi; import java.util.ServiceLoader; public class SPITestMain { public static void main(String[] args) { ServiceLoader<SPITest> spiTests = ServiceLoader.load(SPITest.class); for (SPITest spiTest : spiTests) { System.out.println(spiTest.say("haha~")); } } }
三、实际例子(JDBC)
相信大家在刚学习jdbc时的使用步骤都是先通过Class.forName("dirver")将对应的Driver加载进JVM才可以使用,但是现在是不需要的。现在我们可以像下面直接使用:
这是因为SPI机制已经将需要的Driver自动加载了,可以看一下DriverManager.getConnection的源码(只截取重要部分):
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { /** ... **/ //遍历已经注册的Dirver,并尝试去获取连接 for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } }
可以看到是遍历registeredDrivers,如何去获取连接。那么这个registeredDrivers是什么时候赋值的呢?通过源码可以看到有如下方法:
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { registerDriver(driver, null); }
但是没看到在什么地方调用了该方法,在了解该方法究竟在哪调用前。我们先看一下DriverManager的static块
/** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {@code ServiceLoader} mechanism */ static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { /** ... **/ public Void run() { //通过SPI机制加载Driver ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; /** ... **/ }
可以看到DriverManager在初始化的时候去加载了Driver的实现类,比如本例子中使用的MySql包
我们在看一下com.mysql.cj.jdbc.Driver初始化过程
public class Driver extends NonRegisteringDriver implements java.sql.Driver { // // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } }
可以看到在初始化的时候调用了registeredDrivers方法,所以我们知道了DriverManager中的registeredDrivers属性是在这时候赋值的。