cola架构:一种扩展点的实现思路浅析
目录
1.扩展点使用实例
2.主要技术点
2.1 注解加持
2.2 注解解析
2.3 扩展点路由
在实际项目中,我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑,进而消除代码中ifelse硬编码分支,使代码结构更清晰,也大大提升了代码可读性;同时也满足了“开闭原则”,具备更高的可扩展性;
在cola架构中,给出了一种“扩展点”的思路,本质还是策略模式的实现方式,通过“扩展点注解”的组装方式将策略模式实现类注册到容器中,供后续场景逻辑决策使用;
1.扩展点使用实例
首先通过实例了解下,cola 扩展点的使用方式:
1.首先定义一个SomeExtPt接口,并实现ExtensionPointI接口
public interface SomeExtPt extends ExtensionPointI {public void doSomeThing();
}
2.具体实现SomeExtPt接口,这里给出了2个实现类,如下:
@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");}}
3.测试方法:
@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);}}
public class CglibProxyFactory {public static <T> T createProxy(T object) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(object.getClass());enhancer.setCallback(new ProxyCallback(object));return (T) enhancer.create();}public static class ProxyCallback implements MethodInterceptor {private Object target;public ProxyCallback(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("ProxyObject::before");Object object = proxy.invoke(target, args);System.out.println("ProxyObject::after");return object;}}
}
2.主要技术点
2.1 注解加持
上述具体策略接口实现方法标注了扩展点注解:@Extension
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Repeatable(Extensions.class)
@Component
public @interface Extension {String bizId() default BizScenario.DEFAULT_BIZ_ID;String useCase() default BizScenario.DEFAULT_USE_CASE;String scenario() default BizScenario.DEFAULT_SCENARIO;
}
所有的具体实现方法都需要标注该注解,表明该类属于一个扩展点;同时,由于标注了@Component注解,表明每一个扩展点也是一个bean实例;
2.2 注解解析
扩展点注解的解析工作主要借助类ExtensionBootstrap和ExtensionRegister完成:
@Component
public class ExtensionBootstrap implements ApplicationContextAware {@Resourceprivate ExtensionRegister extensionRegister;private ApplicationContext applicationContext;@PostConstructpublic void init(){Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class);extensionBeans.values().forEach(extension -> extensionRegister.doRegistration((ExtensionPointI) extension));// handle @Extensions annotationMap<String, Object> extensionsBeans = applicationContext.getBeansWithAnnotation(Extensions.class);extensionsBeans.values().forEach( extension -> extensionRegister.doRegistrationExtensions((ExtensionPointI) extension));}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}
@Component
public class ExtensionRegister {/*** 扩展点接口名称不合法*/private static final String EXTENSION_INTERFACE_NAME_ILLEGAL = "extension_interface_name_illegal";/*** 扩展点不合法*/private static final String EXTENSION_ILLEGAL = "extension_illegal";/*** 扩展点定义重复*/private static final String EXTENSION_DEFINE_DUPLICATE = "extension_define_duplicate";@Resourceprivate ExtensionRepository extensionRepository;public final static String EXTENSION_EXTPT_NAMING = "ExtPt";public void doRegistration(ExtensionPointI extensionObject) {Class<?> extensionClz = extensionObject.getClass();if (AopUtils.isAopProxy(extensionObject)) {extensionClz = ClassUtils.getUserClass(extensionObject);}Extension extensionAnn = AnnotationUtils.findAnnotation(extensionClz, Extension.class);BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}public void doRegistrationExtensions(ExtensionPointI extensionObject){Class<?> extensionClz = extensionObject.getClass();if (AopUtils.isAopProxy(extensionObject)) {extensionClz = ClassUtils.getUserClass(extensionObject);}Extensions extensionsAnnotation = AnnotationUtils.findAnnotation(extensionClz, Extensions.class);Extension[] extensions = extensionsAnnotation.value();if (!ObjectUtils.isEmpty(extensions)){for (Extension extensionAnn : extensions) {BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario());ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}}//String[] bizIds = extensionsAnnotation.bizId();String[] useCases = extensionsAnnotation.useCase();String[] scenarios = extensionsAnnotation.scenario();for (String bizId : bizIds) {for (String useCase : useCases) {for (String scenario : scenarios) {BizScenario bizScenario = BizScenario.valueOf(bizId, useCase, scenario);ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity());ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject);if (preVal != null) {String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate;throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage);}}}}}/*** @param targetClz* @return*/private String calculateExtensionPoint(Class<?> targetClz) {Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(targetClz);if (interfaces == null || interfaces.length == 0) {throw new ExtensionException(EXTENSION_ILLEGAL, "Please assign a extension point interface for " + targetClz);}for (Class intf : interfaces) {String extensionPoint = intf.getSimpleName();if (extensionPoint.contains(EXTENSION_EXTPT_NAMING)) {return intf.getName();}}String errMessage = "Your name of ExtensionPoint for " + targetClz +" is not valid, must be end of " + EXTENSION_EXTPT_NAMING;throw new ExtensionException(EXTENSION_INTERFACE_NAME_ILLEGAL, errMessage);}}
最终将扩展点和决策条件的映射关系存储到ExtensionRepository中:
@Component
public class ExtensionRepository {public Map<ExtensionCoordinate, ExtensionPointI> getExtensionRepo() {return extensionRepo;}private Map<ExtensionCoordinate, ExtensionPointI> extensionRepo = new HashMap<>();}
2.3 扩展点路由
在实际业务场景调度过程中,会调用ExtensionExecutor的方法locateExtension完成扩展点的查找,最终执行扩展点逻辑;
@Component
public class ExtensionExecutor extends AbstractComponentExecutor {private static final String EXTENSION_NOT_FOUND = "extension_not_found";private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class);@Resourceprivate ExtensionRepository extensionRepository;@Overrideprotected <C> C locateComponent(Class<C> targetClz, BizScenario bizScenario) {C extension = locateExtension(targetClz, bizScenario);logger.debug("[Located Extension]: " + extension.getClass().getSimpleName());return extension;}/*** if the bizScenarioUniqueIdentity is "ali.tmall.supermarket"* <p>* the search path is as below:* 1、first try to get extension by "ali.tmall.supermarket", if get, return it.* 2、loop try to get extension by "ali.tmall", if get, return it.* 3、loop try to get extension by "ali", if get, return it.* 4、if not found, try the default extension** @param targetClz*/protected <Ext> Ext locateExtension(Class<Ext> targetClz, BizScenario bizScenario) {checkNull(bizScenario);Ext extension;logger.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity());// first try with full namespaceextension = firstTry(targetClz, bizScenario);if (extension != null) {return extension;}// second try with default scenarioextension = secondTry(targetClz, bizScenario);if (extension != null) {return extension;}// third try with default use case + default scenarioextension = defaultUseCaseTry(targetClz, bizScenario);if (extension != null) {return extension;}String errMessage = "Can not find extension with ExtensionPoint: " +targetClz + " BizScenario:" + bizScenario.getUniqueIdentity();throw new ExtensionException(EXTENSION_NOT_FOUND, errMessage);}/*** first try with full namespace* <p>* example: biz1.useCase1.scenario1*/private <Ext> Ext firstTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("First trying with " + bizScenario.getUniqueIdentity());return locate(targetClz.getName(), bizScenario.getUniqueIdentity());}/*** second try with default scenario* <p>* example: biz1.useCase1.#defaultScenario#*/private <Ext> Ext secondTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("Second trying with " + bizScenario.getIdentityWithDefaultScenario());return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultScenario());}/*** third try with default use case + default scenario* <p>* example: biz1.#defaultUseCase#.#defaultScenario#*/private <Ext> Ext defaultUseCaseTry(Class<Ext> targetClz, BizScenario bizScenario) {logger.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase());return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase());}private <Ext> Ext locate(String name, String uniqueIdentity) {final Ext ext = (Ext) extensionRepository.getExtensionRepo().get(new ExtensionCoordinate(name, uniqueIdentity));return ext;}private void checkNull(BizScenario bizScenario) {if (bizScenario == null) {throw new IllegalArgumentException("BizScenario can not be null for extension");}}}
相关文章:
cola架构:一种扩展点的实现思路浅析
目录 1.扩展点使用实例 2.主要技术点 2.1 注解加持 2.2 注解解析 2.3 扩展点路由 在实际项目中,我们经常使用策略模式、或者状态模式来隔离同一接口下不同的实现逻辑,进而消除代码中ifelse硬编码分支,使代码结构更清晰,也大大…...

Thread常用API
setname方法每个线程取名 需要创建构造器 线程设置名字 package Thread_api_test;// 继承Thread类 public class MyThread extends Thread {//创建构造器 线程设置名字public MyThread(String name){super(name);}Overridepublic void run() {super.run();Thread mThread.cur…...

系列九、Redis的发布订阅
一、概述 Redis的发布订阅是进程间的一种消息通信模式,发送者(Publisher)发送消息,订阅者(Subscriber)接收消息。 二、命令 三、案例演示 3.1、订阅单个 客户端订阅 cctv-1、cctv-2、 cctv-3三个频道 SUB…...
《TypeScript》系列之对比JavaScript,TypeScript的优势
概述 TypeScript是微软公司开发的一种基于JavaScript语言的编程语言,它的目的并不是创造一种全新的语言,而是增强JavaScript的功能,使其更适合多人合作的企业级项目。TypeScript可以看做是JavaScript的超集,即它继承了后者的全部…...

Notepad++使用技巧
显示远程连接的文件目录 自动完成:函数自动提示 自动输入:输入一半括号自动补全另一半 自动关联 .pc文件识别为C 列模式 按住Alt不松手,可以直接范围选择,便于编辑选择的区域 关键行筛选 1.进入搜索页面的标记 2.选中标…...
React 中报Type error: Could not find a declaration file for module ‘qs‘.(已解决)
ts项目引入qs 比如 nextjs 或者reactjs npm install --save-dev types/qs 确保你的tsconfig.json文件中有以下设置 {"compilerOptions": {"types": ["qs"]}} 如果你的项目中没有tsconfig.json文件,你可以创建一个。在项目根目录下创…...

【Linux】:Linux环境与版本
以下哪个命令输出Linux内核的版本信息 A.uname -r B.vmstat C.sar D.stat uname -r 查看linux内核版本信息 vmstat 报告关于内核线程、虚拟内存、磁盘、陷阱和 CPU 活动的统计信息 sar 主要帮助我们掌握系统资源的使用情况,特别是内存和CPU的使用情况 stat 用于显示…...

PNG转EPS,包括Latex导入
在电脑TEXLIVE文件夹里中找到bmeps.exe TEXLIVE\2022\bin\win32 可以新建一个文件夹picture(图片和exe文件必须在一个文件夹里),将bmeps.exe复制出来,方便后续大量图片操作 导入png图片 新建一个txt文件,命名为Fig1.…...

Visual Studio 2022 cmake编译 PP-OCRv4
1 环境准备 下载PaddleOCR PaddleOCR C 部署代码位于 PaddleOCR\deploy\cpp_infer目录下 paddle_inference paddle_inference opencv 这里使用已经安装好的opencv4.5.5下载dirent-master.zip 下载dirent-master.zip, 解压并复制dirent.h文件到PaddleOCR\deploy\cpp_infer目录下…...

学习笔记|串口与PC通信的接线|移植驱动程序|串口通信实战|STC32G单片机视频开发教程(冲哥)|第二十一集(上):串口与PC通信
目录 1.串口与PC通信的接线芯片与芯片之间的通讯:芯片与电脑之间的通讯: 2.利用实验箱示例代码移植驱动程序3.串口通信实战最小工程代码移植编译提示错误:undefined identifier 1.串口与PC通信的接线 上节试验是串口和电脑进行一个通信&…...
关于Mybaits缓存....
记Mybaits缓存踩的坑 1.问题提出 最近开发一个记录操作前后修改内容的功能,获取修改前数据比较简单,直接从数据库获取,记录修改后的功能也比较简单,直接将用户修改的内容封装成po对象,然后两个比对就可以了ÿ…...
Vue axios调用springboot接口获取数据库数据并显示到网页
axios调用接口获取数据 可以查看简述化的此文 点击 此文简述化文章 PS**由于我自己的本次springboot项目内容很多,所以只是截取了其中关于axios调用接口获取数据的内容,还请大家了解工作原理即可** 前端 添加axios和vue2链接 <script src"htt…...

12-bean创建流程3
文章目录 1 bean实例化前 2. bean实例化doCreateBean() 1 bean实例化前 createBean方法里面的resolveBeforeInstantiation方法,InstantiationAwareBeanPostProcessor接口创建一个代理对象返回 try {// Give BeanPostProcessors a chance to return a p…...

volatile关键字 和 i = i + 1过程
本文是复制粘贴,请直接看原文 原文链接:Java并发编程:volatile关键字解析 - Matrix海子 - 博客园 (cnblogs.com) ------------------------------------------------------------------------------------------------------------------- Java并发编程࿱…...
ubuntu20 安装 cmake 3.27
1. 下载cmake3.27 建议从cmake官网下载安装,虽然比较慢,但从清华镜像里下载的cmake文件不全。 我下载的是:cmake-3.27.7.tar.gz 博客 ubuntu安装cmake的三种方法(超方便!)-CSDN博客 里面提供了三种方法&am…...

faster lio 回环 加入GTSAM优化的记录
首先感谢这位博主的文章:https://blog.csdn.net/weixin_41281151/article/details/125371285,其中部分代码参考于改博主中的github: https://github.com/kahowang/FAST_LIO_SAM 不同的是,我使用的是faster lio进行更改,…...

深入剖析 深度学习中 __init()__函数和forward()函数
目录 前言1. __init()__函数2. forward()函数3. 两者关系 前言 再看代码时,发现init函数和forward函数都有参数,具体是怎么传参的呢? 为了更方便的讲解,会举简单的代码例子结合讲解。 forward() 和 __init__() 是神经网络模型类…...

BUUCTF学习(一):SQL注入,万能密码
1、场景 2、题目 3、解题 用户名:admin or 11# 密码:123456 4、解析SQL注入 “SQL注入是一种常见的Web应用程序漏洞,攻击者可以通过注入的SQL语句获取数据库的敏感信息,对网站用户的数据安全造成威胁。SQL注入的特点包括广泛性、隐…...

基于springboot实现心灵治愈心理健康平台系统项目【项目源码+论文说明】计算机毕业设计
基于springboot实现心灵心理健康平台系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个心灵治愈交流平台 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论…...
百度Apollo自动驾驶
百度从2013年开始布局自动驾驶领域,十年来一直坚持压强式的、马拉松式的研发投入,以技术创新驱动长期发展。百度Apollo L4级自动驾驶运营测试里程累计已超5000万公里,拥有自动驾驶专利族超4600件,其中高级别自动驾驶专利族数全球第…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...