• 常用代码扩展点设计方式



    在平时业务开中经常会遇到不同业务走不同的业务逻辑,为了代码的扩展性,不得不采取一些手段来对进行解耦,本文将介绍常用的代码扩展点实现方式,包括 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 定义工厂类用于根据不同的类型获取不同的 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 在 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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 客户端调用示例代码

      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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    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!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 在以下三个任意一个目录下定义文件: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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 客户端调用示例代码
    public class DubboSpiTest {
    
        @Test
        public void testDubboSpi() {
            HumanService maleHumanService = ExtensionLoader.getExtensionLoader(HumanService.class)
                    .getExtension("maleHumanService");
            maleHumanService.say();
        }
    }
    
    // 输出
    this is man human say!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    策略模式及改进版扩展点实现

    策略模式扩展点实现

    这里和 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 定义 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 客户端调用示例代码
    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    策略模式改进扩展点实现

    使用策略模式更高级的做法将服务实例工厂类进行封装,做到业务无感多业务类型支持,示例中将各不同业务实现类统一由启动类 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  扩展类实例
         * @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    客户端调用代码如下:

    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!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Cola 扩展点设计

    1)cola 框架简介

    cola 框架是以 DDD 思想为依据定义了应用工程就有的框架和组件,为业务应用工程提供了参考,可以详细参考 cola 的官方文档。cola 2.0 的扩展点支持到了“业务身份”,“用例”,“场景”的三级扩展,详细介绍参考:https://blog.csdn.net/significantfrank/article/details/100074716

    在这里插入图片描述

    2)示例代码

    示例代码详细参考 cola 框架源码地址:cola扩展点示例代码 github地址

    • 定义扩展点 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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 客户端调用
    @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);
        }   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    附参考文档:

    抽象业务扩展点实现方式

    基础概念理解

    扩展点的实现离不开业务,业务的扩展点需要更高的抽象才能支持得更灵活,先明确几个关键词:

    • 业务流程与业务活动: 用户完成某次业务操作的全过程,视为业务活动的编排。如用户执行一次下单操作包括:生成订单、营销优惠计算和库存扣减三个业务活动,业务活动即业务流程编排的基础单元。
    • 领域(@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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 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:放置货物
            //......
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 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斤");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如转载,请注明出处!欢迎关v信公众号:方辰的博客

  • 相关阅读:
    【CGAL_网格】Surface_mesh
    d合并json
    Autosar模块介绍:AutosarOS(4)
    R数据分析:如何简洁高效地展示统计结果
    xxl-job重复执行问题
    1.1 大数据简介-hadoop-最全最完整的保姆级的java大数据学习资料
    集合—Vector底层结构和源码分析
    MySQL 基础知识(八)之用户权限管理
    方案设计|汽车轮胎数显胎压计方案
    D - Letter Picking codefoeces 1728D
  • 原文地址:https://blog.csdn.net/zhuqiuhui/article/details/126233200