日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java SPI机制分析

發布時間:2025/6/17 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java SPI机制分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

SPI概述

SPI全稱為(Service Provider Interface) ,是JDK內置的一種服務提供發現機制;主要被框架的開發人員使用,比如java.sql.Driver接口,數據庫廠商實現此接口即可,當然要想讓系統知道具體實現類的存在,還需要使用固定的存放規則,需要在classpath下的META-INF/services/目錄里創建一個以服務接口命名的文件,這個文件里的內容就是這個接口的具體的實現類;下面以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,發現每個jar包的META-INF/services/都存在一個java.sql.Driver文件,文件里面存在一個或多個類名,比如mysql:

com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver

提供的每個驅動類占據一行,解析的時候會按行讀取,具體使用哪個會根據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來識別,不同的數據庫有不同的url前綴;

3.驅動類加載分析

具體META-INF/services/下的驅動類是什么時候加載的,DriverManager有一個靜態代碼塊:

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類的時候會執行loadInitialDrivers方法,方法內通過了兩種加載驅動類的方式,分別是:使用系統變量方式和ServiceLoader加載方式;系統變量方式其實就是在變量jdbc.drivers中配置好驅動類,然后使用Class.forName進行加載;下面重點看一下ServiceLoader方式,此處調用了load方法但是并沒有真正去加載驅動類,而是返回了一個LazyIterator,后面的代碼就是循環變量迭代器:

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}......}

類中指定了一個靜態常量PREFIX = “META-INF/services/”,然后和java.sql.Driver拼接組成了fullName,然后通過類加載器去獲取所有類路徑下java.sql.Driver文件,獲取之后存放在configs中,里面的每個元素對應一個文件,每個文件中可能會存在多個驅動類,所以使用pending用來存放每個文件中的驅動信息,獲取驅動信息之后在nextService中使用Class.forName加載類信息,并且指定不進行初始化;同時在下面使用newInstance對驅動類進行了實例化操作;每個驅動類中都提供了一個靜態注冊代碼塊,比如mysql:

static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");} }

這里又實例化了一個驅動類,同時注冊到DriverManager;接下來就是調用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.擴展

如果想擴展新的驅動類也很簡單,只需要在類路徑下創建META-INF/services/文件夾,同時在里面創建java.sql.Driver文件,在文件中寫入具體的驅動類名稱,當然此類需要繼承java.sql.Driver接口類;例如實例中提供的TestDriver。

序列化實戰

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.準備實現類

分別準備JsonSerialization和ProtobufSerialization

3.接口文件

在META-INF/services/目錄下創建文件com.spi.serializer.Serialization,內容如下:

com.spi.serializer.JsonSerialization com.spi.serializer.ProtobufSerialization

4.提供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方式服務發現進行分析,同時提供了序列化的簡單實戰;dubbo也提供了類似的SPI方式,核心類是ExtensionLoader,比起java官方提供的ServiceLoader功能更強大,后續繼續分析一下dubbo的SPI方式,然后進行一個對比。

示例代碼地址

https://github.com/ksfzhaohui...
https://gitee.com/OutOfMemory...

轉載于:https://my.oschina.net/OutOfMemory/blog/2993335

總結

以上是生活随笔為你收集整理的Java SPI机制分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。