[TOC]
在平时业务开中经常会遇到不同业务走不同的业务逻辑,为了代码的扩展性,不得不采取一些手段来对进行解耦,本文将介绍常用的代码扩展点实现方式,包括 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 {
@Override
public String type() {
return "DingDing";
}
@Override
public void sendMessage(String message) {
System.out.println("this id DingDing's message! " + message);
}
}
public class EmailServer implements IMessageServer {
@Override
public String type() {
return "email";
}
@Override
public void sendMessage(String message) {
System.out.println("this is email's message! " + message);
}
}
public class QQServer implements IMessageServer {
@Override
public String type() {
return "QQ";
}
@Override
public 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 { @Test public void testJavaSpi() { // init message server factory(只实例化一次) MessageServerFactory messageServerFactory = new MessageServerFactory(); // client invoke IMessageServer 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 {
@Override
public void say() {
System.out.println("this is female human say!");
}
}
public class MaleHumanServiceImpl implements HumanService {
@Override
public 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.MaleHumanServiceImpl
femaleHumanService=com.zqh.extension.dubbospi.impl.FemaleHumanServiceImpl
- 客户端调用示例代码
public class DubboSpiTest {
@Test
public 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 {
@Override
public String type() {
return "DingDing";
}
@Override
public void sendMessage(String message) {
System.out.println("this id DingDing's message! " + message);
}
}
public class EmailServer extends AbstractMessageServer {
@Override
public String type() {
return "email";
}
@Override
public void sendMessage(String message) {
System.out.println("this is email's message! " + message);
}
}
public class QQServer extends AbstractMessageServer {
@Override
public String type() {
return "QQ";
}
@Override
public 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 map
for(IMessageServer iMessageServer : iMessageServers) {
messageServerMap.put(iMessageServer.type(), iMessageServer);
}
}
public IMessageServer getByType(String type) {
return messageServerMap.get(type);
}
}
- 客户端调用示例代码
public class StrategyTest {
@Test
public 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 进 extendPlugins
loadExtendPluginClasses();
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 {
@Test
public 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 框架源码地址:https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register
- 定义扩展点 SomeExtPt 及实现类 SomeExtensionA、SomeExtensionB
public interface SomeExtPt extends ExtensionPointI {
public void doSomeThing();
}
@Extension(bizId = "A")
@Component
public class SomeExtensionA implements SomeExtPt {
@Override
public void doSomeThing() {
System.out.println("SomeExtensionA::doSomething");
}
}
@Extension(bizId = "B")
@Component
public class SomeExtensionB implements SomeExtPt {
@Override
public void doSomeThing() {
System.out.println("SomeExtensionB::doSomething");
}
}
- 客户端调用
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class ExtensionRegisterTest {
@Resource
private ExtensionRegister register;
@Resource
private ExtensionExecutor executor;
@Test
public 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 {
/**
* 搬运货物(领域服务)
*/
@DomainService
public void carry();
}
public class FemaleHuman implements Human {
@Override
public 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 {
@Override
public 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 {
/**
* 扩展点实现类
*/
@AbilityExtension
void carry();
}
@AbilityInstance
public class XiaoMingExt implements ICarryBusinessExt {
@Override
public void carry() {
System.out.println("我能搬运100斤");
}
}