拿差錯系統來講,大致上有核查、差錯提交、貸記調整、例外交易、例外複覈、收付調整等差錯交易類型,每一個差錯交易類型又分爲不少緣由碼,好比覈查有200一、220一、230一、250二、210二、240一、2402等緣由碼,每一個緣由碼還可能分有不一樣的子緣由碼。在接收到差錯交易請求時,因爲每一個差錯交易的發起方與接收方的不一樣,再加上代理清算、原交易狀態、清算狀態等各個維度的判斷,會致使整個差錯業務的校驗部分比較複雜,可是每一個差錯交易的總體邏輯是相同的。前期代碼的開發都是由不一樣的人員開發,代碼編寫能力也都不相同,致使總體代碼校驗部分看起來很是複雜,雖然已經對各個方法進行了封裝,但總體看起來仍然不太滿意,因此須要在業務穩定後,對原有代碼進行重構,增強模塊化與可擴展性。java
下面截圖爲差錯覈查的代碼,雖然已經有不少註釋了,可是並無一個很清晰的思路在裏面。git
在閱讀dubbo源碼的時候,會發現總體的結構很是清晰,並且對外提供了不少擴展點,它是怎麼作到的呢?如下內容若是看的不太懂,直接看下一章節後再回頭看。github
dubbo擴展都會被放置在下面幾個目錄spring
META-INF/dubbo/internal/ :用於dubbo內部提供的拓展實現設計模式
META-INF/dubbo/ :用於用戶自定義的拓展實現api
META-INF/service/ :Java SPI的配置目錄數組
下圖爲dubbo的Compiler接口的內部實現配置bash
另外須要知道三個註解及一個對象:@SPI,@Adaptive,@Activate,URL。框架
@SPI:定義一個接口是一個可被擴展的接口,@SPI註解中的值表明其默認的實現類,對應上面配置文件中的key。下圖爲Compile接口,默認實現爲javassist。ide
@Adaptive:分爲兩種
標記在類上,表明手工實現拓展實現類的邏輯,好比下圖Compile的拓展類實現AdaptiveCompiler,若是DEFAULT_COMPILE有值(就是對應的dubbo配置文件中的key),就使用其對應的實現類實現,若是沒有配置就使用默認的實現(對應SPI註解中配置的值)。
標記在方法上時,表明自動生成代碼實現該接口的拓展實現類(這個目前咱們能夠先不用,由於這塊的動態生成的代碼和dubbo內部的邏輯綁的很緊,有必要時能夠本身從新實現這塊功能)。
@Activate:表明自動激活,是什麼意思呢?拿Filter接口相關的服務來講,在調用ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension相關的方法時,返回一個根據getActivateExtension參數過濾出來的List。好比下圖中組裝過濾器時先根據key和group過濾出對應的過濾器數組,在進行其餘操做。
URL:dubbo內部將服務相關的全部變量放到了一個URL對象中,這樣很容易在各個環節進行處理,URL就至關於各個環節間通用的語言。
我是在研究dubbo源碼的時候,感覺到其spi機制在實現可擴展代碼時的便利與強大。而後回想當前的業務場景,是否是也能夠藉助這種模式來編寫出優美的可擴展性代碼。我下面拿dubbo spi的這一套實現一個最簡單的差錯交易demo。
假如咱們如今要實現一個最簡單的需求:實現覈查與差錯提交兩種交易,有2201和2301兩個緣由碼。這在目前的代碼中是覈查與差錯提交各用一個接口實現,而後在覈查中使用一個Map保存了2201和2301的校驗實現類,根據參數拿到緣由碼的實現類後在調用。下面開始借鑑dubbo spi機制實現上述需求。
先看下主要代碼及代碼運行結果,注意看紅線部分,咱們當前實現的需求是覈查的2301緣由碼:
好,如今需求變了,咱們要新增一個差錯提交的2201的緣由碼,怎麼辦?改代碼結構?不存在的,看下圖,只須要將disputeType和disputeReasonCode的值調整一下便可:
@Activate的做用是自動激活,是什麼意思先不要管,先看以下的執行結果,紅線部分是獲取groupName爲dispute的Filter實現列表,再組裝成一個Invoker執行鏈。先根據過濾器配置的order順序執行各個過濾器,最後再執行業務實現,也就是i'm last invoker這部分的邏輯。此處過濾器的裝配使用了責任鏈設計模式。
先看總體結構
META-INF/dubbo目錄中放的是咱們的擴展配置:
com.epcc.risk.api.biz.base.spi.disputeReasonCode.DisputeReasonCode文件:差錯緣由碼擴展配置
com.epcc.risk.api.biz.base.spi.disputeTran.DisputeTran文件:差錯交易擴展
com.epcc.risk.api.biz.base.spi.Filter文件:過濾器擴展配置
spi包下放的是幾個公共的類:
filter包下放的是過濾器實現:
disputeTran包下放的差錯交易實現:
disputeReasonCode包下放的是差錯緣由碼實現:
拿差錯交易的實現來講,獲取DisputeTran實現的代碼是
DisputeTran disputeTran = MyExtensionLoader.getExtensionLoader(DisputeTran.class).getAdaptiveExtension();
複製代碼
差錯交易配置文件中配置了覈查實現、差錯提交實現及差錯交易的拓展實現類:
inspectTran=com.epcc.risk.api.biz.base.spi.disputeTran.InspectTran
submitTran=com.epcc.risk.api.biz.base.spi.disputeTran.SubmitTran
adptiveDisputeTran=com.epcc.risk.api.biz.base.spi.disputeTran.AdaptiveDisputeTran
複製代碼
先看一下接口如何定義,接口類上有一個@SPI標記其是一個可擴展接口,默認拓展是inspectTran。
@SPI("inspectTran")
public interface DisputeTran {
/** * * @param context * @return */
public Result process(Context context);
}
複製代碼
再看下DisputeTran的拓展實現類,類上有一個@Adaptive註解標記其是一個拓展實現類,實現是若是contextType中有值,就使用該值找到實現拓展,若是沒有值,使用默認的拓展:
@Adaptive
public class AdaptiveDisputeTran implements DisputeTran {
@Override
public Result process(Context context) {
DisputeTran disputeTran;
ExtensionLoader<DisputeTran> loader = ExtensionLoader.getExtensionLoader(DisputeTran.class);
if (context.getDisputeType() != null && context.getDisputeType() != "") {
disputeTran = loader.getExtension(context.getDisputeType());
} else {
disputeTran = loader.getDefaultExtension();
}
return disputeTran.process(context);
}
}
複製代碼
再看下覈查交易的實現,打印了兩條日誌來表明其要作的事情。另外,差錯交易中用一樣的方式獲取了差錯緣由碼的實現,此處就不作講解了,原理相同。
@Slf4j
public class InspectTran implements DisputeTran {
DisputeReasonCode disputeReasonCode = ExtensionLoader.getExtensionLoader(DisputeReasonCode.class).getAdaptiveExtension();
@Override
public Result process(Context context) {
log.info("deal inspect");
disputeReasonCode.deal(context);
log.info("insert db");
return null;
}
}
複製代碼
再回過頭看主流程執行的代碼,其disputeType的值爲inspectTran時,便會根據拓展實現類中的邏輯,取配置文件中對應的com.epcc.risk.api.biz.base.spi.disputeTran.InspectTran爲實現類進行處理,disputeReasonCode值爲reasonCode2201時,一樣根據差錯緣由碼的拓展實現類邏輯,取com.epcc.risk.api.biz.base.spi.disputeReasonCode.inspect.ReasonCode2201爲實現類進行處理。當新增了差錯類型或差錯緣由碼時,無需改動主邏輯代碼,直接在配置文件中指定新增的差錯類型和差錯緣由碼的實現類便可。
@Test
public void test1() {
Context context = new Context();
context.setDisputeType("submitTran");
context.setDisputeReasonCode("reasonCode2201");
disputeTran.process(context);
}
複製代碼
Filter配置文件中配置了各個過濾器對應的過濾器實現:
logFilter=com.epcc.risk.api.biz.base.spi.filter.LogFilter
limitFilter=com.epcc.risk.api.biz.base.spi.filter.LimitFilter
tokenFilter=com.epcc.risk.api.biz.base.spi.filter.TokenFilter
複製代碼
先看Filter的實現,接口類上有一個@SPI標記其是一個可擴展接口。
@SPI
public interface Filter {
Result invoke(Invoker invoker, Context context);
default Result onResponse(Result result, Context invocation) {
return result;
}
}
複製代碼
再看其各個實現類的實現,這裏只列出LogFilter和LimitFilter,類上有@Activate註解表名其是一個自動激活的類,其groupName都是dispute(這裏的groupName是用來給獲取自動激活的列表提供過濾條件)。
@Slf4j
@Activate(group = "dispute", order = 999)
public class LogFilter implements Filter {
@Override
public Result invoke(Invoker invoker, Context context) {
log.info("i'm log filter, order is 999");
return invoker.invoke(context);
}
}
複製代碼
@Slf4j
@Activate(group = "dispute", order = 1000)
public class LimitFilter implements Filter {
@Override
public Result invoke(Invoker invoker, Context context) {
log.info("i'm limit filter, order is 1000");
return invoker.invoke(context);
}
}
複製代碼
這部份內容是徹底模擬dubbo spi的機制寫的一個簡單的可擴展程序的小demo,dubbo內部實現可擴展程序主要靠的是com.alibaba.dubbo.common.extension.ExtensionLoader,咱們徹底能夠按照其實現一個本身的可擴展程序。也能夠利用spring等框架實現一個相似的可擴展框架,目標是將代碼模塊化管理,插件式開發,盡力編寫出可擴展、可閱讀、可測試的高質量代碼。