[TOC]
在平時(shí)業(yè)務(wù)開中經(jīng)常會(huì)遇到不同業(yè)務(wù)走不同的業(yè)務(wù)邏輯,為了代碼的擴(kuò)展性,不得不采取一些手段來對進(jìn)行解耦,本文將介紹常用的代碼擴(kuò)展點(diǎn)實(shí)現(xiàn)方式,包括 Java SPI、dubbo SPI、策略模式及改進(jìn)擴(kuò)展點(diǎn)實(shí)現(xiàn)、Cola擴(kuò)展點(diǎn)和抽象業(yè)務(wù)擴(kuò)展點(diǎn)實(shí)現(xiàn)方式。
Java SPI
1)簡介
Java SPI,即 Java Service Provider Interface,是 Java 提供的一套供第三方實(shí)現(xiàn)或者擴(kuò)展的 API,用于為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)類,實(shí)現(xiàn)代碼的解耦。這里以示例說明:假設(shè)消息服務(wù)器有 email、dingding、qq 三種類型,每個(gè)具體的消息服務(wù)器都具有發(fā)送消息的能力,類圖如下:

2)代碼示例
示例代碼詳細(xì)參考 extension-examples 工程 com.zqh.extension.javaspi 包,github 地址:https://github.com/zhuqiuhui/extension-examples
- 創(chuàng)建接口及實(shí)現(xiàn)類
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);
}
}
- 定義工廠類用于根據(jù)不同的類型獲取不同的 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 目錄下創(chuàng)建 META-INF/services 目錄,同時(shí)該目錄下新建一個(gè)與上述接口的全限定名一致的文件名,在這個(gè)文件中寫入接口的實(shí)現(xiàn)類的全限定名:
// 文件名 com.zqh.extension.javaspi.IMessageServer // 文件內(nèi)容 com.zqh.extension.javaspi.impl.DingDingServer com.zqh.extension.javaspi.impl.EmailServer com.zqh.extension.javaspi.impl.QQServer -
客戶端調(diào)用示例代碼
public class JavaSpiTest { @Test public void testJavaSpi() { // init message server factory(只實(shí)例化一次) 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)實(shí)現(xiàn)原理優(yōu)缺點(diǎn)
java SPI 本質(zhì)上采用“基于接口編程+策略模式+配置文件”來實(shí)現(xiàn)服務(wù)的動(dòng)態(tài)獲取,ServiceLoader 類的 load 方法會(huì)從 META-INF/services 目錄下找到待實(shí)例化的服務(wù),依次進(jìn)行實(shí)例化。所以這里的缺點(diǎn)是如果不使用某些類就會(huì)造成資源浪費(fèi),不能實(shí)例懶加載機(jī)制(有興趣的可以解讀下 ServiceLoader 源代碼)。
dubbo SPI
1)簡介
dubbo SPI 又稱為 dubbo 擴(kuò)展自適應(yīng)機(jī)制,即 dubbo 定義了 @SPI 注解表示該接口是一個(gè)擴(kuò)展點(diǎn),同時(shí)若實(shí)現(xiàn)類或方法上存在 @Adaptive 注解,則表示該類或方法是一個(gè)自適應(yīng)的擴(kuò)展點(diǎn)。相對于 Java SPI 優(yōu)化了以下幾點(diǎn):
文件內(nèi)容通過 KV 配置,key 是服務(wù)別名,value 是服務(wù)類實(shí)現(xiàn)的全限定名
實(shí)現(xiàn)按需實(shí)例化,而不是一次性將某接口的所有實(shí)現(xiàn)類全部加載到內(nèi)存
更詳細(xì)的 dubbo 擴(kuò)展自適應(yīng)機(jī)制源碼,可以參考:dubbo源碼一:ExtensionLoader及獲取適配類過程解析:https://blog.csdn.net/zhuqiuhui/article/details/83820876
2)代碼示例
示例代碼詳細(xì)參考 extension-examples 工程 com.zqh.extension.dubbospi 包,github 地址:https://github.com/zhuqiuhui/extension-examples
- 定義擴(kuò)展點(diǎn)和實(shí)現(xiàn)類,如下:
@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!");
}
}
- 在以下三個(gè)任意一個(gè)目錄下定義文件:com.zqh.extension.dubbospi.HumanService,內(nèi)容如下:
// 目錄(任選其一)
META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/
// 文件內(nèi)容
maleHumanService=com.zqh.extension.dubbospi.impl.MaleHumanServiceImpl
femaleHumanService=com.zqh.extension.dubbospi.impl.FemaleHumanServiceImpl
- 客戶端調(diào)用示例代碼
public class DubboSpiTest {
@Test
public void testDubboSpi() {
HumanService maleHumanService = ExtensionLoader.getExtensionLoader(HumanService.class)
.getExtension("maleHumanService");
maleHumanService.say();
}
}
// 輸出
this is man human say!
策略模式及改進(jìn)版擴(kuò)展點(diǎn)實(shí)現(xiàn)
策略模式擴(kuò)展點(diǎn)實(shí)現(xiàn)
這里和 Java SPI 很相似,只不過加載服務(wù)實(shí)現(xiàn)類的方式不同,Java SPI 加載服務(wù)實(shí)例使用 ServiceLoader.load 方法,本方法使用手動(dòng)創(chuàng)建對象,示例中直接進(jìn)行 new 對象,如果在 Spring 容器中還可以使用類型自動(dòng)注入或構(gòu)造器注入方式。示例代碼詳細(xì)參考 extension-examples 工程 com.zqh.extension.strategy 包,github 地址:https://github.com/zhuqiuhui/extension-examples
- 定義擴(kuò)展點(diǎn)和實(shí)現(xiàn)類,如下:
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);
}
}
- 客戶端調(diào)用示例代碼
public class StrategyTest {
@Test
public void testStrategy() {
/**
* 初始化 MessageServerFactory,在Spring 容器中可使用構(gòu)造器注入方式進(jìn)行服務(wù)類進(jìn)行自動(dòng)注入
*/
IMessageServer[] iMessageServers = new IMessageServer[]{
new DingDingServer(),
new EmailServer(),
new QQServer()
};
MessageServerFactory messageServerFactory = new MessageServerFactory(iMessageServers);
// 調(diào)用
IMessageServer emailMessageServer = messageServerFactory.getByType("email");
emailMessageServer.sendMessage("hello world");
}
}
策略模式改進(jìn)擴(kuò)展點(diǎn)實(shí)現(xiàn)
使用策略模式更高級的做法將服務(wù)實(shí)例工廠類進(jìn)行封裝,做到業(yè)務(wù)無感和多業(yè)務(wù)類型支持,示例中將各不同業(yè)務(wù)實(shí)現(xiàn)類統(tǒng)一由啟動(dòng)類 ExtensionPluginBoot 來管理,詳細(xì)見代碼說明(示例代碼詳細(xì)參考 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() {
// 加載擴(kuò)展點(diǎn),將服務(wù)實(shí)現(xiàn)類 put 進(jìn) extendPlugins
loadExtendPluginClasses();
instance = this;
}
private void loadExtendPluginClasses() {
// 這里可使用掃描注解、配置文件等方式,下面直接 new 做為示例
/**
* 消息服務(wù)器
*/
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);
}
/**
* 根據(jù)擴(kuò)展接口和名稱,獲取具體的實(shí)現(xiàn)
* @param extensionPoint 擴(kuò)展接口
* @param name 名稱
* @param <T> 擴(kuò)展類實(shí)例
* @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);
}
}
客戶端調(diào)用代碼如下:
public class StrategyImproveTest {
@Test
public void testStrategyImprove() {
// 使用 qq 服務(wù)器進(jìn)行發(fā)送
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 擴(kuò)展點(diǎn)設(shè)計(jì)
1)cola 框架簡介
cola 框架是以 DDD 思想為依據(jù)定義了應(yīng)用工程就有的框架和組件,為業(yè)務(wù)應(yīng)用工程提供了參考,可以詳細(xì)參考 cola 的官方文檔。cola 2.0 的擴(kuò)展點(diǎn)支持到了“業(yè)務(wù)身份”,“用例”,“場景”的三級擴(kuò)展,詳細(xì)介紹參考:https://blog.csdn.net/significantfrank/article/details/100074716

2)示例代碼
示例代碼詳細(xì)參考 cola 框架源碼地址:https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register
- 定義擴(kuò)展點(diǎn) SomeExtPt 及實(shí)現(xiàn)類 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");
}
}
- 客戶端調(diào)用
@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 擴(kuò)展點(diǎn)介紹:https://blog.csdn.net/significantfrank/article/details/100074716
抽象業(yè)務(wù)擴(kuò)展點(diǎn)實(shí)現(xiàn)方式
基礎(chǔ)概念理解
擴(kuò)展點(diǎn)的實(shí)現(xiàn)離不開業(yè)務(wù),業(yè)務(wù)的擴(kuò)展點(diǎn)需要更高的抽象才能支持得更靈活,先明確幾個(gè)關(guān)鍵詞:
- 業(yè)務(wù)流程與業(yè)務(wù)活動(dòng):用戶完成某次業(yè)務(wù)操作的全過程,視為業(yè)務(wù)活動(dòng)的編排。如用戶執(zhí)行一次下單操作包括:生成訂單、營銷優(yōu)惠計(jì)算和庫存扣減三個(gè)業(yè)務(wù)活動(dòng),業(yè)務(wù)活動(dòng)即業(yè)務(wù)流程編排的基礎(chǔ)單元。
- 領(lǐng)域(@Domain):一個(gè)完整上下文的抽象,可大可小,視具體業(yè)務(wù)而定。常見的大的電商領(lǐng)域有訂單域、支付域、庫存域等,小的如營銷域中的活動(dòng)域、價(jià)格域等。
- 領(lǐng)域服務(wù)(@DomainService):各個(gè)領(lǐng)域能對外提供的服務(wù),比如活動(dòng)域可以提供查詢優(yōu)惠領(lǐng)域服務(wù)等
- 域能力(@Ability):領(lǐng)域具備的可擴(kuò)展的能力,比如活動(dòng)域的活動(dòng)添加、刪除能力等
- 域能力擴(kuò)展點(diǎn)(@AbilityExtension):域能力的可擴(kuò)展點(diǎn),通常是方法級的擴(kuò)展,如針對于不同場景減庫存的邏輯是不一樣的,這個(gè)不同的邏輯處理就放到域能力擴(kuò)展點(diǎn)上來實(shí)現(xiàn)。
- 域能力實(shí)例(@AbilityInstance):域能力的子類實(shí)現(xiàn),理解為具象的域能力
上面的關(guān)鍵詞有點(diǎn)抽象,結(jié)合下面一句話來理解:小明可以搬運(yùn)100斤大米
這句話抽象出來:
- 小明是一個(gè)人,“人”即可視為一個(gè)領(lǐng)域,而小明則是“人”領(lǐng)域的一個(gè)實(shí)例。
- “搬運(yùn)貨物”視為“人”可以提供的服務(wù)(領(lǐng)域服務(wù)),從某一方面講“人”具備搬運(yùn)貨物的能力(域能力,除此之外人還具備看、吃、說話等能力)
- “可以搬運(yùn)100斤大米”這句話抽象出來是:“人”能搬運(yùn)多重的貨物,即域能力擴(kuò)展點(diǎn)?!叭恕蹦馨徇\(yùn)100斤重的貨物,即域能力實(shí)例。

示意代碼結(jié)構(gòu)如下(示例代碼 github:https://github.com/zhuqiuhui/extension-examples):
- Step 1:領(lǐng)域及領(lǐng)域服務(wù)定義
@Domain
public interface Human {
/**
* 搬運(yùn)貨物(領(lǐng)域服務(wù))
*/
@DomainService
public void carry();
}
public class FemaleHuman implements Human {
@Override
public void carry() {
// 1. 獲取搬運(yùn)貨物的能力
ICarryAbility carryAbility = getCarryAbility();
// 2. 搬運(yùn)貨物
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:搬運(yùn)貨物(可根據(jù)不同業(yè)務(wù)場景bizCode獲取不的擴(kuò)展點(diǎn))
ICarryBusinessExt iCarryBusinessExt = getICarryBusinessExt(bizCode);
iCarryBusinessExt.carry();
// Step 3:放置貨物
//......
}
}
- Step 3:定義擴(kuò)展點(diǎn)
public interface IExtensionPoints {
}
public interface ICarryBusinessExt extends IExtensionPoints {
/**
* 擴(kuò)展點(diǎn)實(shí)現(xiàn)類
*/
@AbilityExtension
void carry();
}
@AbilityInstance
public class XiaoMingExt implements ICarryBusinessExt {
@Override
public void carry() {
System.out.println("我能搬運(yùn)100斤");
}
}