深入理解java SPI机制
What?
SPI機制(Service Provider Interface)其實源自服務(wù)提供者框架(Service Provider Framework,參考【EffectiveJava】page6),是一種將服務(wù)接口與服務(wù)實現(xiàn)分離以達(dá)到解耦、大大提升了程序可擴展性的機制。引入服務(wù)提供者就是引入了spi接口的實現(xiàn)者,通過本地的注冊發(fā)現(xiàn)獲取到具體的實現(xiàn)類,輕松可插拔
典型實例:jdbc的設(shè)計
通常各大廠商(如Mysql、Oracle)會根據(jù)一個統(tǒng)一的規(guī)范(java.sql.Driver)開發(fā)各自的驅(qū)動實現(xiàn)邏輯。客戶端使用jdbc時不需要去改變代碼,直接引入不同的spi接口服務(wù)即可。
Mysql的則是com.mysql.jdbc.Drive,Oracle則是oracle.jdbc.driver.OracleDriver。
偽代碼如下:
?? ?//注:從jdbc4.0之后無需這個操作,spi機制會自動找到相關(guān)的驅(qū)動實現(xiàn)
?? ?//Class.forName(driver);
?? ?
?? ?//1.getConnection()方法,連接MySQL數(shù)據(jù)庫。有可能注冊了多個Driver,這里通過遍歷成功連接后返回。
?? ?con = DriverManager.getConnection(mysqlUrl,user,password);
?? ?//2.創(chuàng)建statement類對象,用來執(zhí)行SQL語句!!
?? ?Statement statement = con.createStatement();
?? ?//3.ResultSet類,用來存放獲取的結(jié)果集!!
?? ?ResultSet rs = statement.executeQuery(sql);
1
2
3
4
5
6
7
8
9
jdbc連接源碼分析
1. java.sql.DriverManager靜態(tài)塊初始執(zhí)行,其中使用spi機制加載jdbc具體實現(xiàn)
?//java.sql.DriverManager.java ??
?//當(dāng)調(diào)用DriverManager.getConnection(..)時,static會在getConnection(..)執(zhí)行之前被觸發(fā)執(zhí)行
? ? /**
? ? ?* 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");
? ? }
1
2
3
4
5
6
7
8
9
10
2.loadInitialDrivers()中完成了引入的數(shù)據(jù)庫驅(qū)動的查找以及載入,本示例只引入了oracle廠商的mysql,我們具體看看。
//java.util.serviceLoader.java
? ?private static void loadInitialDrivers() {
? ? ? ? String drivers;
? ? ? ? try {
? ? ? ? ? ? drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
? ? ? ? ? ? ? ? public String run() {
? ? ? ? ? ? ? ? //使用系統(tǒng)變量方式加載
? ? ? ? ? ? ? ? ? ? return System.getProperty("jdbc.drivers");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? } catch (Exception ex) {
? ? ? ? ? ? drivers = null;
? ? ? ? }
? ? ? ? //如果spi 存在將使用spi方式完成提供的Driver的加載
? ? ? ? // If the driver is packaged as a Service Provider, load it.
? ? ? ? // Get all the drivers through the classloader
? ? ? ? // exposed as a java.sql.Driver.class service.
? ? ? ? // ServiceLoader.load() replaces the sun.misc.Providers()
? ? ? ? AccessController.doPrivileged(new PrivilegedAction<Void>() {
? ? ? ? ? ? public Void run() {
//查找具體的provider,就是在META-INF/services/***.Driver文件中查找具體的實現(xiàn)。
? ? ? ? ? ? ? ? ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
? ? ? ? ? ? ? ? Iterator<Driver> driversIterator = loadedDrivers.iterator();
? ? ? ? ? ? ? ? /* Load these drivers, so that they can be instantiated.
? ? ? ? ? ? ? ? ?* It may be the case that the driver class may not be there
? ? ? ? ? ? ? ? ?* i.e. there may be a packaged driver with the service class
? ? ? ? ? ? ? ? ?* as implementation of java.sql.Driver but the actual class
? ? ? ? ? ? ? ? ?* may be missing. In that case a java.util.ServiceConfigurationError
? ? ? ? ? ? ? ? ?* will be thrown at runtime by the VM trying to locate
? ? ? ? ? ? ? ? ?* and load the service.
? ? ? ? ? ? ? ? ?*
? ? ? ? ? ? ? ? ?* Adding a try catch block to catch those runtime errors
? ? ? ? ? ? ? ? ?* if driver not available in classpath but it's
? ? ? ? ? ? ? ? ?* packaged as service and that service is there in classpath.
? ? ? ? ? ? ? ? ?*/
? ? ? ? ? ? ? ? ?//查找具體的實現(xiàn)類的全限定名稱
? ? ? ? ? ? ? ? try{
? ? ? ? ? ? ? ? ? ? while(driversIterator.hasNext()) {
? ? ? ? ? ? ? ? ? ? ? ? driversIterator.next();//加載并初始化實現(xiàn)類
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } catch(Throwable t) {
? ? ? ? ? ? ? ? // Do nothing
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return null;
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? println("DriverManager.initialize: jdbc.drivers = " + drivers);
? ? ? ? if (drivers == null || drivers.equals("")) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? String[] driversList = drivers.split(":");
....
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
3.java.util.ServiceLoader 加載spi實現(xiàn)類.
上一步的核心代碼如下,我們接著分析:
//java.util.serviceLoader.java
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
? //查找具體的實現(xiàn)類的全限定名稱
? ? ?while(driversIterator.hasNext()) {
? ? ?//加載并初始化實現(xiàn)
? ? ? ? ?driversIterator.next();
? ? ?}
?} catch(Throwable t) {
?// Do nothing
?}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
主要是通過ServiceLoader來完成的,我們按照執(zhí)行順序來看看ServiceLoader實現(xiàn):
//初始化一個ServiceLoader,load參數(shù)分別是需要加載的接口class對象,當(dāng)前類加載器
? ? 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);
? ? }
1
2
3
4
5
6
7
8
9
10
遍歷所有存在的service實現(xiàn)
? ? ? ? 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);
? ? ? ? ? ? }
? ? ? ? }
1
2
3
4
5
6
7
8
9
10
?? ?//寫死的一個目錄
? ? ? ?private static final String PREFIX = "META-INF/services/";
? ? ? ?private boolean hasNextService() {
? ? ? ? ? ? if (nextName != null) {
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? if (configs == null) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? String fullName = PREFIX + service.getName();
? ? ? ? ? ? ? ? ? ? //通過相對路徑讀取classpath中META-INF目錄的文件,也就是讀取服務(wù)提供者的實現(xiàn)類全限定名
? ? ? ? ? ? ? ? ? ? if (loader == null)
? ? ? ? ? ? ? ? ? ? ? ? configs = ClassLoader.getSystemResources(fullName);
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? configs = loader.getResources(fullName);
? ? ? ? ? ? ? ? } catch (IOException x) {
? ? ? ? ? ? ? ? ? ? fail(service, "Error locating configuration files", x);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? //判斷是否讀取到實現(xiàn)類全限定名,比如mysql的“com.mysql.jdbc.Driver
”
? ? ? ? ? ? while ((pending == null) || !pending.hasNext()) {
? ? ? ? ? ? ? ? if (!configs.hasMoreElements()) {
? ? ? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? pending = parse(service, configs.nextElement());
? ? ? ? ? ? }
? ? ? ? ? ? nextName = pending.next();//nextName保存,后續(xù)初始化實現(xiàn)類使用
? ? ? ? ? ? return true;//查到了 返回true,接著調(diào)用next()
? ? ? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
? ? ? ? public S next() {
? ? ? ? ? ? if (acc == null) {//用來判斷serviceLoader對象是否完成初始化
? ? ? ? ? ? ? ? return nextService();
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? PrivilegedAction<S> action = new PrivilegedAction<S>() {
? ? ? ? ? ? ? ? ? ? public S run() { return nextService(); }
? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? return AccessController.doPrivileged(action, acc);
? ? ? ? ? ? }
? ? ? ? }
? ? ? private S nextService() {
? ? ? ? ? ? if (!hasNextService())
? ? ? ? ? ? ? ? throw new NoSuchElementException();
? ? ? ? ? ? String cn = nextName;//上一步找到的服務(wù)實現(xiàn)者全限定名
? ? ? ? ? ? nextName = null;
? ? ? ? ? ? Class<?> c = null;
? ? ? ? ? ? try {
? ? ? ? ? ? //加載字節(jié)碼返回class對象.但并不去初始化(換句話就是說不去執(zhí)行這個類中的static塊與static變量初始化)
? ? ? ? ? ? //
? ? ? ? ? ? ? ? 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 {
?? ? ? ? ? ? ? ?//初始化這個實現(xiàn)類.將會通過static塊的方式觸發(fā)實現(xiàn)類注冊到DriverManager(其中組合了一個CopyOnWriteArrayList的registeredDrivers成員變量)中
? ? ? ? ? ? ? ? S p = service.cast(c.newInstance());
? ? ? ? ? ? ? ? providers.put(cn, p);//本地緩存 (全限定名,實現(xiàn)類對象)
? ? ? ? ? ? ? ? return p;
? ? ? ? ? ? } catch (Throwable x) {
? ? ? ? ? ? ? ? fail(service,
? ? ? ? ? ? ? ? ? ? ?"Provider " + cn + " could not be instantiated",
? ? ? ? ? ? ? ? ? ? ?x);
? ? ? ? ? ? }
? ? ? ? ? ? throw new Error(); ? ? ? ? ?// This cannot happen
? ? ? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
上一步中,Sp = service.cast(c.newInstance()) 將會導(dǎo)致具體實現(xiàn)者的初始化,比如mysqlJDBC,會觸發(fā)如下代碼:
//com.mysql.jdbc.Driver.java
......
? ? private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
......
? ? static {
? ? ? ? try {
?? ??? ? ? ? //并發(fā)安全的想一個copyOnWriteList中方
? ? ? ? ? ? java.sql.DriverManager.registerDriver(new Driver());
? ? ? ? } catch (SQLException E) {
? ? ? ? ? ? throw new RuntimeException("Can't register driver!");
? ? ? ? }
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
4.最終Driver全部注冊并初始化完畢,開始執(zhí)行DriverManager.getConnection(url, “root”, “root”)方法并返回。
使用實例
四個項目:spiInterface、spiA、spiB、spiDemo
spiInterface中定義了一個com.zs.IOperation接口。
spiA、spiB均是這個接口的實現(xiàn)類,服務(wù)提供者。
spiDemo作為客戶端,引入spiA或者spiB依賴,面向接口編程,通過spi的方式獲取具體實現(xiàn)者并執(zhí)行接口方法。
├─spiA
│ ?└─src
│ ? ? ?├─main
│ ? ? ?│ ?├─java
│ ? ? ?│ ?│ ?└─com
│ ? ? ?│ ?│ ? ? ?└─zs
│ ? ? ?│ ?├─resources
│ ? ? ?│ ?│ ?└─META-INF
│ ? ? ?│ ?│ ? ? ?└─services
│ ? ? ?│ ?└─webapp
│ ? ? ?│ ? ? ?└─WEB-INF
│ ? ? ?└─test
│ ? ? ? ? ?└─java
├─spiB
│ ?└─src
│ ? ? ?├─main
│ ? ? ?│ ?├─java
│ ? ? ?│ ?│ ?└─com
│ ? ? ?│ ?│ ? ? ?└─zs
│ ? ? ?│ ?├─resources
│ ? ? ?│ ?│ ?└─META-INF
│ ? ? ?│ ?│ ? ? ?└─services
│ ? ? ?│ ?└─webapp
│ ? ? ?│ ? ? ?└─WEB-INF
│ ? ? ?└─test
│ ? ? ? ? ?└─java
├─spiDemo
│ ?└─src
│ ? ? ?├─main
│ ? ? ?│ ?├─java
│ ? ? ?│ ?│ ?└─com
│ ? ? ?│ ?│ ? ? ?└─zs
│ ? ? ?│ ?├─resources
│ ? ? ?│ ?└─webapp
│ ? ? ?│ ? ? ?└─WEB-INF
│ ? ? ?└─test
│ ? ? ? ? ?└─java
└─spiInterface
? ? └─src
? ? ? ? ├─main
? ? ? ? │ ?├─java
? ? ? ? │ ?│ ?└─com
? ? ? ? │ ?│ ? ? ?└─zs
? ? ? ? │ ?├─resources
? ? ? ? │ ?└─webapp
? ? ? ? │ ? ? ?└─WEB-INF
? ? ? ? └─test
? ? ? ? ? ? └─java
? ? ? ? ? ? ? ? └─spiInterface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
spiDemo
package com.zs;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Launcher {
?? ?public static void main(String[] args) throws Exception {
//?? ??? ?jdbcTest();
?? ??? ?showSpiPlugins();
?? ??? ?
?? ?}
?? ?private static void jdbcTest() throws SQLException {
?? ??? ?String url = "jdbc:mysql://localhost:3306/test";
?? ??? ?Connection conn = DriverManager.getConnection(url, "root", "root");
?? ??? ?Statement statement = conn.createStatement();
?? ??? ?ResultSet set = statement.executeQuery("select * from test.user");
?? ??? ?while (set.next()) {
?? ??? ??? ?System.out.println(set.getLong("id"));
?? ??? ??? ?System.out.println(set.getString("userName"));
?? ??? ??? ?System.out.println(set.getInt("age"));
?? ??? ?}
?? ?}
?? ?private static void showSpiPlugins() {
?? ??? ?ServiceLoader<IOperation> operations = ServiceLoader.load(IOperation.class);
?? ??? ?Iterator<IOperation> operationIterator = operations.iterator();
?? ??? ?
?? ??? ?while (operationIterator.hasNext()) {
?? ??? ??? ?IOperation operation = operationIterator.next();
?? ??? ??? ?System.out.println(operation.operation(6, 3));
?? ??? ?}
?? ?}
}
SPI示例 完整代碼
---------------------?
作者:lemon的博客?
來源:CSDN?
原文:https://blog.csdn.net/lemon89/article/details/79189475?
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!
總結(jié)
以上是生活随笔為你收集整理的深入理解java SPI机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDK源码分析——Java的SPI机制分
- 下一篇: Java日期时间