SpringBoot專案基於責任鏈模式實現複雜介面的解耦和動態編排

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

一、背景

專案中有一個 OpenApi 介面提供給客戶(上游系統)呼叫。
這個介面中包含十幾個功能點,比如:入參校驗、系統配置校驗、基本資料入庫、核心資料入庫、傳送給訊息中心、傳送給 MQ…..
不同的客戶對這個介面的要求也不同,有些功能不需要,有些需要新增特定功能。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

二、思路

  • 基於以上背景,考慮把十幾個功能點進行拆分形成獨立的功能。因此使用責任鏈模式實現。
  • 建立一個抽象類(ComponentAbstract.java),每個拆分功能點繼承抽象類形成子類。
  • 子類建立時,需要在 @Component("1") 註解中設定類名,如果不設定咋使用預設的(小駝峰)名稱
  • 子類之間的資料通訊使用自定義的上下文類(Contxt.java)子類中可以對上下文資料進行修改。(業務解耦)
  • 透過事先定義好的執行順序,透過 spring 的上下文 ApplicationContext 根據子類名稱迴圈獲取子類物件,執行抽象類中handlerRequest() 方法。
  • “事先定義好的執行順序”,可以儲存到資料庫中專案啟動的時候載入到記憶體,或者直接維護到Redis中。我這邊直接使用介面進行演示:http://localhost:8082/test/chain?index=2,1,3,4
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

三、程式碼

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;

/**

 * 元件抽象類

 */


@Slf

4j

publicabstractclassComponentAbstract

{

publicvoidhandlerRequest(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);

        }

    }

abstractpublicvoiddoHandler(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"

)

@Slf

4j

publicclassTest1extendsComponentAbstract

{

@Override
publicvoiddoHandler(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"

)

@Slf

4j

publicclassTest2extendsComponentAbstract

{

@Override
publicvoiddoHandler(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"

)

@Slf

4j

publicclassTest3extendsComponentAbstract

{

@Override
publicvoiddoHandler(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"

)

@Slf

4j

publicclassTest4extendsComponentAbstract

{

@Override
publicvoiddoHandler(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
publicclassContxt

{

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
publicclassAopProxyUtilsimplementsApplicationContextAware

{

privatestatic

 ApplicationContext applicationContext;

/**

     * 實現ApplicationContextAware介面的setApplicationContext方法,

     * 用於注入ApplicationContext。

     */


@Override
publicvoidsetApplicationContext(ApplicationContext context)throws BeansException 

{

        applicationContext = context;

    }

/**

     * 獲取指定類的代理物件,適用於需要事務或其他AOP增強的場景。

     *

     * 

@param

 clazz 要獲取代理的物件的類

     * 

@param

 <T>   泛型標記

     * 

@return

 代理物件例項

     */


publicstatic

 <T> 

getProxyBean(Class<T> clazz)

{

if

 (applicationContext == 

null

) {

thrownew

 IllegalStateException(

"ApplicationContext not initialized."

);

        }

return

 applicationContext.getBean(clazz);

    }

publicstatic 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"

)

@Slf

4j

publicclassLiteFlowController

{

/**

     * 不使用框架,手動實現動態業務編排

     *

     * 

@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
我是因為公司內部對依賴包的引入有要求稽核嚴格,所以自己實現了一個簡單版本的。

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章