编写可扩展程序

痛点

​ 拿差错系统来讲,大致上有核查、差错提交、贷记调整、例外交易、例外复核、收付调整等差错交易类型,每一个差错交易类型又分为不少缘由码,好比核查有200一、220一、230一、250二、210二、240一、2402等缘由码,每一个缘由码还可能分有不一样的子缘由码。在接收到差错交易请求时,因为每一个差错交易的发起方与接收方的不一样,再加上代理清算、原交易状态、清算状态等各个维度的判断,会致使整个差错业务的校验部分比较复杂,可是每一个差错交易的总体逻辑是相同的。前期代码的开发都是由不一样的人员开发,代码编写能力也都不相同,致使总体代码校验部分看起来很是复杂,虽然已经对各个方法进行了封装,但总体看起来仍然不太满意,因此须要在业务稳定后,对原有代码进行重构,增强模块化与可扩展性。java

​ 下面截图为差错核查的代码,虽然已经有不少注释了,可是并无一个很清晰的思路在里面。git

img

dubbo框架的SPI机制

​ 在阅读dubbo源码的时候,会发现总体的结构很是清晰,并且对外提供了不少扩展点,它是怎么作到的呢?如下内容若是看的不太懂,直接看下一章节后再回头看。github

​ dubbo扩展都会被放置在下面几个目录spring

  1. META-INF/dubbo/internal/ :用于dubbo内部提供的拓展实现设计模式

  2. META-INF/dubbo/ :用于用户自定义的拓展实现api

  3. META-INF/service/ :Java SPI的配置目录数组

    下图为dubbo的Compiler接口的内部实现配置bash

    img

​ 另外须要知道三个注解及一个对象:@SPI,@Adaptive,@Activate,URL。框架

@SPI:定义一个接口是一个可被扩展的接口,@SPI注解中的值表明其默认的实现类,对应上面配置文件中的key。下图为Compile接口,默认实现为javassist。ide

1552302968617

@Adaptive:分为两种

  1. 标记在类上,表明手工实现拓展实现类的逻辑,好比下图Compile的拓展类实现AdaptiveCompiler,若是DEFAULT_COMPILE有值(就是对应的dubbo配置文件中的key),就使用其对应的实现类实现,若是没有配置就使用默认的实现(对应SPI注解中配置的值)。

    img

  2. 标记在方法上时,表明自动生成代码实现该接口的拓展实现类(这个目前咱们能够先不用,由于这块的动态生成的代码和dubbo内部的逻辑绑的很紧,有必要时能够本身从新实现这块功能)。

@Activate:表明自动激活,是什么意思呢?拿Filter接口相关的服务来讲,在调用ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension相关的方法时,返回一个根据getActivateExtension参数过滤出来的List。好比下图中组装过滤器时先根据key和group过滤出对应的过滤器数组,在进行其余操做。

img

URL:dubbo内部将服务相关的全部变量放到了一个URL对象中,这样很容易在各个环节进行处理,URL就至关于各个环节间通用的语言。

借鉴dubbo spi机制编写可扩展的代码

​ 我是在研究dubbo源码的时候,感觉到其spi机制在实现可扩展代码时的便利与强大。而后回想当前的业务场景,是否是也能够借助这种模式来编写出优美的可扩展性代码。我下面拿dubbo spi的这一套实现一个最简单的差错交易demo。

​ 假如咱们如今要实现一个最简单的需求:实现核查与差错提交两种交易,有2201和2301两个缘由码。这在目前的代码中是核查与差错提交各用一个接口实现,而后在核查中使用一个Map保存了2201和2301的校验实现类,根据参数拿到缘由码的实现类后在调用。下面开始借鉴dubbo spi机制实现上述需求。

运行结果

自动拓展运行结果

先看下主要代码及代码运行结果,注意看红线部分,咱们当前实现的需求是核查的2301缘由码:

1552310720159

好,如今需求变了,咱们要新增一个差错提交的2201的缘由码,怎么办?改代码结构?不存在的,看下图,只须要将disputeType和disputeReasonCode的值调整一下便可:

img

自动激活过滤器运行结果

@Activate的做用是自动激活,是什么意思先不要管,先看以下的执行结果,红线部分是获取groupName为dispute的Filter实现列表,再组装成一个Invoker执行链。先根据过滤器配置的order顺序执行各个过滤器,最后再执行业务实现,也就是i'm last invoker这部分的逻辑。此处过滤器的装配使用了责任链设计模式。

img

代码结构介绍

先看总体结构

img

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包下放的是几个公共的类:

  • DisputeTranTest:测试类入口
  • Filter:过滤器接口
  • Invoker:业务执行接口
  • Context:差错业务类,相似于dubbo中的URL
  • MyExtensionLoader:等同dubbo中的ExtensionLoader,将其复制了一份,新增了一个getActivateExtension方法用来作实验用,由于其余的getActivateExtension都带有各类参数,同dubbo框架绑的比较死。

filter包下放的是过滤器实现:

  • LimitFilter:模拟限流过滤器
  • LogFilter:模拟日志过滤器
  • TokenFilter:模拟Token过滤器

disputeTran包下放的差错交易实现:

  • AdaptiveDisputeTran:差错交易拓展实现类
  • DisputeTran:差错交易接口
  • InspectTran:核查接口实现
  • SubmitTran:差错提交接口实现

disputeReasonCode包下放的是差错缘由码实现:

  • AdaptiveDisputeTran:差错缘由码拓展实现类
  • DisputeReasonCode:差错缘由码接口
  • ReasonCode2201:差错缘由码2201的实现
  • ReasonCode2301:差错缘由码2301的实现

代码实现

自动拓展代码实现

拿差错交易的实现来讲,获取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等框架实现一个相似的可扩展框架,目标是将代码模块化管理,插件式开发,尽力编写出可扩展、可阅读、可测试的高质量代码。

代码

github.com/ls960972314… 包下。

相关文章
相关标签/搜索