前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从模版方法模式到 SPI 演变 :好的思想通用而持久

从模版方法模式到 SPI 演变 :好的思想通用而持久

作者头像
用户2781897
发布2019-10-28 17:32:53
7260
发布2019-10-28 17:32:53
举报
文章被收录于专栏:服务端思维服务端思维

一般情况下,我们会通过 API 对外提供服务。这里,API 提供服务的接口的逻辑是固定的,换句话说,它具有通用性。但是,但我们遇到具有类似的业务逻辑的场景时,即核心的主干逻辑相同,而细节的实现略有不同,那我们该何去何从?很多时候,我们会选择提供多个 API 接口给不同的业务方使用。事实上,我们可以通过 SPI 扩展点来实现的更加优雅。什么是 SPI?SPI 的英文全称是 Serivce Provider Interface,即服务提供者接口,它是一种动态发现机制,可以在程序执行的过程中去动态的发现某个扩展点的实现类。因此,当 API 被调用时会动态加载并调用 SPI 的特定实现方法。

此时,你是不是联想到了模版方法模式。模板方法模式的核心思想是定义骨架,转移实现,换句话说,它通过定义一个流程的框架,而将一些步骤的具体实现延迟到子类中。事实上,在微服务的落地过程中,这种思想也给我们提供了非常好的理论基础。

现在,我们来看一个案例:电商业务场景中的未发货仅退款。这种情况在电商业务中非常场景,用户下单付款后由于各种原因可能就申请退款了。此时,因为不涉及退货,所以只需要用户申请退款并填写退款原因,然后让卖家审核退款。那么,由于不同平台的退款原因可能不同,我们可以考虑通过 SPI 扩展点来实现。

我们先来看下 JDK 对 SPI 机制的支持。在面向对象编程的设计中,我们会采取面向接口编程的方式。

代码语言:javascript
复制
public interface IRefundSeason {
    default List<String> invoke(){
        // 1. 前置业务
        // 。。。
        // 2. 获取退款原因列表
        List<String> refundSeasons = getRefundSeasonList();
        // 3. 后置业务
        // 。。。
        return refundSeasons;
    }

    List<String> getRefundSeasonList();
}

这里,我们在简单地定义两个实现类。

代码语言:javascript
复制
public class RefundSeason1 implements IRefundSeason{

    @Override
    public List<String> getRefundSeasonList() {
        return ImmutableList.of("商品降价","商品无货","其他");
    }
}

public class RefundSeason2 implements IRefundSeason{

    @Override
    public List<String> getRefundSeasonList() {
        return ImmutableList.of("不想要了","七天无理由","其他");
    }
}

服务提供者提供接口的一种实现后,我们需要在 META-INF/services 目录中创建一个以接口全限定名的文件 com.lianggzone.design.template_method.example.spi.IRefundSeason

这里,文件地内容为实现类的全限定名。

代码语言:javascript
复制
com.lianggzone.design.template_method.example.spi.RefundSeason1

最后,我们通过测试代码来验证下功能。

代码语言:javascript
复制
public class RefundSeasonTest {

    public static void main(String[] args) {
        ServiceLoader<IRefundSeason> loader = ServiceLoader.load(IRefundSeason.class);
        loader.forEach(i -> {
            List<String> refundSeasons = i.invoke();
            System.out.println(refundSeasons);
        });
    }
}

至此,我们实现了一个简单地 Java 的 SPI 功能。事实上,Java 中的 SPI 实现非常简单,我们可以阅读 java.util.ServiceLoader 类。

注意的是,ServiceLoader 每次加载都会生成一份实例,且只能遍历获取所有接口实例,非常浪费资源。同时,获取实现类不够灵活,不能根据某个参数获取对应的实现类,且不支持排序,会出现排序不稳定的情况。因此,很多框架为了解决以上的问题,重新实现了一套更强大的 SPI 机制。例如,Dubbo SPI 自定义了一套 SPI 机制,并把所需的配置文件需放置在 META-INF/dubbo 路径下。与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样可以按需加载指定的实现类。Dubbo SPI 源码分析,参见 http://dubbo.apache.org/zh-cn/docs/sourcecodeguide/dubbo-spi.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 服务端思维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档