SOFABoot 是基于 Spring Boot 的一套研发框架。
在彻底兼容 Spring Boot 的基础上,SOFABoot 还提供了启动期监控检查,上下文隔离,模块化开发,类隔离,日志空间隔离等等能力。
SOFABoot 地址: https://github.com/alipay/sofa-boot
本文工程案例: github.com/glmapper/gl…
春节前 SOFABoot 发布了 2.6.x 系列版本,新特性也是至关给力,这里简单罗列下新特性:html
以前的文章中有 @玄北 写过的模块化的文章( 传送门 : 剖析 | 详谈 SOFABoot 模块化原理 & 实操 | 基于 SOFABoot 进行模块化开发 ),这两篇文章中介绍了模块化的几种实现方案(PS:固然主要仍是为了宣传一下 SOFABoot 提供的基于 Spring 上下文隔离的模块化能力)。SOFABoot 的模块隔离方案是为了解决传统的模块化方案模块化不完全的问题,从 2.4.0 版本开始支持基于 Spring 上下文隔离的模块化能力,每一个 SOFABoot 模块使用独立的 Spring 上下文,每一个模块自包含,模块与模块之间经过 JVM Service 进行通讯,从而避免模块间的紧耦合。java
在 Spring 上下文隔离的状况下,各个上下文之间的 bean 是互不可见;SOFABoot 中经过发布 JVM 服务的方式使得不一样模块 bean 之间的访问得以实现。可是同时又带来了另一个问题,若是一个模块以独立 jar 的方式对外提供 api ,那么对于其余依赖此模块的模块来讲,就没法去改变这个模块中的 bean 实例行为。mysql
在实际的使用场景中,一个模块中的 bean 有时候须要开放一些入口,供另一个模块扩展。SOFABoot 借鉴和使用了 Nuxeo Runtime 项目 以及 nuxeo 项目,并在上面扩展,与 Spring 融合,提供了扩展点的能力。git
本篇将针对 SOFABoot 2.6.x 版本中提供的扩展点进行简单尝试,结合官方文档提供的示例,一步一步实现咱们自定义的一个扩展点功能(本文过于简单,可能会引发极度温馨,请备好被子和热水袋)。github
这里先抛出一个例子,如今有一个三方 jar ,它定义了获取数据源接口的顶层抽象;不一样的业务方若是依赖这个 jar,则须要本身提供一个数据源的实现,固然其自己提供了默认实现(假设是 mysql)。基于此咱们大概可以想到的方式就是基于 SPI 来提供这种扩展能力,可是对于在 Spring 上下文隔离的状况下,业务方的 Spring 上下文是没法与引入 jar 的上下文共享 bean 的,这样天然也就没法实现对原有数据源实现的扩展。web
那么这里咱们就能够选择使用 SOFABoot 扩展点来实现,这里有两个比较重要的概念,也是扩展点区别于 SPI 的主要特性:spring
下面基于这两个点,来完成自定义扩展点的一个案例。在实现上述案例以前咱们须要先构建一个基于 Spring 上下文隔离的模块化工程,而后再简单介绍下扩展点的基本使用方式。sql
SOFABoot 开源版本中并无给出扩展点相关的案例工程,只是在测试用例中进行了详细的测试,有兴趣的同窗能够看下相关测试用例代码。实际上测试用例中也没有涉及到在模块化的场景下使用扩展点,测试用例都是基于同一个Spring 上下文来完成的。本篇文章将先搭建一个简单的模块化工程,而后基于此工程来实现扩展点的能力。api
本工程主要包括 4 个模块:bash
官方文档及案例 中给的比较复杂,包含了多种使用服务发布和引用的方式,这里我使用了最新提供的基于注解的方式来实现;获取本文工程案例。
在 SOFABoot 中使用扩展点能力,须要如下三个步骤:
这三步中前两步都是由服务提供方来完成,最后一步由具体的业务使用方式来定义。
本案例工程中,是将 glmapper-sofa-provider 做为服务提供方,所以也在此模块下定义一个具备扩展能力的 bean 。
定义这个接口的实现:
在模块的 Spring 配置文件 resources/META-INF/service-provider.xml 中,咱们把这个 bean 给配置起来:
在上面的 bean 中有一个字段 word ,在实际中,咱们但愿这个字段可以被其余的模块自定义进行覆盖,这里咱们将其以扩展点的形式暴露出来。这里先定义一个类去描述这个扩展点:
而后在模块的 Spring 配置文件 resources/META-INF/service-provider.xml 中定义扩展点:
至此服务提供端已经暴露出了扩展点,那么在服务使用端,也就是须要扩展这个 bean 的使用方就能够扩展这个bean 了。
上述已经将扩展点定义好了,此时咱们就能够对这个 bean 进行扩展了。扩展是具体业务使用方来作的事,在本案例中,glmapper-sofa-web 模块做为使用服务使用方,所以在 resources/META-INF/spring/web-home.xml 下进行扩展定义:
须要注意一点,glmapper-sofa-web 模块不是一个 SOFABoot 模块,这里留坑。
编写一个 TestController 类,这里最早参考的是 SOFABoot 测试用例中的写法,以下:
启动运行,结果抛了一个错:
没有找到 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 文件位置,不然工程能够正常运行,可是基于此工程扩展点扩展的能力是无效的。
细心的同窗能够注意到了一个点,就是前面扩展点实现 IExtensionImpl 这个类中有一个特殊的方法,在整个案例演示中其实都是没有用到的。
最开始对这个方法我也很迷糊,由于我并无用到。既然本身没用到,那必定是框架本身用到了。有兴趣的同窗能够本身断点下这段逻辑。实际上 SOFABoot 在解析到贡献点时,会调用被扩展 bean 的 registerExtension 方法,其中包含了用户定义的贡献点处理逻辑。在上述的例子中,获取用户定义的 value 值,并将其设置到 word 字段中覆盖 bean 中原始定义的值,最后调用 extension.say() 方法,能够看到返回扩展中定义的值: newValue 。
上述的例子中只是一个很简单的扩展,其实 XMap 包含了很是丰富的描述能力,包括 List
, Map
等,这些能够经过查看 XMap 的文档 来了解。在 SOFABoot 中,除了 XMap 原生的支持之外,还扩展了跟 Spring 集成的能力:
这部分的扩展能力,让扩展点的能力更加丰富,描述对象中能够直接指向一个 SpringBean (用户配置 bean 的名字,SOFABoot 会根据名字从 Spring 上下文中获取到 bean)。
基于前小结对于 XMAP 的扩展的介绍以及开篇的案例, 这里举一个使用 XNodeSpring 的例子,来实现 Spring 上下文隔离场景对于数据源 bean 的扩展。依然是前文描述的三个步骤:
一、定义一个 DatasourceBean ,而且提供一个 getDatasource 方法,用于获取 数据源实例。
public interface DatasourceBean {
void getDatasource();
}复制代码
二、定义一个 DefaultDataSourceBean ,做为 DatasourceBean 接口的默认实现。
public class DefaultDataSourceBean implements DatasourceBean {
@Override
public void getDatasource() {
System.out.println("mysql datasource");
}
}复制代码
新建 DatasourceExtension 接口
public interface DatasourceExtension {
/**
* 获取数据源 Bean 实例
* @return
*/
DatasourceBean getDatasourceBean();
}复制代码
新建 DatasourceExtensionImpl 实现类,而且实现 DatasourceExtension 中的 getDatasourceBean 方法,且里面经过 datasourceBean 去执行获取数据源实例。
定义而且暴露扩展点,这里还须要一个扩展点描述。
下面在 xml 文件中将此扩展点经过 xml 暴露出去,并配置相关默认实现。
以上几步在此案例工程包括定义提供扩展能力的 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 而且对扩展点进行扩展。以下:
详细代码见:glmapper-sofa-extension 。
下面开始启动项目工程,首先将扩展部分注释掉,执行 http://localhost:8080/extension ,查看控制台打印结果以下:
mysql datasource复制代码
打开扩展部分注释,从新启动,刷新地址,查看控制台打印结果以下:
oracle datasource复制代码
那么这里能够看到,provider 中提供的数据源 bean 被自定义的 数据源 bean 替换了。实现了在 Spring 上下文隔离状况下,替换 bean 的操做。
扩展点的存在很好的解决了这样一个问题:须要在另外一个模块中对依赖的模块中定义的组件进行定制化。在模块化的场景下,若是可以容许改变另一个模块中 bean 的行为,无疑会解决不少棘手的问题。
本文经过一个简单的 Demo 对 SOFABoot 中扩展点进行了演示,本篇基于 SOFABoot 官方文档,补充了一些使用上的细节以及须要注意的一些坑,但愿经过本篇文章能够帮助你们对 SOFABoot 扩展点的能力及使用有初步了解。
未出正月都是年,这里给你们拜个晚年,祝你们新年快乐、升职加薪!
本文工程案例:github.com/glmapper/gl…
SOFABoot 2.6.x 系列版本:github.com/alipay/sofa…
剖析 | 详谈 SOFABoot 模块化原理:mp.weixin.qq.com/s/7WAClC-f9…
实操 | 基于 SOFABoot 进行模块化开发:mp.weixin.qq.com/s/-7_wXRcvW…
SOFABoot 官方文档及案例:www.sofastack.tech/sofa-boot/d…
nuxeo 项目:github.com/nuxeo/nuxeo
公众号:金融级分布式架构(Antfin_SOFA)