SpringBoot+责任链 实现接口动态编排!

一、背景

项目中有一个 OpenApi 接口提供给客户(上游系统)调用。

这个接口中包含十几个功能点,比如:入参校验、系统配置校验、基本数据入库、核心数据入库、发送给消息中心、发送给 MQ.....

不同的客户对这个接口的要求也不同,有些功能不需要,有些需要添加特定功能。

二、思路

  • 基于以上背景,考虑把十几个功能点进行拆分形成独立的功能。因此使用责任链模式实现。

  • 创建一个抽象类(ComponentAbstract.java),每个拆分功能点继承抽象类形成子类。

  • 子类创建时,需要在 @Component("1") 注解中设置类名,如果不设置咋使用默认的(小驼峰)名称;关注公众号:码猿技术专栏,回复关键词:1111 获取阿里内部java性能调优手册

  • 子类之间的数据通信使用自定义的上下文类(Contxt.java)子类中可以对上下文数据进行修改。(业务解耦)

  • 通过事先定义好的执行顺序,通过 spring 的上下文 ApplicationContext 根据子类名称循环获取子类对象,执行抽象类中handlerRequest() 方法。

  • “事先定义好的执行顺序”,可以保存到数据库中项目启动的时候加载到内存,或者直接维护到Redis中。我这边直接使用接口进行演示:http://localhost:8082/test/chain?index=2,1,3,4

三、代码

maven依赖,没有特别的依赖fastjson用于测试时打印日志

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

ComponentAbstract.java 抽象类实现责任链的基础

import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

/**
 * 组件抽象类
 */
@Slf4j
publicabstractclass ComponentAbstract {

    public void handlerRequest(Contxt contxt) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 执行子类业务逻辑
        this.doHandler(contxt);
        stopWatch.stop();
        long cost = stopWatch.getTotalTimeMillis();
        if (cost <= 10) {
            log.info("-----------监控统方法执行时间,执行 {} 方法, 用时优秀: {} ms -----------", getClass(), cost);
        } elseif (cost <= 50) {
            log.info("-----------监控统方法执行时间,执行 {} 方法, 用时一般: {} ms -----------", getClass(), cost);
        } elseif (cost <= 500) {
            log.info("-----------监控统方法执行时间,执行 {} 方法, 用时延迟: {} ms -----------", getClass(), cost);
        } elseif (cost <= 1000) {
            log.info("-----------监控统法执行时间,执行 {} 方法, 用时缓慢: {} ms -----------", getClass(), cost);
        } else {
            log.info("-----------监控方法执行时间,执行 {} 方法, 用时卡顿: {} ms -----------", getClass(), cost);
        }
    }
    abstract public void doHandler(Contxt contxt);
}

Test1.java 业务类1,继承抽象类实现doHandler()方法,在@Component中设置类名1

import com.alibaba.fastjson.JSON;
import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component("1")
@Slf4j
publicclass Test1 extends ComponentAbstract {
    @Override
    public void doHandler(Contxt contxt) {
        log.info("Test1-顺序1-上下文内容为:{}", JSON.toJSONString(contxt));
        contxt.setName("Test1");
        contxt.setAge("Test1");
        contxt.setAdrss("Test1");
        contxt.setUserid("Test1");
    }
}

Test2.java 业务类2,继承抽象类实现doHandler()方法,在@Component中设置类名2

import com.alibaba.fastjson.JSON;
import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component("2")
@Slf4j
publicclass Test2 extends ComponentAbstract {

    @Override
    public void doHandler(Contxt contxt) {
        log.info("Test2-顺序2-上下文内容为:{}", JSON.toJSONString(contxt));
        contxt.setName("Test2");
        contxt.setAge("Test2");
        contxt.setAdrss("Test2");
        contxt.setUserid("Test2");
    }
}

Test3.java 业务类3,继承抽象类实现doHandler()方法,在@Component中设置类名3

import com.alibaba.fastjson.JSON;
import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component("3")
@Slf4j
publicclass Test3 extends ComponentAbstract {
    @Override
    public void doHandler(Contxt contxt) {
        log.info("Test3-顺序3-上下文内容为:{}", JSON.toJSONString(contxt));
        contxt.setName("Test3");
        contxt.setAge("Test3");
        contxt.setAdrss("Test3");
        contxt.setUserid("Test3");
    }
}

Test4.java 业务类4,继承抽象类实现doHandler()方法,在@Component中设置类名4

import com.alibaba.fastjson.JSON;
import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component("4")
@Slf4j
publicclass Test4 extends ComponentAbstract {

    @Override
    public void doHandler(Contxt contxt) {
        log.info("Test4-顺序4-上下文内容为:{}", JSON.toJSONString(contxt));
        contxt.setName("Test4");
        contxt.setAge("Test4");
        contxt.setAdrss("Test4");
        contxt.setUserid("Test4");
    }
}

Contxt.java 业务上下文,用于每个子类(每个功能点)之间的数据通信。需要什么数据可以在此类中添加字段进行写入,后面执行的类可以读取。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
publicclass Contxt {
    private String name;
    private String age;
    private String adrss;
    private String userid;
}

AopProxyUtils.java,spring 管理的上下文,用于根据类名获取类实体。

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
publicclass AopProxyUtils implements ApplicationContextAware {

    privatestatic ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的setApplicationContext方法,
     * 用于注入ApplicationContext。
     */
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }

    /**
     * 获取指定类的代理对象,适用于需要事务或其他AOP增强的场景。
     *
     * @param clazz 要获取代理的对象的类
     * @param <T>   泛型标记
     * @return 代理对象实例
     */
    publicstatic <T> T getProxyBean(Class<T> clazz) {
        if (applicationContext == null) {
            thrownew IllegalStateException("ApplicationContext not initialized.");
        }
        return applicationContext.getBean(clazz);
    }

    public static Object getProxyBean(String name) {
        return applicationContext.getBean(name);
    }
}

LiteFlowController.java 用于测试,演示如何动态编排。调用接口http://localhost:8082/test/chain?index=2,1,3,4 传入不同的index顺序,业务逻辑中执行的顺序也不同。

import com.alibaba.fastjson.JSON;
import com.liran.middle.liteflow.component.pattern.chain.ComponentAbstract;
import com.liran.middle.liteflow.slot.Contxt;
import com.liran.middle.liteflow.utils.AopProxyUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping(value = "/test")
@Slf4j
publicclass LiteFlowController {
    /**
     * 不使用框架,手动实现动态业务编排
     *
     * @param index 类名称
     * @return
     */
    @GetMapping(value = "chain")
    public String pattern(@RequestParam String index) {
        Contxt contxt = new Contxt().builder()
                .age("初始化")
                .adrss("初始化")
                .name("初始化")
                .userid("初始化")
                .age("初始化")
                .build();
        String[] split = index.split(",");

        for (String className : split) {
            // 此处直接根据类名从 spring 管理的上下文中进行获取。这里的类名是子类注解@Component("1")中自定义的,如果没有定义的话,默认使用类名
            // 使用这种方式可以保证类名不重复。
            ComponentAbstract msgHandler = (ComponentAbstract) AopProxyUtils.getProxyBean(className);
            if (ObjectUtils.isNotEmpty(msgHandler)) {
                msgHandler.handlerRequest(contxt);
            } else {
                log.info("没有找到对应的组件: {}", className);
            }
        }
        return JSON.toJSONString(contxt);
    }
}

四、注意

其实要实现这个功能使用 LiteFlow 框架最合适,文档友好,接入简单,功能强大。

LiteFlow 框架官网:

https://liteflow.cc/pages/5816c5/

作者提供的代码示例:

https://gitee.com/bryan31/liteflow-example

我是因为公司内部对依赖包的引入有要求审核严格,所以自己实现了一个简单版本的。

最后说一句(别白嫖,求关注)

陈某每一篇文章都是精心输出,如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!

另外陈某的知识星球开通了,加入只需199元,星球回馈的价值巨大,目前更新了Spring全家桶实战系列、亿级数据分库分表实战、DDD微服务实战专栏、我要进大厂、Spring,Mybatis等框架源码、架构实战22讲、精尽RocketMQ等....每增加一个专栏价格将上涨20元

需要加入星球的添加陈某微信:special_coder

图片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值