使用jdk的spi机制实现接口的扩展和解耦

0、背景

总所周知,SPI在很多地方都有着很好的实践,比如JDBC驱动的加载、dubbo等,SpringBoot项目的autoConfiguration也是类似的原理。这里就感觉用来做模块解耦也不错。比如我们做应用管理的一个服务,它具有应用安装、升级、扩容等功能,经常在安装、升级、扩容流程中应用要求做一些定制化的东西,比如安装数据库,需要初始化database;安装kafka,需要初始化topic;对kafka的消费者进行扩容,需要同步扩topic的分区等等。而对于做应用管理的服务来说,它不应该区别化的对待这些应用,否则随着时间的发展,这个服务就会变得越来越复杂,难以与上层应用解耦。

这时候我们可以使用SPI的机制,为每一个支持扩展的流程定义一个扩展接口,交给上层应用去实现,使用SPI来加载这些实现类,从而实现让应用在安装、升级、扩容流程中加入自己个性化的功能。
对于应用管理服务和应用自身都有好处:

  • 于应用管理服务而言,只需要维护扩展接口,无需理解应用的逻辑,无需维护应用的个性化定制功能
  • 于上层应用而言,无需理解应用管理服务的逻辑,需要扩展就自己去实现接口,自己维护定制化的流程代码,当不需要的定制时候直接删除实现即可。

1、先玩一玩SPI吧

1.1 定义一个接口

新建一个module,名为service-spi,新增如下的接口:

package com.example.service.spi;

public interface MyServiceSpi {
    int order();
    void service();
}

这个接口中有两个方法,service是让应用自己去实现的扩展,order用来控制多个实现类的顺序,万一多个应用之间有顺序要求呢,可用这个order来控制。

1.2 定义第一个实现类

新建一个module名为service-impl-one,pom.xml中引用service-spi,新增实现类:

package com.example.service.imp.one;

import com.example.service.spi.MyServiceSpi;

public class ServiceImpleOne implements MyServiceSpi {

    public int order() {
        return 0;
    }

    public void service() {
        System.out.println("my service one");
    }
}

在resources中增加文件夹META-INF/services,新建一个名为com.example.service.spi.MyServiceSpi的文件,与接口的全路径一致
文件内容为com.example.service.imp.one.ServiceImpleOne

1.3 定义第二个实现类

新建一个module名为service-impl-two,pom.xml中引用service-spi,新增实现类:

package com.example.service.imp.two;

import com.example.service.spi.MyServiceSpi;

public class ServiceImplTwo implements MyServiceSpi {
    public int order() {
        return 1;
    }

    public void service() {
        System.out.println("my service two");
    }
}

在resources中增加文件夹META-INF/services,新建一个名为com.example.service.spi.MyServiceSpi的文件,与接口的全路径一致
文件内容为com.example.service.imp.two.ServiceImplTwo

1.4 调用MyServiceSpi

调用的module引用

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>service-impl-one</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>service-imp-two</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

通过ServiceLoader获取所有实现类并排序后依次调用

        ServiceLoader<MyServiceSpi> loader = ServiceLoader.load(MyServiceSpi.class);
        Iterator<MyServiceSpi> iterator = loader.iterator();
        List<MyServiceSpi> myServiceSpiList = new ArrayList<>();
        while(iterator.hasNext()){
            myServiceSpiList.add(iterator.next());
        }
        // 根据order进行排序,order值小的先执行
        Collections.sort(myServiceSpiList, new Comparator<MyServiceSpi>() {
            @Override
            public int compare(MyServiceSpi o1, MyServiceSpi o2) {
                return o1.order()- o2.order();
            }
        });
        // 串行调用所有实现类
        for (MyServiceSpi serviceSpi:myServiceSpiList) {
            serviceSpi.service();
        }

运行后结果
my service one
my service two

2、思考

  • 调用扩展接口地方是不需要关心接口到底有没有实现类,有多少个实现类,这达到我们想要的模块之间解耦。
  • 但是,发现没有,提供接口的jar要和实现的jar跑在同一个jvm里面,这就意味着要么提前把扩展jar包全部打到我们的应用管理服务中,要么支持在单独安装或者升级时动态加载扩展jar,然后reload重新加载实现类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值