Java中的SPI機制及接口多實現調用

1、SPI機制

SPI 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。

SPI充分體現了面向接口編程的特點。系統內置接口方法,在實際運行中用戶可以自定義實現類來滿足不通的實現需求。

SPI機制在JDK的DriverManagerSpringDubbo中得到了充分的利用,Dubbo中更是擴展了SPI機制來實現組件的可擴展性。

SPI在JDKDriverManager中的使用

mysql-connectorojdbc的jar包中,可以發現在META-INF/services目錄下有一個名爲java.sql.Driver的文件,在mysql-connectorjar包下,文件內容爲:

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中擴展了ServiceLoaderExtentionLoader,來加載接口的實現類並維護其生命週期。

定義@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、使用工廠模式

通過ApplicationContextgetBeansOfType獲取接口所有實現類並放入容器中,在調用時動態獲取實現類;

如定義如下接口:

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);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章