文章目录

  • Java SPI
    • 1)简介
    • 2)代码示例
    • 3)实现原理优缺点
  • dubbo SPI
    • 1)简介
    • 2)代码示例
  • 策略模式及改进版扩展点实现
    • 策略模式扩展点实现
    • 策略模式改进扩展点实现
  • Cola 扩展点设计
    • 1)cola 框架简介
    • 2)示例代码
  • 抽象业务扩展点实现方式
    • 基础概念理解

在平时业务开中经常会遇到不同业务走不同的业务逻辑,为了代码的扩展性,不得不采取一些手段来对进行解耦,本文将介绍常用的代码扩展点实现方式,包括 Java SPI、dubbo SPI、策略模式及改进扩展点实现、Cola扩展点和抽象业务扩展点实现方式。

Java SPI

1)简介

Java SPI,即 Java Service Provider Interface,是 Java 提供的一套供第三方实现或者扩展的 API,用于为某个接口寻找服务实现类,实现代码的解耦。这里以示例说明:假设消息服务器有 email、dingding、qq 三种类型,每个具体的消息服务器都具有发送消息的能力,类图如下:

2)代码示例

示例代码详细参考 extension-examples 工程 com.zqh.extension.javaspi 包,github 地址:https://github.com/zhuqiuhui/extension-examples

  • 创建接口及实现类
public interface IMessageServer {void sendMessage(String message);
}public class DingDingServer implements IMessageServer {@Overridepublic String type() {return "DingDing";}@Overridepublic void sendMessage(String message) {System.out.println("this id DingDing's message! " + message);}
}public class EmailServer implements IMessageServer {@Overridepublic String type() {return "email";}@Overridepublic void sendMessage(String message) {System.out.println("this is email's message! " + message);}
}public class QQServer implements IMessageServer {@Overridepublic String type() {return "QQ";}@Overridepublic void sendMessage(String message) {System.out.println("this is QQ's message! " + message);}
}
  • 定义工厂类用于根据不同的类型获取不同的 MessageServer:
public class MessageServerFactory {private ServiceLoader<IMessageServer> messageServerServiceLoader = ServiceLoader.load(IMessageServer.class);public IMessageServer getByType(String type) {for (IMessageServer messageServer : messageServerServiceLoader) {if(Objects.equals(messageServer.type(), type)) {return messageServer;}}return null;}
}
  • 在 resources 目录下创建 META-INF/services 目录,同时该目录下新建一个与上述接口的全限定名一致的文件名,在这个文件中写入接口的实现类的全限定名:

    // 文件名
    com.zqh.extension.javaspi.IMessageServer// 文件内容
    com.zqh.extension.javaspi.impl.DingDingServer
    com.zqh.extension.javaspi.impl.EmailServer
    com.zqh.extension.javaspi.impl.QQServer
    
  • 客户端调用示例代码

    public class JavaSpiTest {@Testpublic void testJavaSpi() {// init message server factory(只实例化一次)MessageServerFactory messageServerFactory = new MessageServerFactory();// client invokeIMessageServer emailMessageServer = messageServerFactory.getByType("email");emailMessageServer.sendMessage("I am hungry");}
    }// 输出
    this is email's message! I am hungry
    

3)实现原理优缺点

java SPI 本质上采用“基于接口编程+策略模式+配置文件”来实现服务的动态获取,ServiceLoader 类的 load 方法会从 META-INF/services 目录下找到待实例化的服务,依次进行实例化。所以这里的缺点是如果不使用某些类就会造成资源浪费,不能实例懒加载机制(有兴趣的可以解读下 ServiceLoader 源代码)。

dubbo SPI

1)简介

dubbo SPI 又称为 dubbo 扩展自适应机制,即 dubbo 定义了 @SPI 注解表示该接口是一个扩展点,同时若实现类或方法上存在 @Adaptive 注解,则表示该类或方法是一个自适应的扩展点。相对于 Java SPI 优化了以下几点:

  • 文件内容通过 KV 配置,key 是服务别名,value 是服务类实现的全限定名

  • 实现按需实例化,而不是一次性将某接口的所有实现类全部加载到内存

更详细的 dubbo 扩展自适应机制源码,可以参考:dubbo源码一:ExtensionLoader及获取适配类过程解析:https://blog.csdn.net/zhuqiuhui/article/details/83820876

2)代码示例

示例代码详细参考 extension-examples 工程 com.zqh.extension.dubbospi 包,github 地址:https://github.com/zhuqiuhui/extension-examples

  • 定义扩展点和实现类,如下:
@SPI
public interface HumanService {void say();
}public class FemaleHumanServiceImpl implements HumanService {@Overridepublic void say() {System.out.println("this is female human say!");}
}public class MaleHumanServiceImpl implements HumanService {@Overridepublic void say() {System.out.println("this is man human say!");}
}
  • 在以下三个任意一个目录下定义文件:com.zqh.extension.dubbospi.HumanService,内容如下:
  // 目录(任选其一)META-INF/services/META-INF/dubbo/META-INF/dubbo/internal/// 文件内容maleHumanService=com.zqh.extension.dubbospi.impl.MaleHumanServiceImplfemaleHumanService=com.zqh.extension.dubbospi.impl.FemaleHumanServiceImpl
  • 客户端调用示例代码
public class DubboSpiTest {@Testpublic void testDubboSpi() {HumanService maleHumanService = ExtensionLoader.getExtensionLoader(HumanService.class).getExtension("maleHumanService");maleHumanService.say();}
}// 输出
this is man human say!

策略模式及改进版扩展点实现

策略模式扩展点实现

这里和 Java SPI 很相似,只不过加载服务实现类的方式不同,Java SPI 加载服务实例使用 ServiceLoader.load 方法,本方法使用手动创建对象,示例中直接进行 new 对象,如果在 Spring 容器中还可以使用类型自动注入或构造器注入方式。示例代码详细参考 extension-examples 工程 com.zqh.extension.strategy 包,github 地址:https://github.com/zhuqiuhui/extension-examples

  • 定义扩展点和实现类,如下:
public interface IMessageServer {String type();void sendMessage(String message);
}public abstract class AbstractMessageServer implements IMessageServer {// 这里可以抽取一些公共流程
}public class DingDingServer extends AbstractMessageServer {@Overridepublic String type() {return "DingDing";}@Overridepublic void sendMessage(String message) {System.out.println("this id DingDing's message! " + message);}
}public class EmailServer  extends AbstractMessageServer {@Overridepublic String type() {return "email";}@Overridepublic void sendMessage(String message) {System.out.println("this is email's message! " + message);}
}public class QQServer extends AbstractMessageServer {@Overridepublic String type() {return "QQ";}@Overridepublic void sendMessage(String message) {System.out.println("this is QQ's message! " + message);}
}
  • 定义 IMessageServer 工厂类
public class MessageServerFactory {private final Map<String, IMessageServer> messageServerMap = new HashMap<>();private final IMessageServer[] iMessageServers;public MessageServerFactory(IMessageServer[] iMessageServers) {this.iMessageServers  = iMessageServers;// init mapfor(IMessageServer iMessageServer : iMessageServers) {messageServerMap.put(iMessageServer.type(), iMessageServer);}}public IMessageServer getByType(String type) {return messageServerMap.get(type);}
}
  • 客户端调用示例代码
public class StrategyTest {@Testpublic void testStrategy() {/*** 初始化 MessageServerFactory,在Spring 容器中可使用构造器注入方式进行服务类进行自动注入*/IMessageServer[] iMessageServers = new IMessageServer[]{new DingDingServer(),new EmailServer(),new QQServer()};MessageServerFactory messageServerFactory = new MessageServerFactory(iMessageServers);// 调用IMessageServer emailMessageServer = messageServerFactory.getByType("email");emailMessageServer.sendMessage("hello world");}
}

策略模式改进扩展点实现

使用策略模式更高级的做法将服务实例工厂类进行封装,做到业务无感多业务类型支持,示例中将各不同业务实现类统一由启动类 ExtensionPluginBoot 来管理,详细见代码说明(示例代码详细参考 extension-examples 工程 com.zqh.extension.strategyimprove 包,github 地址:https://github.com/zhuqiuhui/extension-examples):

public class ExtensionPluginBoot {private static ExtensionPluginBoot instance = null;/*** class --> (name, instance)*/private static Map<Class<? extends IExtension>, Map<String, IExtension>> extendPlugins = new LinkedHashMap<>();public static ExtensionPluginBoot getInstance() {if(instance == null) {synchronized (ExtensionPluginBoot.class) {if(instance == null) {new ExtensionPluginBoot().init();}}}return instance;}public void init() {// 加载扩展点,将服务实现类 put 进 extendPluginsloadExtendPluginClasses();instance = this;}private void loadExtendPluginClasses() {// 这里可使用扫描注解、配置文件等方式,下面直接 new 做为示例/*** 消息服务器*/Map<String, IExtension> messageServerMap = new HashMap<>();messageServerMap.put("DingDing", new DingDingServer());messageServerMap.put("email", new DingDingServer());messageServerMap.put("QQ", new DingDingServer());extendPlugins.put(IMessageServer.class, messageServerMap);/*** 人类*/Map<String, IExtension> humanMap = new HashMap<>();humanMap.put("maleHuman", new MaleHumanServiceImpl());humanMap.put("femaleHuman", new FemaleHumanServiceImpl());extendPlugins.put(HumanService.class, humanMap);}/*** 根据扩展接口和名称,获取具体的实现* @param extensionPoint 扩展接口* @param name 名称* @param <T> 扩展类实例* @return*/public <T extends IExtension> T getNameExtension(Class<T> extensionPoint, String name) {Map<String, IExtension> pluginMap = extendPlugins.get(extensionPoint);if(pluginMap == null) {return null;}return (T) pluginMap.get(name);}
}

客户端调用代码如下:

public class StrategyImproveTest {@Testpublic void testStrategyImprove() {// 使用 qq 服务器进行发送IMessageServer qqMessageServer = ExtensionRouterFactory.getPlugin(IMessageServer.class, "QQ");qqMessageServer.sendMessage("hello world");// 男人说话HumanService maleHumanService = ExtensionRouterFactory.getPlugin(HumanService.class, "maleHuman");maleHumanService.say();}
}// 输出
this id DingDing's message! hello world
this is man human say!

Cola 扩展点设计

1)cola 框架简介

cola 框架是以 DDD 思想为依据定义了应用工程就有的框架和组件,为业务应用工程提供了参考,可以详细参考 cola 的官方文档。cola 2.0 的扩展点支持到了“业务身份”,“用例”,“场景”的三级扩展,详细介绍参考:https://blog.csdn.net/significantfrank/article/details/100074716

2)示例代码

示例代码详细参考 cola 框架源码地址:cola扩展点示例代码 github地址

  • 定义扩展点 SomeExtPt 及实现类 SomeExtensionA、SomeExtensionB
public interface SomeExtPt extends ExtensionPointI {public void doSomeThing();
}@Extension(bizId = "A")
@Component
public class SomeExtensionA implements SomeExtPt {@Overridepublic void doSomeThing() {System.out.println("SomeExtensionA::doSomething");}
}@Extension(bizId = "B")
@Component
public class SomeExtensionB implements SomeExtPt {@Overridepublic void doSomeThing() {System.out.println("SomeExtensionB::doSomething");}
}
  • 客户端调用
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ExtensionRegisterTest {@Resourceprivate ExtensionRegister register;@Resourceprivate ExtensionExecutor executor;@Testpublic void test() {SomeExtPt extA = new SomeExtensionA();register.doRegistration(extA);SomeExtPt extB = CglibProxyFactory.createProxy(new SomeExtensionB());register.doRegistration(extB);executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("A"), SomeExtPt::doSomeThing);executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("B"), SomeExtPt::doSomeThing);}
}

附参考文档:

  • cola 框架 github 源码:https://github.com/alibaba/COLA
  • cola 框架介绍:https://blog.csdn.net/significantfrank/article/details/110934799
  • cola 扩展点介绍:https://blog.csdn.net/significantfrank/article/details/100074716

抽象业务扩展点实现方式

基础概念理解

扩展点的实现离不开业务,业务的扩展点需要更高的抽象才能支持得更灵活,先明确几个关键词:

  • 业务流程与业务活动: 用户完成某次业务操作的全过程,视为业务活动的编排。如用户执行一次下单操作包括:生成订单、营销优惠计算和库存扣减三个业务活动,业务活动即业务流程编排的基础单元。
  • 领域(@Domain): 一个完整上下文的抽象,可大可小,视具体业务而定。常见的大的电商领域有订单域、支付域、库存域等,小的如营销域中的活动域、价格域等。
  • 领域服务(@DomainService): 各个领域能对外提供的服务,比如活动域可以提供查询优惠领域服务等
  • 域能力(@Ability): 领域具备的可扩展的能力,比如活动域的活动添加、删除能力等
  • 域能力扩展点(@AbilityExtension): 域能力的可扩展点,通常是方法级的扩展,如针对于不同场景减库存的逻辑是不一样的,这个不同的逻辑处理就放到域能力扩展点上来实现。
  • 域能力实例(@AbilityInstance): 域能力的子类实现,理解为具象的域能力

上面的关键词有点抽象,结合下面一句话来理解:小明可以搬运100斤大米

这句话抽象出来:

  • 小明是一个人,“人”即可视为一个领域,而小明则是“人”领域的一个实例。
  • “搬运货物”视为“人”可以提供的服务(领域服务),从某一方面讲“人”具备搬运货物的能力(域能力,除此之外人还具备看、吃、说话等能力)
  • “可以搬运100斤大米”这句话抽象出来是:“人”能搬运多重的货物,即域能力扩展点。“人”能搬运100斤重的货物,即域能力实例。

示意代码结构如下(示例代码 github:https://github.com/zhuqiuhui/extension-examples):

  • Step 1:领域及领域服务定义
@Domain
public interface Human {/*** 搬运货物(领域服务)*/@DomainServicepublic void carry();
}public class FemaleHuman implements Human {@Overridepublic void carry() {// 1. 获取搬运货物的能力ICarryAbility carryAbility = getCarryAbility();// 2. 搬运货物carryAbility.carry();}
}
  • Step 2:定义域能力
public interface IAbility {}public interface ICarryAbility extends IAbility {void carry();
}@Ability
public class DefaultCarryAbility implements ICarryAbility {@Overridepublic void carry() {// Step 1:找到货物//......// Step 2:搬运货物(可根据不同业务场景bizCode获取不的扩展点)ICarryBusinessExt iCarryBusinessExt = getICarryBusinessExt(bizCode);iCarryBusinessExt.carry();// Step 3:放置货物//......}
}
  • Step 3:定义扩展点
public interface IExtensionPoints {}public interface ICarryBusinessExt extends IExtensionPoints {/*** 扩展点实现类*/@AbilityExtensionvoid carry();
}@AbilityInstance
public class XiaoMingExt implements ICarryBusinessExt {@Overridepublic void carry() {System.out.println("我能搬运100斤");}
}

如转载,请注明出处!欢迎关v信公众号:方辰的博客

常用代码扩展点设计方式相关推荐

  1. 框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解)

    框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解) 一丶什么是框架,以及框架的作用 什么是框架? 框架,简而言之就是把东西封装好了,使用框架开发可以快速开发程序,例如MFC程序的双击写代码. 为 ...

  2. 提高Java开发效率:5个常用的Visual Studio代码扩展工具

    对于软件工程师来说,能够更好地管理时间是一项宝贵的技能.因此,这里有5个Visual Studio代码扩展工具,可以帮助前端开发人员(以及更多的人!)将生产力至少提高10%到20%,下面和小编一起来看 ...

  3. 用代码来说明,为什么需要面向扩展的设计

    在基本的面向对象编程中,你只能直接调用一个类的方法,而这些方法是由这个类的作者定义的,这对于面向用户设计的类来说是没有问题的.此外,在 20 - 30 年前,在大型标准库和开源库被大量复用之前,大部分 ...

  4. pcb 理论阻值、 过孔_超实用!PCB设计中过孔常用的6种处理方式

    原标题:超实用!PCB设计中过孔常用的6种处理方式 小伙伴们我们又见面啦! 上一次的" 神仙过孔 ",还没过瘾吧? <整齐的过孔固然符合审美,但是却...> 今天的小课 ...

  5. html天气插件iframe,分享常用7款天气预报代码iframe嵌入网页方式

    如果在网站上加入天气预报功能,你找不到更好的天气预报代码,可以看下本站和大家分享的7款天气预报代码iframe嵌入网页方式. 天气预报代码1 src='http://appnews.qq.com/cg ...

  6. IOS常用代码总结 - 第三方库部分

    1 SBJson的使用 JSON是一种数据交换语言,和XML是同样用途的.不过JSON的体积要比XML小,也就意味着在网络传输中 速度会比XML更快. 这里可以看到更多关于json的资料:http:/ ...

  7. 5 款阿里常用代码检测工具,免费用!

    作者 | 喻阳 面临问题 在日常研发过程中,我们通常面临的代码资产问题主要分为两大类:代码质量问题和代码安全漏洞. 1.代码质量问题 代码质量其实是一个老生常谈的话题,但问题是大家都知道它很重要,却又 ...

  8. 巧用拦截器:高效的扩展点设计

    最近在设计框架时,需要设计一类扩展点,发现不能简单地继承或使用事件来给使用者提供 API.最终使用拦截器模式解决了 API 的设计. 扩展点使用场景 该扩展点的使用场景如下: 不能使用继承:需要在类型 ...

  9. pytorch常用代码

    20211228 https://mp.weixin.qq.com/s/4breleAhCh6_9tvMK3WDaw 常用代码段 本文代码基于 PyTorch 1.x 版本,需要用到以下包: impo ...

最新文章

  1. JS跳转手机QQ的聊天页面
  2. 前端学习(616):变量的定义
  3. mui初级入门教程(六)— 模板页面实现原理及多端适配指南
  4. c语言 枚举类型 uint32_最全面C语言数据基本数据类型解析
  5. 设计模式之—工厂方法模式
  6. visio画图-去掉visio中多余的连接点
  7. 软件版本GA、RC、beta等含义
  8. 基于debezium实时数据同步(Oracle为主)
  9. 装了mysql电脑黑屏怎么办_电脑黑屏的原因,教你解决黑屏
  10. 关于MOS管的详细介绍
  11. Oracle数据库 表空间
  12. 不太吸引人的成就系统
  13. find() python
  14. 关于Gson的TypeToken
  15. php的表达爱意的一句代码,含蓄表达爱意的爱情诗句(70条)
  16. 计算机动画制作流程文字版,常见的三维动画制作流程总结
  17. Bios工程师手边事—ACPI电源管理
  18. Python实践-咚咚呛讲师Python进阶教程
  19. php里的除号,PHP学习之PHP运算符
  20. VSCode插件之实时字数统计与选中词英汉互译

热门文章

  1. 技术行业的一些新闻———改变了时代的企业
  2. mcp3208C语言程序,MCP3208 12位ADC与单片机的Proteus仿真
  3. 【面试】网易游戏面试题目整理及答案(5)
  4. 腾讯云重装系统后不能远程账户密码登录
  5. 关联度分析法-灰色关联分析
  6. shell脚本三大文本处理工具
  7. 更新下离线语音插座调试进度
  8. GH3536(GH536) 热处理制度 板材和管材
  9. NAT和代理服务器的原理及代表产品
  10. IT圈的“年龄歧视”,android模拟器中文输入法