Java中的SPI機制及接口多實現(xiàn)調(diào)用
0x00 SPI機制
SPI 全稱為 (Service Provider Interface) 失息,是JDK內(nèi)置的一種服務提供發(fā)現(xiàn)機制芳绩。
SPI充分體現(xiàn)了面向接口編程的特點路召。系統(tǒng)內(nèi)置接口方法偷线,在實際運行中用戶可以自定義實現(xiàn)類來滿足不通的實現(xiàn)需求柱嫌。
SPI機制在JDK的DriverManager
历帚、Spring
滔岳、Dubbo
中得到了充分的利用,Dubbo
中更是擴展了SPI機制來實現(xiàn)組件的可擴展性挽牢。
SPI在JDKDriverManager
中的使用
在mysql-connector
和ojdbc
的jar包中谱煤,可以發(fā)現(xiàn)在META-INF/services
目錄下有一個名為java.sql.Driver
的文件,在mysql-connector
jar包下禽拔,文件內(nèi)容為:
com.mysql.cj.jdbc.Driver
這里就是定義了java.sql.Driver
接口的實現(xiàn)類為com.mysql.cj.jdbc.Driver
刘离,在java.sql.DriverManager
中,通過java.util.ServiceLoader
來獲取實現(xiàn)類睹栖,并實現(xiàn)調(diào)用硫惕。
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);
}
}
0x01 Dubbo中的SPI擴展
Dubbo中擴展了ServiceLoader
為ExtentionLoader
,來加載接口的實現(xiàn)類并維護其生命周期野来。
定義@SPI
注解來標識擴展點的名稱恼除,表示可以該接口可以被ExtentionLoader
類來加載,接口中的value
值表示默認實現(xiàn)曼氛。
定義@Adaptive
注解表示方法是一個自適應方法豁辉。在調(diào)用時會根據(jù)方法的參數(shù)來決定調(diào)用哪個具體的實現(xiàn)類令野。
Dubbo也擴展了Java SPI的目錄。Dubbo會從以下目錄中讀取擴展配置信息:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
如LoadBalance接口:
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
這里RandomLoadBalance.NAME
的值為random
徽级,在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance
文件中配置了該接口的實現(xiàn)類:
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
在調(diào)用時通過ExtentionLoader
來獲取實現(xiàn)類:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
0x02 Spring中接口多實現(xiàn)調(diào)用
使用@Qualifier
注解
Spring中@Service
提供了value
屬性彩掐,來區(qū)分服務名稱。并可以通過@Qualifier
指定注入的服務灰追;
如定義如下接口:
public interface PayService {
void pay();
}
分別有如下實現(xiàn):
@Service("aliPayService")
public class AliPayService implements PayService{
@Override
public void pay() {
// ...
}
}
@Service("wxPayService")
public class WxPayService implements PayService{
@Override
public void pay() {
// ...
}
}
在調(diào)用的時候就可以使用@ Qualifier
指定注入的服務:
@Autowired
@Qualifier("wxPayService")
private PayService payService;
使用工廠模式
通過ApplicationContext
的getBeansOfType
獲取接口所有實現(xiàn)類并放入容器中堵幽,在調(diào)用時動態(tài)獲取實現(xiàn)類;
如定義如下接口:
public interface RemoteLockerService {
/**
* 獲取鎖設(shè)備廠商
*
* @return 鎖設(shè)備廠商
*/
LockerManufacturerEnum getLockerManufacturer();
/**
* 解鎖
*
* @param identify 鎖唯一標識
*/
void unLock(String identify);
}
注入容器:
@Component
public class RemoteLockerServiceFactory implements ApplicationContextAware {
private static Map<LockerManufacturerEnum, RemoteLockerService> lockerServiceMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
lockerServiceMap = new HashMap<>();
Map<String, RemoteLockerService> map = applicationContext
.getBeansOfType(RemoteLockerService.class);
map.forEach((key, value) -> lockerServiceMap.put(value.getLockerManufacturer(), value));
}
public static <T extends RemoteLockerService> T getRemoteLockerService(
LockerManufacturerEnum lockerManufacturer) {
return (T) lockerServiceMap.get(lockerManufacturer);
}
}
調(diào)用時:
RemoteLockerService remoteLockerService = RemoteLockerServiceFactory.getRemoteLockerService(locker.getManufacturer());
remoteLockerService.unLock(identify);