<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
SPI的全名為:Service Provider Interface。在java.util.ServiceLoader的檔案裡有比較詳細的介紹。簡單的總結下 Java SPI 機制的思想。我們系統裡抽象的各個模組,往往有很多不同的實現方案,比如紀錄檔模組的方案,xml解析模組、jdbc模組的方案等。面向的物件的設計裡,我們一般推薦模組之間基於介面程式設計,模組之間不對實現類進行寫死。一旦程式碼裡涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改程式碼。為了實現在模組裝配的時候能不在程式裡動態指明,這就需要一種服務發現機制。
Java SPI 就是提供這樣的一個機制:為某個介面尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模組化設計中這個機制尤其重要Java SPI 的具體約定為:當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的組態檔找到具體的實現類名,並裝載範例化,完成模組的注入。基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類:java.util.ServiceLoader。
定義一個介面:
package com.hiwei.spi.demo; public interface Animal { void speak(); }
建立兩個實現類:
package com.hiwei.spi.demo; public class Cat implements Animal { @Override public void speak() { System.out.println("喵喵喵!"); } }
package com.hiwei.spi.demo; public class Dog implements Animal { @Override public void speak() { System.out.println("汪汪汪!"); } }
在resources目錄下建立META-INF/services目錄:
建立以介面類路徑命名的檔案,檔案中新增實現類路徑:
com.hiwei.spi.demo.Cat
com.hiwei.spi.demo.Dog
使用
package com.hiwei.spi; import com.hiwei.spi.demo.Animal; import java.sql.SQLException; import java.util.ServiceLoader; public class SpiDemoApplication { public static void main(String[] args){ //會根據檔案找到對應的實現類 ServiceLoader<Animal> load = ServiceLoader.load(Animal.class); //執行實現類方法 for (Animal animal : load) { animal.speak(); } } }
執行結果:
上面我們可以看到java spi會幫助我們找到介面實現類。那麼實際生產中怎麼使用呢? 將上面的程式碼打成jar,然後在其它專案中引入,同樣的目錄下建立檔案,並寫上自己實現類的路徑:
本專案實現類:
package com.example.demo; import com.hiwei.spi.demo.Animal; public class Pig implements Animal { @Override public void speak() { System.out.println("哼哼哼!"); } }
程式碼中,我們呼叫jar中的main方法:
package com.example.demo; import com.hiwei.spi.SpiDemoApplication; public class DemoApplication { public static void main(String[] args) { SpiDemoApplication.main(args); } }
執行結果:
可以看見自定義的實現類也被執行了。在實際生產中,我們就可以使用java spi面向介面程式設計,實現可插拔。
以最新的mysql-connector-java-8.0.27.jar為例
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency>
在使用JDBC連線資料庫時,只需要使用:
DriverManager.getConnection("url", "username", "password");
DriverManager有靜態方法:
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
看下loadInitialDrivers()方法,其中有:
AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { //獲取Driver.class的實現類 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; } });
可以看見,會根據java spi獲取Driver.class的實現類,可以在mysql-connector-java-8.0.27.jar下面看到,定義的檔案:
程式會根據檔案找到對應的實現類,並連線資料庫。
sharding-jdbc是一款用於分庫分表的中介軟體,在資料庫分散式場景中,對於主鍵生成要保證唯一性,主鍵生成策略有很多種實現。sharding-jsbc在主鍵生成上就使用了SPI進行擴充套件。
下面看下sharding-jdbc原始碼在主鍵生成上是怎麼應用的: 原始碼中的 ShardingRule.class主要封裝分庫分表的策略規則,包括主鍵生成。看下createDefaultKeyGenerator方法:
//生成預設主鍵生成策略 private ShardingKeyGenerator createDefaultKeyGenerator(final KeyGeneratorConfiguration keyGeneratorConfiguration) { //SPI服務發現 ShardingKeyGeneratorServiceLoader serviceLoader = new ShardingKeyGeneratorServiceLoader(); return containsKeyGeneratorConfiguration(keyGeneratorConfiguration) ? serviceLoader.newService(keyGeneratorConfiguration.getType(), keyGeneratorConfiguration.getProperties()) : serviceLoader.newService(); }
繼續看ShardingKeyGeneratorServiceLoader(),有靜態程式碼塊註冊:
static { //SPI: 載入主鍵生成策略 NewInstanceServiceLoader.register(ShardingKeyGenerator.class); }
看下register方法:
public static <T> void register(final Class<T> service) { //服務發現 for (T each : ServiceLoader.load(service)) { registerServiceClass(service, each); } }
看到這,真相大白,就是應用java spi機制。
我們再看下resources目錄下:
可以看到有對應介面命名的檔案,檔案內容:
有兩個實現,分別是雪花演演算法和UUID,這也對應了sharding-jdbc的提供的兩種生成策略。我們在使用sharding-jdbc時,也可以自定義策略,便於擴充套件。 sharding-jdbc對於SPI的使用點還有很多,這裡就不一一列舉了。對於SPI機制,我們在工作中也可以實際應用,提升程式的可延伸性。
以上是Java SPI的解析。其實SPI機制在很多地方都有用到,只是以不同的形式應用,具體的實現略有不同。例如dubbo中也有類似的spi機制;springboot的自動裝配,也使用了spi機制:
springboot自動裝配:
定義檔案:
檔案中宣告需要發現的類:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.hiwei.valve.ValveAutoConfiguration
springboot的掃描檔案,裝配對應的類:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { //載入檔案中的類 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
FACTORIES_RESOURCE_LOCATION的值:
SPI在Java開發中是個很重要的設計,所以我們一定要熟練掌握。
到此這篇關於Java深入講解SPI的使用的文章就介紹到這了,更多相關Java SPI內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45