Java中的SPI机制及接口多实现调用
0x00 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() {
public Void run() {
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator 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
,来加载接口的实现类并维护其生命周期。
定义@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")
Invoker select(List> 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);
0x02 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;
使用工厂模式
通过ApplicationContext
的getBeansOfType
获取接口所有实现类并放入容器中,在调用时动态获取实现类;
如定义如下接口:
public interface RemoteLockerService {
/**
* 获取锁设备厂商
*
* @return 锁设备厂商
*/
LockerManufacturerEnum getLockerManufacturer();
/**
* 解锁
*
* @param identify 锁唯一标识
*/
void unLock(String identify);
}
注入容器:
@Component
public class RemoteLockerServiceFactory implements ApplicationContextAware {
private static Map lockerServiceMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
lockerServiceMap = new HashMap<>();
Map map = applicationContext
.getBeansOfType(RemoteLockerService.class);
map.forEach((key, value) -> lockerServiceMap.put(value.getLockerManufacturer(), value));
}
public static T getRemoteLockerService(
LockerManufacturerEnum lockerManufacturer) {
return (T) lockerServiceMap.get(lockerManufacturer);
}
}
调用时:
RemoteLockerService remoteLockerService = RemoteLockerServiceFactory.getRemoteLockerService(locker.getManufacturer());
remoteLockerService.unLock(identify);