⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7402435366647758857 「栗然」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

一、背景

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

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

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

二、思路

  • 基于以上背景,考虑把十几个功能点进行拆分形成独立的功能。因此使用责任链模式实现。
  • 创建一个抽象类(ComponentAbstract.java),每个拆分功能点继承抽象类形成子类。
  • 子类创建时,需要在 @Component("1") 注解中设置类名,如果不设置咋使用默认的(小驼峰)名称
  • 子类之间的数据通信使用自定义的上下文类(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
public abstract class 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);
} else if (cost <= 50) {
log.info("-----------监控统方法执行时间,执行 {} 方法, 用时一般: {} ms -----------", getClass(), cost);
} else if (cost <= 500) {
log.info("-----------监控统方法执行时间,执行 {} 方法, 用时延迟: {} ms -----------", getClass(), cost);
} else if (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
public class 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
public class 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
public class 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
public class 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
public class 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
public class AopProxyUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext;

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

/**
* 获取指定类的代理对象,适用于需要事务或其他AOP增强的场景。
*
* @param clazz 要获取代理的对象的类
* @param <T> 泛型标记
* @return 代理对象实例
*/
public static <T> T getProxyBean(Class<T> clazz) {
if (applicationContext == null) {
throw new 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
public class 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

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

文章目录
  1. 1. 一、背景
  2. 2. 二、思路
  3. 3. 三、代码
  4. 4. 四、注意