SOFABoot 扩展点初体验 | SOFALab 实践系列

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: SOFABoot 是基于 Spring Boot 的一套研发框架。 在完全兼容 Spring Boot 的基础上,SOFABoot 还提供了启动期监控检查,上下文隔离,模块化开发,类隔离,日志空间隔离等等能力。

SOFABoot 是基于 Spring Boot 的一套研发框架。

在完全兼容 Spring Boot 的基础上,SOFABoot 还提供了启动期监控检查,上下文隔离,模块化开发,类隔离,日志空间隔离等等能力。

SOFABoot 地址:
https://github.com/alipay/sofa-boot


本文工程案例:
https://github.com/glmapper/glmapper-sofa-extension

春节小长假还没感觉就过去了,对于“热爱工作”的我,也早早的回到了工作岗位;感受下假期中的我和上班时的我。

后面拿枪的就是"逼着"我写文章的五花肉,上次 SOFATracer 采样用的是刀,这次用了枪!

image.png    image.png

模块化与扩展点

言归正传,节前 SOFABoot 发布了 2.6.x 系列版本,新特性也是相当给力,这里简单罗列下新特性:

  • 支持扩展和扩展点
  • 在刷新上下文期间支持 spring bean 的并行初始化
  • 支持使用注解方式发布 JVM 服务

之前的文章中有 @玄北 写过的模块化的文章( 传送门 : 剖析 | 详谈 SOFABoot 模块化原理  & 实操 | 基于 SOFABoot 进行模块化开发 ),这两篇文章中介绍了模块化的几种实现方案(PS:当然主要还是为了宣传一下 SOFABoot 提供的基于 Spring 上下文隔离的模块化能力)。SOFABoot 的模块隔离方案是为了解决传统的模块化方案模块化不彻底的问题,从 2.4.0 版本开始支持基于 Spring 上下文隔离的模块化能力,每个 SOFABoot 模块使用独立的 Spring 上下文,每个模块自包含,模块与模块之间通过 JVM Service 进行通信,从而避免模块间的紧耦合。

在 Spring 上下文隔离的情况下,各个上下文之间的 bean 是互不可见;SOFABoot 中通过发布 JVM 服务的方式使得不同模块 bean 之间的访问得以实现。但是同时又带来了另外一个问题,如果一个模块以独立 jar 的方式对外提供 api ,那么对于其他依赖此模块的模块来说,就无法去改变这个模块中的 bean 实例行为。

在实际的使用场景中,一个模块中的 bean 有时候需要开放一些入口,供另外一个模块扩展。SOFABoot 借鉴和使用了 Nuxeo Runtime 项目 以及 nuxeo 项目,并在上面扩展,与 Spring 融合,提供了扩展点的能力。

本篇将针对 SOFABoot 2.6.x 版本中提供的扩展点进行简单尝试,结合官方文档提供的示例,一步一步实现我们自定义的一个扩展点功能(本文过于简单,可能会引起极度舒适,请备好被子和热水袋)。

案例背景

这里先抛出一个例子,现在有一个三方 jar ,它定义了获取数据源接口的顶层抽象;不同的业务方如果依赖这个 jar,则需要自己提供一个数据源的实现,当然其本身提供了默认实现(假设是 mysql)。基于此我们大概能够想到的方式就是基于 SPI 来提供这种扩展能力,但是对于在 Spring 上下文隔离的情况下,业务方的 Spring 上下文是无法与引入 jar 的上下文共享 bean 的,这样自然也就无法实现对原有数据源实现的扩展。

那么这里我们就可以选择使用 SOFABoot 扩展点来实现,这里有两个比较重要的概念,也是扩展点区别于 SPI 的主要特性:

  • 可以在基于 Spring 上下文隔离的情况下实现扩展
  • 扩展的是 Spring Bean 

下面基于这两个点,来完成自定义扩展点的一个案例。在实现上述案例之前我们需要先构建一个基于 Spring 上下文隔离的模块化工程,然后再简单介绍下扩展点的基本使用方式。

构建模块化工程

SOFABoot 开源版本中并没有给出扩展点相关的案例工程,只是在测试用例中进行了详细的测试,有兴趣的同学可以看下相关测试用例代码。实际上测试用例中也没有涉及到在模块化的场景下使用扩展点,测试用例都是基于同一个Spring 上下文来完成的。本篇文章将先搭建一个简单的模块化工程,然后基于此工程来实现扩展点的能力。

本工程主要包括 4 个模块:

  • glmapper-sofa-facade         // JVM 服务发布与引用的 API 包
  • glmapper-sofa-provider      // Annotation 方式发布 JVM 服务
  • glmapper-sofa-consumer    // Annotation 方式引用 JVM 服务
  • glmapper-sofa-web             // 启动包含 SOFABoot 模块的 SOFA Boot 应用

官方文档及案例 中给的比较复杂,包含了多种使用服务发布和引用的方式,这里我使用了最新提供的基于注解的方式来实现;获取本文工程案例

扩展点基本使用

在 SOFABoot 中使用扩展点能力,需要以下三个步骤:

  • 定义提供扩展能力的 bean
  • 定义扩展点
  • 定义扩展并使用

这三步中前两步都是由服务提供方来完成,最后一步由具体的业务使用方式来定义。

定义提供扩展能力的 bean

本案例工程中,是将 glmapper-sofa-provider 作为服务提供方,因此也在此模块下定义一个具有扩展能力的 bean 。
image.png

定义这个接口的实现:
image.png

在模块的 Spring 配置文件 resources/META-INF/service-provider.xml  中,我们把这个 bean 给配置起来:
image.png

定义扩展点

在上面的 bean 中有一个字段 word ,在实际中,我们希望这个字段能够被其他的模块自定义进行覆盖,这里我们将其以扩展点的形式暴露出来。这里先定义一个类去描述这个扩展点:
image.png

然后在模块的 Spring 配置文件 resources/META-INF/service-provider.xml  中定义扩展点:

image.png

  • name   :为扩展点的名字
  • ref       :为扩展点所作用在的 bean
  • object :为扩展点的贡献点具体的描述,这个描述是通过 XMap 的方式来进行的(XMap 的作用是将 Java 对象和 XML 文件进行映射,这里建议通过在网上搜索下 XMap 的文档来了解 XMap)

至此服务提供端已经暴露出了扩展点,那么在服务使用端,也就是需要扩展这个 bean 的使用方就可以扩展这个bean 了。

定义扩展

上述已经将扩展点定义好了,此时我们就可以对这个 bean 进行扩展了。扩展是具体业务使用方来做的事,在本案例中,glmapper-sofa-web 模块作为使用服务使用方,因此在 resources/META-INF/spring/web-home.xml 下进行扩展定义:
image.png

  • bean     :为扩展所作用在的 bean
  • point    :为扩展点的名字
  • content  里面的内容为扩展的定义,会通过 XMap 将内容解析为:扩展点的贡献点具体的描述对象,在这里即为 com.glmapper.bridge.extension.ExtensionDescriptor 对象

需要注意一点,glmapper-sofa-web 模块不是一个 SOFABoot 模块,这里留坑。

编写一个 TestController 类,这里最先参考的是 SOFABoot 测试用例中的写法,如下:
image.png

启动运行,结果抛了一个错:

image.png

没有找到 extension 这个 bean ,但是实际上我们在前面 定义提供扩展能力的 bean 小结中已经将 extension 配置成一个 bean 了。

原因在于,glmapper-sofa-provider 是一个 SOFABoot 模块,它有自己独立的 Spring 上下文环境,web 模块这里作为根上下文无法感知到这个 bean 的存在,所以这里还需要将 extension 这个发布成一个 JVM 服务,然后才能正常启动。具体就是在 IExtensionImpl 类上加上 @SofaService 注解。然后在 TestController 中,将@Autowired 改成 @SofaReference 。

另外,因为 glmapper-sofa-web 不是一个 SOFABoot 模块(这里特指的是 isle 模块),在  resources/META-INF/spring/web-home.xml 定义的扩展无法直接被 spring 扫到,因此还要在启动类上使用 @ImportResource 来指定当前 web 模块的 xml 文件位置,否则工程可以正常运行,但是基于此工程扩展点扩展的能力是无效的。

registerExtension 

细心的同学可以注意到了一个点,就是前面扩展点实现 IExtensionImpl 这个类中有一个特殊的方法,在整个案例演示中其实都是没有用到的。
image.png

最开始对这个方法我也很迷糊,因为我并没有用到。既然自己没用到,那一定是框架自己用到了。有兴趣的同学可以自己断点下这段逻辑。实际上 SOFABoot 在解析到贡献点时,会调用被扩展 bean 的 registerExtension 方法,其中包含了用户定义的贡献点处理逻辑。在上述的例子中,获取用户定义的 value 值,并将其设置到 word 字段中覆盖 bean 中原始定义的值,最后调用 extension.say() 方法,可以看到返回扩展中定义的值: newValue 。

XMap 支持和扩展

上述的例子中只是一个很简单的扩展,其实 XMap 包含了非常丰富的描述能力,包括 ListMap 等,这些可以通过查看 XMap 的文档 来了解。在 SOFABoot 中,除了 XMap 原生的支持以外,还扩展了跟 Spring 集成的能力:

  • 通过 XNode        扩展出了 XNodeSpring
  • 通过 XNodeList  扩展出了 XNodeListSpring
  • 通过 XNodeMap 扩展出了 XNodeMapSpring

这部分的扩展能力,让扩展点的能力更加丰富,描述对象中可以直接指向一个 SpringBean (用户配置 bean 的名字,SOFABoot 会根据名字从 Spring 上下文中获取到 bean)。

Datasource 扩展点案例

基于前小结对于 XMAP 的扩展的介绍以及开篇的案例, 这里举一个使用 XNodeSpring 的例子,来实现 Spring 上下文隔离场景对于数据源 bean 的扩展。依然是前文描述的三个步骤:

辅助接口和类

1、定义一个 DatasourceBean ,并且提供一个 getDatasource 方法,用于获取 数据源实例。

public interface DatasourceBean {
    void getDatasource();
}

2、定义一个 DefaultDataSourceBean ,作为 DatasourceBean 接口的默认实现。

public class DefaultDataSourceBean implements DatasourceBean {
    @Override
    public void getDatasource() {
        System.out.println("mysql datasource");
    }
}

定义提供扩展能力的 DatasourceExtension Bean

新建 DatasourceExtension 接口

public interface DatasourceExtension {
    /**
     * 获取数据源 Bean 实例
     * @return
     */
    DatasourceBean getDatasourceBean();
}

新建 DatasourceExtensionImpl 实现类,并且实现 DatasourceExtension 中的 getDatasourceBean 方法,且里面通过 datasourceBean 去执行获取数据源实例。

image.png

定义扩展点

定义并且暴露扩展点,这里还需要一个扩展点描述。
image.png
下面在 xml 文件中将此扩展点通过 xml 暴露出去,并配置相关默认实现。
image.png

以上几步在此案例工程包括定义提供扩展能力的 bean、包括扩展点等均在 glmapper-sofa-provider 中完成,作为扩展点的提供方。

定义扩展

这部分实现是需要由业务方来完成,这里就需要对于 provider 中提供的扩展点进行扩展,以改变其本身提供的 bean 实例的行为。

扩展扩展点,这里我们希望能够扩展对于 oracle 数据源的支持,那么对于 provider 中提供的默认对 mysql 的支持的 bean 实例就需要被“扩展”,此处的扩展本身上就是 bean 实例的替换。

首先定义一个 OracleDatasourceBean ,同样实现 DatasourceBean 这个接口,getDatasource 方法中返回 oracle 的数据源实例:

public class OracleDatasourceBean implements DatasourceBean {
    @Override
    public void getDatasource() {
        System.out.println("oracle datasource");
    }
}

然后在业务模块(本案例在 glmapper-sofa-web 模块下)的 resources/META-INF/spring/web-home.xml 中配置扩展的 bean 并且对扩展点进行扩展。如下:
image.png

详细代码见:glmapper-sofa-extension 。

下面开始启动项目工程,首先将扩展部分注释掉,执行 http://localhost:8080/extension ,查看控制台打印结果如下:

mysql datasource

打开扩展部分注释,重新启动,刷新地址,查看控制台打印结果如下:

oracle datasource

那么这里可以看到,provider 中提供的数据源 bean 被自定义的 数据源 bean 替换了。实现了在 Spring 上下文隔离情况下,替换 bean 的操作。

小结

扩展点的存在很好的解决了这样一个问题:需要在另一个模块中对依赖的模块中定义的组件进行定制化。在模块化的场景下,如果能够允许改变另外一个模块中 bean 的行为,无疑会解决很多棘手的问题。

本文通过一个简单的 Demo 对 SOFABoot 中扩展点进行了演示,本篇基于 SOFABoot 官方文档,补充了一些使用上的细节以及需要注意的一些坑,希望通过本篇文章可以帮助大家对 SOFABoot 扩展点的能力及使用有初步了解。

未出正月都是年,这里给大家拜个晚年,祝大家新年快乐、升职加薪!

文中相关链接

本文工程案例:https://github.com/glmapper/glmapper-sofa-extension

SOFABoot  2.6.x 系列版本:https://github.com/alipay/sofa-boot/releases
剖析 | 详谈 SOFABoot 模块化原理:https://mp.weixin.qq.com/s/7WAClC-f9mM7-_WIa10M_g
实操 | 基于 SOFABoot 进行模块化开发:https://mp.weixin.qq.com/s/-7_wXRcvWpt6JwsBIOkQjA
SOFABoot 官方文档及案例:https://www.sofastack.tech/sofa-boot/docs/extension

nuxeo 项目:https://github.com/nuxeo/nuxeo

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
1月前
|
缓存 安全 Java
Java并发性能优化|读写锁与互斥锁解析
本文深入解析Java中两种核心锁机制——互斥锁与读写锁,通过概念对比、代码示例及性能测试,揭示其适用场景。互斥锁适用于写多或强一致性场景,读写锁则在读多写少时显著提升并发性能。结合锁降级、公平模式等高级特性,助你编写高效稳定的并发程序。
117 0
|
4月前
|
关系型数据库 MySQL Linux
实现MySQL数据库的定时自动备份脚本。
拿走,不谢,这个脚本配方(指引)保证你的数据库数据像蛋糕店一样地天天更新,还能确保老旧的蛋糕(数据)不会堆积满仓库。这下可好,数据安全有保障,数据库管理员也能轻松一点,偶尔闲下来的时候,煮杯咖啡,看个剧岂不美哉?别忘了偶尔检查一下你的自动备份是否正常工作,以防万一蛋糕机器出了点小差错。
238 20
|
2月前
|
人工智能 安全 Java
Spring Boot 过滤器 拦截器 监听器
本文介绍了Spring Boot中的过滤器、拦截器和监听器的实现与应用。通过Filter接口和FilterRegistrationBean类,开发者可实现对请求和响应的数据过滤;使用HandlerInterceptor接口,可在控制器方法执行前后进行处理;利用各种监听器接口(如ServletRequestListener、HttpSessionListener等),可监听Web应用中的事件并作出响应。文章还提供了多个代码示例,帮助读者理解如何创建和配置这些组件,适用于构建更高效、安全和可控的Spring Boot应用程序。
510 0
|
消息中间件 监控 Java
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽【RabbitMQ实战 一】
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽【RabbitMQ实战 一】
797 0
|
Prometheus 监控 关系型数据库
数据库同步革命:MySQL GTID模式下主从配置的全面解析
数据库同步革命:MySQL GTID模式下主从配置的全面解析
1242 0
|
11月前
|
网络协议 安全 JavaScript
Web实时通信的学习之旅:WebSocket入门指南及示例演示
Web实时通信的学习之旅:WebSocket入门指南及示例演示
1780 0
|
机器学习/深度学习 人工智能 安全
【网安AIGC专题10.11】①代码大模型的应用:检测、修复②其安全性研究:模型窃取攻击(API和网页接口) 数据窃取攻击 对抗攻击(用途:漏洞隐藏) 后门攻击(加触发器+标签翻转)(下)
【网安AIGC专题10.11】①代码大模型的应用:检测、修复②其安全性研究:模型窃取攻击(API和网页接口) 数据窃取攻击 对抗攻击(用途:漏洞隐藏) 后门攻击(加触发器+标签翻转)
437 1
|
运维 Java 开发工具
|
移动开发 安全 算法
mPaaS云平台运维系列之—移动网关常见问题
移动网关服务(Mobile Gateway Service,MGS)是mPaaS提供的连接移动客户端与服务端的组件产品。该组件简化了移动端与服务端的数据协议和通讯协议,能够显著提升开发效率和网络通讯效率。本章主要介绍产品常见问题。
1823 0
mPaaS云平台运维系列之—移动网关常见问题
|
弹性计算 Kubernetes 架构师
985毕业,工作3年,分享从阿里辞职到了国企的一路辛酸和经验
楼主本硕985,毕业的时候去了杭州某互联网大厂,后来又跳槽去了北京某互联网大厂。简单的谈一下互联网的感受吧,工作压力大,节奏快,但是从技术上确实得到了成长,尤其是当你维护与大促相关的系统的时候。记得在北京的时候,作为系统负责人,那个系统docker就800个,那个系统的并发量在全国来说肯定是top级别的。通过维护大促系统,排查跳点,不断地优化系统的框架,优化JVM,所带来的技术提升是质的飞跃。