常用代码扩展点设计方式
文章目录
- 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信公众号:方辰的博客
常用代码扩展点设计方式相关推荐
- 框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解)
框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解) 一丶什么是框架,以及框架的作用 什么是框架? 框架,简而言之就是把东西封装好了,使用框架开发可以快速开发程序,例如MFC程序的双击写代码. 为 ...
- 提高Java开发效率:5个常用的Visual Studio代码扩展工具
对于软件工程师来说,能够更好地管理时间是一项宝贵的技能.因此,这里有5个Visual Studio代码扩展工具,可以帮助前端开发人员(以及更多的人!)将生产力至少提高10%到20%,下面和小编一起来看 ...
- 用代码来说明,为什么需要面向扩展的设计
在基本的面向对象编程中,你只能直接调用一个类的方法,而这些方法是由这个类的作者定义的,这对于面向用户设计的类来说是没有问题的.此外,在 20 - 30 年前,在大型标准库和开源库被大量复用之前,大部分 ...
- pcb 理论阻值、 过孔_超实用!PCB设计中过孔常用的6种处理方式
原标题:超实用!PCB设计中过孔常用的6种处理方式 小伙伴们我们又见面啦! 上一次的" 神仙过孔 ",还没过瘾吧? <整齐的过孔固然符合审美,但是却...> 今天的小课 ...
- html天气插件iframe,分享常用7款天气预报代码iframe嵌入网页方式
如果在网站上加入天气预报功能,你找不到更好的天气预报代码,可以看下本站和大家分享的7款天气预报代码iframe嵌入网页方式. 天气预报代码1 src='http://appnews.qq.com/cg ...
- IOS常用代码总结 - 第三方库部分
1 SBJson的使用 JSON是一种数据交换语言,和XML是同样用途的.不过JSON的体积要比XML小,也就意味着在网络传输中 速度会比XML更快. 这里可以看到更多关于json的资料:http:/ ...
- 5 款阿里常用代码检测工具,免费用!
作者 | 喻阳 面临问题 在日常研发过程中,我们通常面临的代码资产问题主要分为两大类:代码质量问题和代码安全漏洞. 1.代码质量问题 代码质量其实是一个老生常谈的话题,但问题是大家都知道它很重要,却又 ...
- 巧用拦截器:高效的扩展点设计
最近在设计框架时,需要设计一类扩展点,发现不能简单地继承或使用事件来给使用者提供 API.最终使用拦截器模式解决了 API 的设计. 扩展点使用场景 该扩展点的使用场景如下: 不能使用继承:需要在类型 ...
- pytorch常用代码
20211228 https://mp.weixin.qq.com/s/4breleAhCh6_9tvMK3WDaw 常用代码段 本文代码基于 PyTorch 1.x 版本,需要用到以下包: impo ...
最新文章
- JS跳转手机QQ的聊天页面
- 前端学习(616):变量的定义
- mui初级入门教程(六)— 模板页面实现原理及多端适配指南
- c语言 枚举类型 uint32_最全面C语言数据基本数据类型解析
- 设计模式之—工厂方法模式
- visio画图-去掉visio中多余的连接点
- 软件版本GA、RC、beta等含义
- 基于debezium实时数据同步(Oracle为主)
- 装了mysql电脑黑屏怎么办_电脑黑屏的原因,教你解决黑屏
- 关于MOS管的详细介绍
- Oracle数据库 表空间
- 不太吸引人的成就系统
- find() python
- 关于Gson的TypeToken
- php的表达爱意的一句代码,含蓄表达爱意的爱情诗句(70条)
- 计算机动画制作流程文字版,常见的三维动画制作流程总结
- Bios工程师手边事—ACPI电源管理
- Python实践-咚咚呛讲师Python进阶教程
- php里的除号,PHP学习之PHP运算符
- VSCode插件之实时字数统计与选中词英汉互译