一、Java SPI機制
SPI(Service Provider Interface)是JDK內置的服務發現機制,用在不同模塊間通過接口調用服務,避免對具體服務服務接口具體實現類的耦合。比如JDBC的數據庫驅動模塊,不同數據庫連接驅動接口相同但實現類不同,在使用SPI機制以前調用驅動代碼需要直接在類里采用Class.forName(具體實現類全名)的方式調用,這樣調用方依賴了具體的驅動實現,在替換驅動實現時要修改代碼。而采用SPI機制后,在驅動jar包的META-INF/services下面新建一個驅動接口全名的UTF-8編碼的文件,里面寫上具體實現類的全名,這樣調用方通過Java 的ServiceLoad接口動態的去加載接口的實現類,從而達到替換驅動實現不用修改代碼的效果,如下代碼:
public static void main(String[] args) { ServiceLoader<DriverService> serviceLoader = ServiceLoader.load(DriverService.class); for (DriverService driverService: serviceLoader){ System.out.println(driverService.getName()); } }
使用步驟:
1、服務調用方通過ServiceLoader.load加載服務接口的實現類實例;
2、服務提供方實現服務接口后,在自己Jar包的META-INF/services目錄下新建一個接口名全名的文件,並將具體實現類全名寫入。
二、Spring SPI機制
很多開源框架庫里都直接或間接使用了Java 的SPI機制。比如Spring就有類似的SPI機制,通過SpringFactoriesLoader代替JDK中ServiceLoader,通過META-INF/spring.factories文件代替META-INF/service目錄下的描述文件,具體實現步驟不同,但原理都是使用Java 的反射機制。
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames); } List<T> result = new ArrayList<>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; }
spring boot讀取properties文件spring.factories
三、Dubbo的SPI擴展
dubbo的擴展機制和java的SPI機制非常相似,但是又增加了如下功能:
1 可以方便的獲取某一個想要的擴展實現,java的SPI機制就沒有提供這樣的功能
2 對於擴展實現IOC依賴注入功能:
舉例來說:接口A,實現者A1、A2。接口B,實現者B1、B2。
現在實現者A1含有setB()方法,會自動注入一個接口B的實現者,此時注入B1還是B2呢?都不是,而是注入一個動態生成的接口B的實現者B$Adpative,該實現者能夠根據參數的不同,自動引用B1或者B2來完成相應的功能
3 對擴展采用裝飾器模式進行功能增強,類似AOP實現的功能