Java中的SPI机制及接口多实现调用

文章目录

    • @[toc]
  • Java中的SPI机制及接口多实现调用
    • 0x00 SPI机制
    • 0x01 Dubbo中的SPI扩展
    • 0x02 Spring中接口多实现调用
      • 使用@Qualifier注解
      • 使用工厂模式

Java中的SPI机制及接口多实现调用

0x00 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);
            }
        }

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

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;

使用工厂模式

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

你可能感兴趣的:(Java)