最近在网上看到了java中SPI这个技术,据了解在JDBC,JNDI,日志门面,Dubbo等很多技术中都有使用,因此决定学习一下.
SPI(service provider interface:服务提供者接口),为接口寻找服务实现类,编程时针对接口编程,由具体的服务提供商提供接口的实现(例如jdbc服务的具体实现可以是oracle,mysql等)
java spi的具体使用如下 :
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过java.util.ServiceLoader解析该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
package com.tdemo.api;
public interface TestSpi
{
String sayHello();
}
接口实现类:
package com.tdemo.api.impl;
import com.tdemo.api.TestSpi;
public class TestSpiImpl implements TestSpi
{
@Override
public String sayHello()
{
return "HELLO";
}
}
META-INF下的services文件夹下:
创建文件以服务接口的全类名命名:
com.tdemo.api.TestSpi
内部内容是接口的实现类全类名:
com.tdemo.api.impl.TestSpiImpl
测试代码:
package com.tdemo.test;
import com.tdemo.api.TestSpi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test
{
public static void main(String[] args)
{
ServiceLoader service = ServiceLoader.load(TestSpi.class);
Iterator its = service.iterator();
while (its.hasNext()) {
TestSpi operation = its.next();
System.out.println(operation.sayHello());
}
}
}
mysql数据库实例中有一个库:test,其中有一个表:user
package com.tdemo.spiofjdbc.test;
import com.tdemo.spiofjdbc.model.User;
import java.sql.*;
public class Test
{
public static void main(String[] args) throws SQLException
{
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement("select * from user where id = 1");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet != null && resultSet.next())
{
int age = resultSet.getInt("age");
System.out.println(age);
}
}
}
注意到连接数据库的这行代码:
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
相对于以前的Class.forname(数据库驱动);简单的多
这里面就用到了SPI的机制,这是JDBC 4.0规范引入的(JDK6),这时候开始可以自动加载jdbc的驱动了
下面从源码的角度理解如何做到的:
DriverManager调用getConnection方法,所有会先加载DriverManager,静态变量初始化,静态代码块会执行:
在DriverManager中有以下静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
静态变量:
private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
loadInitialDrivers();方法如下:
private static void loadInitialDrivers() {
// 读取jvm的系统变量:jdbc.drivers,驱动的字符串名称
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// SPI自动加载驱动的关键代码
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
// 通过Class.forName显示加载jvm系统配置的驱动:jdbc.drivers中的配置,可以配置多个,每个之间使用:分割
if (drivers == null || drivers.equals("")) {
return;
}
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);
}
}
}
先来看看SPI自动加载那段:
// SPI自动加载驱动的关键代码
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// 加载驱动前的准备:存储驱动的缓存以及迭代器
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
// 加载驱动
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
在ServiceLoader类中可以看到以下变量,加载服务的路径,后面会用到
ServiceLoader.load(Driver.class);中主要做了以下事情:
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear(); // 清空当前驱动缓存
lookupIterator = new LazyIterator(service, loader); // 初始化迭代器
}
以上可以说是准备工作,真正加载驱动的代码如下:
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
public Iterator iterator() {
return new Iterator() {
// 初始驱动缓存肯定是空的
Iterator> knownProviders
= providers.entrySet().iterator();
// 判断是否包含下一个驱动
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
// 获取下一个驱动
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
以下代码循环加载驱动:
while(driversIterator.hasNext()) {
driversIterator.next();
}
当执行driversIterator.hasNext()时
// 判断是否包含下一个驱动
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
初始加载时knownProviders.hasNext()为false,所以执行lookupIterator.hasNext()
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
代理到lookupIterator的 hasNextService();这个地方会真正去加载驱动,初始时LazyIterator的nextName为空,configs(驱动名称枚举)为空,所以会执行 String fullName = PREFIX + service.getName();其中PREFIX 在ServiceLoader中:META-INF/services,显然此处fullName = META-INF/services/java.sql.Driver,执行完成后中存储了驱动的全名称,在下面这句
pending = parse(service, configs.nextElement());把所有驱动名加载到ArrayList中,通过迭代器访问
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
回到以下代码:在上面服务实现类名全都通过迭代器访问,hasNext返回true,访问一次,都将类型赋值给nextName,可以看到下文实际还是通过 c = Class.forName(cn, false, loader);来加载,然后创建实例存入缓存:
S p = service.cast(c.newInstance());
providers.put(cn, p);
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
到此可以说驱动加载完成
总的来说:
这行代码初始化了 ServiceLoader内部的迭代器lookupIterator,它使用了适配器模式
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
// 把访问代理到lookupIterator上
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
lookupIterator的hasNext每执行一次就是读取一个驱动名赋值给nextName,并返回true,接下来
lookupIterator的next就在加载每一个nextName对应的驱动,并存入缓存:
// Cached providers, in instantiation order
private LinkedHashMap