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

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349