常用代码扩展点设计方式


在平时业务开中经常会遇到不同业务走不同的业务逻辑,为了代码的扩展性,不得不采取一些手段来对进行解耦,本文将介绍常用的代码扩展点实现方式,包括 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);
    }
}
  • 定义工厂类用于根据不同的类型获取不同的 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 目录下创建 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
    
  • 客户端调用示例代码

    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
    
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!");
    }
}
  • 在以下三个任意一个目录下定义文件: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
  • 客户端调用示例代码
public class DubboSpiTest {

    @Test
    public void testDubboSpi() {
        HumanService maleHumanService = ExtensionLoader.getExtensionLoader(HumanService.class)
                .getExtension("maleHumanService");
        maleHumanService.say();
    }
}

// 输出
this is man human say!

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

策略模式扩展点实现

这里和 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);
    }
}
  • 定义 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);
    }
}
  • 客户端调用示例代码
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");
    }
}
策略模式改进扩展点实现

使用策略模式更高级的做法将服务实例工厂类进行封装,做到业务无感多业务类型支持,示例中将各不同业务实现类统一由启动类 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 <T> 扩展类实例
     * @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);
    }
}

客户端调用代码如下:

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!

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");
    }
}
  • 客户端调用
@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);
    }   
}

附参考文档:

抽象业务扩展点实现方式

基础概念理解

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

  • 业务流程与业务活动: 用户完成某次业务操作的全过程,视为业务活动的编排。如用户执行一次下单操作包括:生成订单、营销优惠计算和库存扣减三个业务活动,业务活动即业务流程编排的基础单元。
  • 领域(@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();
    }
}
  • 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:放置货物
        //......
    }
}
  • 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斤");
    }
}

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
# 基于springboot的web项目最佳实践 + [web](#web) + [单元测试](#test) + [actuator应用监控](#actuator) + [lombok](#lombok) + [baseEntity](#baseEntity) + [统一响应返回值](#result) + [异常](#exception) + [数据校验](#validation) + [日志](#log) + [swagger](#swagger) + [数据库连接池](#datasource) + [spring jdbc](#jdbc) + [jpa](#jpa) + [redis](#redis) + [spring cache](#springcache) + [mogodb](#mogodb) + [mybatis](#mybatis) + [spring security](#security) + [项目上下文](#ContextHolder) + [单登录](#sso) + [邮件](#mail) + [maven](#maven) + [总结](#总结) `springboot` 可以说是现在做`javaweb`开发最火的技术,我在基于`springboot`搭建项目的过程中,踩过不少坑,发现整合框架时并非仅仅引入`starter` 那么简单。 要做到简单,易用,扩展性更好,还需做不少二次封装,于是便写了个基于`springboot`的web项目脚手架,对一些常用框架进行整合,并进行了简单的二次封装。 项目名`baymax`取自动画片超能陆战队里面的大白,大白是一个医护充气机器人,希望这个项目你能像大白一样贴心,可以减少你的工作量。 ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
首先,我们的VB项目源码提供了丰富的功能和模块,可以满足各种类型的应用需求。无论是办公自动化、数据管理、报表生成还是业务应用,我们的项目源码都提供了相应的功能和界面设计,使得开发人员可以快速构建符合需求的应用程序。 其次,我们的VB项目源码具有良好的可扩展性和可定制性。我们将项目源码设计为模块化的结构,开发人员可以根据自己的需求选择和定制所需的功能模块。同时,我们还提供了丰富的文档和示例代码,以帮助开发人员理解和使用项目源码。 我们的VB项目源码还采用了一些常用的技术和工具,以提高开发效率和代码质量。例如,我们使用了Visual Studio作为开发工具,它提供了一系列的功能和工具,如代码编辑器、调试器和界面设计器,使得开发人员可以快速开发和测试应用程序。我们还使用了VB作为主要的编程语言,它是一种易于学习和使用的语言,具有丰富的库和框架,使得开发人员可以轻松实现各种功能和特性。 此外,我们的VB项目源码还注重用户体验和界面设计。我们提供了一系列的界面模板和样式库,使得开发人员可以轻松创建漂亮而用户友好的应用界面。我们还提供了一些常用的界面组件和交互效果,以增加应用程序的交互性和吸引力。 总之,我们的VB项目源码旨在帮助开发人员快速构建各种类型的应用程序。无论是办公自动化、数据管理、报表生成还是业务应用,我们相信我们的项目源码将能够提供强大的支持和帮助。感谢您对我们项目的关注和支持!
Java重构代码经典设计模式是指在进行代码重构过程中,使用经典的设计模式来改善代码结构和提高代码的可维护性、可读性和可扩展性。其中,常用设计模式包括: 1. 单例模式:确保一个类只有一个实例,并提供全局访问,可以用于管理资源或配置信息。 2. 工厂模式:将对象的创建和使用分离,通过一个工厂类来创建具体的对象,提高代码的灵活性和可扩展性。 3. 观察者模式:定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动更新。 4. 装饰器模式:通过动态地为对象添加额外的功能,而不需要修改原始类,提高代码的灵活性和可维护性。 5. 策略模式:定义了一系列可相互替换的算法,使得算法的变化对客户端不可见,提高代码的可扩展性和可维护性。 6. 模板方法模式:定义了一个算法的骨架,将具体的实现延迟到子类中,提高代码的复用性和可扩展性。 7. 适配器模式:将一个类的接口转换成客户端所期望的另一个接口,使得原本不兼容的类能够一起工作。 8. 迭代器模式:提供一种统一的方法访问一个容器对象中的各个元素,而不暴露其内部的表示。 以上是Java重构代码常用设计模式,通过合理地应用这些设计模式,可以使得代码更加可读、可维护和可扩展,同时也能减少代码重复和冗余,提高开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bboyzqh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值