spi(Service Provider Interface)
用于接口编程,主要应用于调用其他厂商实现并部署在classpath下的jar包,如jndi,jdbc等
在jdk6后,提供了java.util.ServiceLoader类,采用META-INF/services/中的配置信息
对于ServiceLoader类,采用了懒加载,在其内部,可以看到
private LazyIterator lookupIterator;
的成员变量代码,在使用Service.load方法后,并不会真正加载实现类,而是返回一个Service服务类,在真正使用到的时候,即调用迭代器next()方法后,才加载服务实现类
以jdbc进行测试,引入mysql的maven依赖后,主类如下
public class SPI_JDBCTest {
public static void main(String[] args) throws Exception {
ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = load.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().getClass());
}
}
}
SPI_JDBCTest为启动类,会先加载,其又依赖到了Driver类和ServiceLoader类,会尝试加载
添加虚拟机参数 -XX:+TraceClassLoading后,查看类加载顺序,
SPI_JDBCTest > java.lang.Driver > java.util.ServiceLoader > com.mysql.cj.jdbc.Driver
调用ServiceLoader.load(Driver.class)方法,并不会直接加载com.mysql.cj.jdbc.Driver类,而是创建了一个ServiceLoader对象并返回
ServiceLoader构造函数:
reload方法,先清空服务缓冲区,即成员变量
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
再创建懒加载迭代器
通过debug,可以看到,调用iterator.hasNext()后,会进入到ServiceLoader内部类LazyIterator中的hasNextService()方法
应用类加载器会搜索classpath下jar 中META-INF/services/目录
查找java.sql.Driver文件,将要加载的驱动名称保存下来
在调用iterator.next()后,进入nextService()方法,可以看到,使用之前保存的全限定名加载驱动类
继续debug,可以看到进入了com.mysql.cj.jdbc.Driver类的静态代码块中,即进行类的初始化,之后程序结束
对于jdbc4.0以前,加载驱动需要显式指定,使用代码
Class.forName("com.mysql.cj.jdbc.Driver");
可以使用系统类加载器将com.mysql.cj.jdbc.Driver类加载进内存
以后,使用jdbc不用再显式使用此代码,直接获取连接即可使用,底层会使用ServiceLoader
在DriverManager的静态代码块中,loadInitialDrivers()方法会通过ServiceLoader类加载驱动
driversIterator.next()方法会进入到ServiceLoader内部类LazyIterator中的hasNextService()方法,加载驱动