一:SPI簡單介紹
最近在網上看到了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/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。
二:SPI舉例
示例工程結構如下:
具體代碼如下:
服務接口:
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<TestSpi> service = ServiceLoader.load(TestSpi.class);
Iterator<TestSpi> its = service.iterator();
while (its.hasNext()) {
TestSpi operation = its.next();
System.out.println(operation.sayHello());
}
}
}
執行結果:
說明實現類的確執行了
三:JDBC中如何使用SPI
mysql數據庫實例中有一個庫:test,其中有一個表:user
使用jdbc連接代碼如下:
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<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
loadInitialDrivers();方法如下:
private static void loadInitialDrivers() {
// 讀取jvm的系統變量:jdbc.drivers,驅動的字符串名稱
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// SPI自動加載驅動的關鍵代碼
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
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;
}
});
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<Void>() {
public Void run() {
// 加載驅動前的準備:存儲驅動的緩存以及迭代器
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;
}
});
在ServiceLoader類中可以看到以下變量,加載服務的路徑,後面會用到
ServiceLoader.load(Driver.class);中主要做了以下事情:
- 將Driver.class和系統類加載器初始化到以下迭代器中
// The current lazy-lookup iterator
private LazyIterator lookupIterator; - 清空以下緩存(存放驅動)
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> 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<S> iterator() {
return new Iterator<S>() {
// 初始驅動緩存肯定是空的
Iterator<Map.Entry<String,S>> 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<Boolean> action = new PrivilegedAction<Boolean>() {
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<String,S> providers = new LinkedHashMap<>();