Java拾遺(一)之SPI機制
例子:
IOperation plus = new PlusOperationImpl();
IOperation division = new DivisionOperationImpl();
System.out.println(plus.operation(6, 3));//加法
System.out.println(division.operation(6, 3));//除法
通常我們要定義一個四則運算介面IOperation,然後會寫他的實現類PlusOperationImpl,DivisionOperationImpl。
然後在各自的實現類中先實現介面,實現相應的方法。
但是,java設計來一波很sao的操作。
再看另外一個例子:
Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "123"; Connection conn = DriverManager.getConnection(url,username,password);
上面是jdbc獲取連線的程式碼,很平常。但是我們不好奇它是怎麼實現的嗎?
首先是第一行載入類,所以看看com.mysql.jdbc.Drver
package com.mysql.jdbc; static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
他有一個靜態程式碼塊,我們都知道類在載入的時候是會執行靜態程式碼塊的,他new了一個Driver物件,術語叫註冊驅動,說人話就是把新建的物件放到記憶體中去了後面來使用。最終它會存到java.sql.DriverManager類中的CopyOnWriteArrayList<DriverInfo> registeredDrivers屬性裡。後面會迭代這個list。
第一步事情做完了,下面就是DriverManager.getConnection這個方法獲取連線了,
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
然後又呼叫getConnection方法:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
......//
for(DriverInfo aDriver : registeredDrivers) {
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());
}
}
......///
}
去掉一些,只看關鍵部分 Connection con = aDriver.driver.connect(url, info);這個才是獲取連線的
然後你會發現,他丫的aDriver.driver是個介面,然鵝它就這麼呼叫了。真是會玩,介面也能調。
當然介面肯定不能直接拿來用,上面肯定藏著它的實現類,點進DriverInfo你會發現它它好像內部類,因為它所在檔案的類名叫java.sql.DriverManager,仔細看才發現他丫的公用了一個檔案才不是什麼內部類,也就是隻要是同一個包裡面就可以new了。
如果你在這行打個斷點,debug的時候會發現aDriver.driver不是介面了,已經被初始化了。
原理就是這個類,java.util.ServiceLoader。你要問我,我怎麼知道是在這個類裡面。祕訣就是debug,driver的初始化時在這個類DriverInfo裡面,而這個類就一個構造方法。咱就在這個構造方法打個斷點,果然執行了,然後你在一步步debug(真是很暈,你會N多個類)
最終的最終你會到達ServiceLoader它定義了一個變數
private static final String PREFIX = "META-INF/services/";
而且還有值了。
然後去mysql的jar包下面/META-INF/services/java.sql.Driver檔案裡面內容:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
看到這,後面就不看程式碼了。
總結一下:
所謂的SPI機制,就是說把介面和實現分離(通常不在一個jar包裡)
也就是寫介面的人和寫實現類的人不是同一個。
然後介面的提供方會獲取對應的實現,呼叫對應的方法。而具體的實現還是在實現類裡面
一方提供規範(介面)和邏輯(具體呼叫哪些方法),另一方根據規範實現相應介面和方法。呼叫提供方給的靜態方法即可