拿差错系统来讲,大致上有核查、差错提交、贷记调整、例外交易、例外复核、收付调整等差错交易类型,每一个差错交易类型又分为不少缘由码,好比核查有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等框架实现一个相似的可扩展框架,目标是将代码模块化管理,插件式开发,尽力编写出可扩展、可阅读、可测试的高质量代码。