您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中的SPI機制是什么”,在日常操作中,相信很多人在Java中的SPI機制是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中的SPI機制是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
SPI
全稱是 Service Provider Interface
,是一種 JDK
內置的動態加載實現擴展點的機制,通過 SPI
技術我們可以動態獲取接口的實現類,不用自己來創建。這個不是什么特別的技術,只是 一種設計理念。
Java SPI 實際上是基于接口的編程+策略模式+配置文件組合實現的動態加載機制。
系統設計的各個抽象,往往有很多不同的實現方案,在面向的對象的設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。
Java SPI就是提供這樣的一個機制:為某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。所以SPI的核心思想就是解耦。
調用者根據實際使用需要 啟用、擴展、或者替換框架的實現策略
下面是一些使用了該機制的場景
JDBC驅動,加載不同數據庫的驅動類
Spring中大量使用了SPI,比如:對servlet3.0規范對ServletContainerInitializer的實現、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等
Dubbo中也大量使用SPI的方式實現框架的擴展, 不過它對Java提供的原生SPI做了封裝,允許用戶擴展實現Filter接口
Tomcat 加載 META-INF/services下找需要加載的類
SpringBoot項目中 使用@SpringBootApplication注解時,會開始自動配置,而啟動配置則會去掃描META-INF/spring.factories下的配置類
4.1 應用程序調用ServiceLoader.load方法
ServiceLoader.load方法內先創建一個新的ServiceLoader,并實例化該類中的成員變量
private static final String PREFIX = "META-INF/services/"; private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } /** * * 在調用該方法之后,迭代器方法的后續調用將延遲地從頭開始查找和實例化提供程序,就像新創建的加載程序所做的 那樣 */ public void reload() { providers.clear(); //清除此加載程序的提供程序緩存,以便重新加載所有提供程序。 lookupIterator = new LazyIterator(service, loader); } private class LazyIterator implements Iterator<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //找到配置文件 String fullName = PREFIX + service.getName(); //加載配置文件中的內容 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = 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 }
步驟1 新建以下類
public interface IService { /** * 獲取價格 * @return */ String getPrice(); /** * 獲取規格信息 * @return */ String getSpecifications(); }
public class GoodServiceImpl implements IService { @Override public String getPrice() { return "2000.00元"; } @Override public String getSpecifications() { return "200g/件"; } }
public class MedicalServiceImpl implements IService { @Override public String getPrice() { return "3022.12元"; } @Override public String getSpecifications() { return "30粒/盒"; } }
步驟2、在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 org.example.IService.txt 。內容是要應用的實現類,我這邊需要放入的數據如下
org.example.GoodServiceImpl
org.example.MedicalServiceImpl
步驟3、使用 ServiceLoader 來加載配置文件中指定的實現。
public class Main { public static void main(String[] args) { final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class); serviceLoader.forEach(service -> { System.out.println(service.getPrice() + "=" + service.getSpecifications()); }); } }
輸出:
2000.00元=200g/件
3022.12元=30粒/盒
解耦 使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起,應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。相比使用提供接口jar包,供第三方服務模塊實現接口的方式,SPI的方式使得源框架,不必關心接口的實現類的路徑
雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。如果你并不想用某些實現類,它也被加載并實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類
多個并發多線程使用ServiceLoader類的實例是不安全的
到此,關于“Java中的SPI機制是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。