Java SPI机制分析
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
SPI概述
SPI全稱為(Service Provider Interface) ,是JDK內(nèi)置的一種服務提供發(fā)現(xiàn)機制;主要被框架的開發(fā)人員使用,比如java.sql.Driver接口,數(shù)據(jù)庫廠商實現(xiàn)此接口即可,當然要想讓系統(tǒng)知道具體實現(xiàn)類的存在,還需要使用固定的存放規(guī)則,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個以服務接口命名的文件,這個文件里的內(nèi)容就是這個接口的具體的實現(xiàn)類;下面以JDBC為實例來進行具體的分析。
JDBC驅動
1.準備驅動包
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.2.2</version></dependency><dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>7.0.0.jre8</version></dependency>分別準備了mysql,postgresql和sqlserver,可以打開jar,發(fā)現(xiàn)每個jar包的META-INF/services/都存在一個java.sql.Driver文件,文件里面存在一個或多個類名,比如mysql:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver提供的每個驅動類占據(jù)一行,解析的時候會按行讀取,具體使用哪個會根據(jù)url來決定;
2.簡單實例
String url = "jdbc:mysql://localhost:3306/db3"; String username = "root"; String password = "root"; String sql = "update travelrecord set name=\'bbb\' where id=1"; Connection con = DriverManager.getConnection(url, username, password);類路徑下存在多個驅動包,具體在使用DriverManager.getConnection應該使用哪個驅動類會解析url來識別,不同的數(shù)據(jù)庫有不同的url前綴;
3.驅動類加載分析
具體META-INF/services/下的驅動類是什么時候加載的,DriverManager有一個靜態(tài)代碼塊:
static {loadInitialDrivers();println("JDBC DriverManager initialized"); }private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}// 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() {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.*/try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + 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);}} }在加載DriverManager類的時候會執(zhí)行l(wèi)oadInitialDrivers方法,方法內(nèi)通過了兩種加載驅動類的方式,分別是:使用系統(tǒng)變量方式和ServiceLoader加載方式;系統(tǒng)變量方式其實就是在變量jdbc.drivers中配置好驅動類,然后使用Class.forName進行加載;下面重點看一下ServiceLoader方式,此處調(diào)用了load方法但是并沒有真正去加載驅動類,而是返回了一個LazyIterator,后面的代碼就是循環(huán)變量迭代器:
private static final String PREFIX = "META-INF/services/";private class LazyIteratorimplements Iterator<S>{Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;Iterator<String> pending = null;String nextName = null;private LazyIterator(Class<S> service, ClassLoader loader) {this.service = service;this.loader = loader;}private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}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}......}類中指定了一個靜態(tài)常量PREFIX = “META-INF/services/”,然后和java.sql.Driver拼接組成了fullName,然后通過類加載器去獲取所有類路徑下java.sql.Driver文件,獲取之后存放在configs中,里面的每個元素對應一個文件,每個文件中可能會存在多個驅動類,所以使用pending用來存放每個文件中的驅動信息,獲取驅動信息之后在nextService中使用Class.forName加載類信息,并且指定不進行初始化;同時在下面使用newInstance對驅動類進行了實例化操作;每個驅動類中都提供了一個靜態(tài)注冊代碼塊,比如mysql:
static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");} }這里又實例化了一個驅動類,同時注冊到DriverManager;接下來就是調(diào)用DriverManager的getConnection方法,代碼如下:
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** When callerCl is null, we should check the application's* (which is invoking this class indirectly)* classloader, so that the JDBC driver class outside rt.jar* can be loaded from here.*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.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());}}// if we got here nobody could connect.if (reason != null) {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}此方法主要是遍歷之前注冊的DriverInfo,拿著url信息去每個驅動類中建立連接,當然每個驅動類中都會進行url匹配校驗,成功之后返回Connection,如果中途有失敗的連接并不影響嘗試新的驅動連接,遍歷完之后還是無法獲取連接,則拋出異常;
4.擴展
如果想擴展新的驅動類也很簡單,只需要在類路徑下創(chuàng)建META-INF/services/文件夾,同時在里面創(chuàng)建java.sql.Driver文件,在文件中寫入具體的驅動類名稱,當然此類需要繼承java.sql.Driver接口類;例如實例中提供的TestDriver。
序列化實戰(zhàn)
1.準備接口類
public interface Serialization {/*** 序列化* * @param obj* @return*/public byte[] serialize(Object obj) throws Exception;/*** 反序列化* * @param param* @param clazz* @return* @throws Exception*/public <T> T deserialize(byte[] param, Class<T> clazz) throws Exception;/*** 序列化名稱* * @return*/public String getName();}2.準備實現(xiàn)類
分別準備JsonSerialization和ProtobufSerialization
3.接口文件
在META-INF/services/目錄下創(chuàng)建文件com.spi.serializer.Serialization,內(nèi)容如下:
com.spi.serializer.JsonSerialization com.spi.serializer.ProtobufSerialization4.提供Manager類
public class SerializationManager {private static Map<String, Serialization> map = new HashMap<>();static {loadInitialSerializer();}private static void loadInitialSerializer() {ServiceLoader<Serialization> loadedSerializations = ServiceLoader.load(Serialization.class);Iterator<Serialization> iterator = loadedSerializations.iterator();try {while (iterator.hasNext()) {Serialization serialization = iterator.next();map.put(serialization.getName(), serialization);}} catch (Throwable t) {t.printStackTrace();}}public static Serialization getSerialization(String name) {return map.get(name);} }提供類似DriverManager的SerializationManager類,在加載類的時候加載所有配置的序列化方式;提供一個getSerialization的今天方法類似getConnection;
總結
本文以JDBC驅動為實例,重點對使用ServiceLoader方式服務發(fā)現(xiàn)進行分析,同時提供了序列化的簡單實戰(zhàn);dubbo也提供了類似的SPI方式,核心類是ExtensionLoader,比起java官方提供的ServiceLoader功能更強大,后續(xù)繼續(xù)分析一下dubbo的SPI方式,然后進行一個對比。
示例代碼地址
https://github.com/ksfzhaohui...
https://gitee.com/OutOfMemory...
轉載于:https://my.oschina.net/OutOfMemory/blog/2993335
總結
以上是生活随笔為你收集整理的Java SPI机制分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue-router 手势滑动触发返回
- 下一篇: 工程设计+算法规模化真的是AI突破吗?D