1、SPI機制
SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。
SPI充分體現了面向接口編程的特點。系統內置接口方法,在實際運行中用戶可以自定義實現類來滿足不通的實現需求。
SPI機制在JDK的DriverManager
、Spring
、Dubbo
中得到了充分的利用,Dubbo
中更是擴展了SPI機制來實現組件的可擴展性。
SPI在JDKDriverManager
中的使用
在mysql-connector
和ojdbc
的jar包中,可以發現在META-INF/services
目錄下有一個名爲java.sql.Driver
的文件,在mysql-connector
jar包下,文件內容爲:
com.mysql.cj.jdbc.Driver
這裏就是定義了java.sql.Driver
接口的實現類爲com.mysql.cj.jdbc.Driver
,在java.sql.DriverManager
中,通過java.util.ServiceLoader
來獲取實現類,並實現調用。
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);
}
}
2、Dubbo中的SPI擴展
Dubbo中擴展了ServiceLoader
爲ExtentionLoader
,來加載接口的實現類並維護其生命週期。
定義@SPI
註解來標識擴展點的名稱,表示可以該接口可以被ExtentionLoader
類來加載,接口中的value
值表示默認實現。
定義@Adaptive
註解表示方法是一個自適應方法。在調用時會根據方法的參數來決定調用哪個具體的實現類。
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
文件中配置了該接口的實現類:
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
在調用時通過ExtentionLoader
來獲取實現類:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
3、Spring中接口多實現調用
使用@Qualifier
註解
Spring中@Service
提供了value
屬性,來區分服務名稱。並可以通過@Qualifier
指定注入的服務;
如定義如下接口:
public interface PayService {
void pay();
}
分別有如下實現:
@Service("aliPayService")
public class AliPayService implements PayService{
@Override
public void pay() {
// ...
}
}
@Service("wxPayService")
public class WxPayService implements PayService{
@Override
public void pay() {
// ...
}
}
在調用的時候就可以使用@ Qualifier
指定注入的服務:
@Autowired
@Qualifier("wxPayService")
private PayService payService;
4、使用工廠模式
通過ApplicationContext
的getBeansOfType
獲取接口所有實現類並放入容器中,在調用時動態獲取實現類;
如定義如下接口:
public interface RemoteLockerService {
/
獲取鎖設備廠商
@return 鎖設備廠商
/
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);
}
}
調用時:
RemoteLockerService remoteLockerService = RemoteLockerServiceFactory.getRemoteLockerService(locker.getManufacturer());
remoteLockerService.unLock(identify);