专栏首页服务端思维从模版方法模式到 SPI 演变 :好的思想通用而持久

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

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

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

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

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

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

    List<String> getRefundSeasonList();
}

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

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

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

com.lianggzone.design.template_method.example.spi.RefundSeason1

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

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

本文分享自微信公众号 - 服务端思维(gh_c3775931ac9d),作者:梁桂钊

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 在Spring Boot中优雅的实现定时任务

    在日常的项目开发中,往往会涉及到一些需要做到定时执行的代码,例如自动将超过24小时的未付款的单改为取消状态,自动将超过14天客户未签收的订单改为已签收状态等等,...

    用户2781897
  • 蚂蚁二面,面试官问我零拷贝的实现原理,当场懵了…

    "狼哥,面试又跪了,碰到了知识盲区" "哪个?" "一面还可以,二面面试官问我零拷贝的原理,懵逼了...这块内容没去研究过" "哦,这个知识点,我之前应该有讲过...

    用户2781897
  • 整理了一份 Docker系统知识,从安装到熟练操作看这篇就够

    当我们在工作中,一款产品从开发设计到上线运行,其中需要开发人员和运维工程师,开发人员负责代码编写,开发产品,运维工程师需要测试环境,产品部署。这之间就会有分歧。

    用户2781897
  • 三歪问我Dubbo的SPI机制是啥?

    上一篇 Dubbo 文章敖丙已经带了大家过了一遍整体的架构,也提到了 Dubbo 的成功离不开它采用微内核设计+SPI扩展,使得有特殊需求的接入方可以自定义扩展...

    敖丙
  • 玩转 Java8 中的 Stream 之从零认识 Stream

    相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。

    Java帮帮
  • URLEncode和URLDecode的注意事项

    注意:当进行URLEncode加密过的参数通过浏览器请求时,浏览器会自动URLDecode解密一次 。并且对于"%" 、 "+" 等特殊字符有不同的处理

    执笔记忆的空白
  • JVM各区溢出分析

    由于在Hotspot虚拟机中中不区分虚拟机栈和本地方法栈,因此通过-Xoss修改参数是无效的,可以通过修改-Xss设定。

    java乐园
  • 把一个txt文件转化为带标题栏的Excel文档

    用户5166556
  • 再见虚拟机!在Win10中使用Linux版本的R和Python

    想象一下,你用 Linux 版本的 Tensorflow 建立了一个美妙的 RNN 模型,然后无缝切换到 Windows 用 Excel 直接编辑结果,画了一幅...

    1480
  • tp5 实现列表数据根据状态排序

    我们的列表数据有时候需要根据据状态来排序,状态有 1,2,3,4 四种状态 如果我们希望将 2 的状态排第一,那么就需要自定义状态

    砸漏

扫码关注云+社区

领取腾讯云代金券