真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

基于Spring實(shí)現(xiàn)管道模式的方法步驟

本篇內(nèi)容主要講解“基于Spring實(shí)現(xiàn)管道模式的方法步驟”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“基于Spring實(shí)現(xiàn)管道模式的方法步驟”吧!

成都創(chuàng)新互聯(lián)基于分布式IDC數(shù)據(jù)中心構(gòu)建的平臺(tái)為眾多戶(hù)提供重慶服務(wù)器托管 四川大帶寬租用 成都機(jī)柜租用 成都服務(wù)器租用。

管道模式(Pipeline Pattern)是責(zé)任鏈模式(Chain of Responsibility Pattern)的常用變體之一。在管道模式中,管道扮演著流水線(xiàn)的角色,將數(shù)據(jù)傳遞到一個(gè)加工處理序列中,數(shù)據(jù)在每個(gè)步驟中被加工處理后,傳遞到下一個(gè)步驟進(jìn)行加工處理,直到全部步驟處理完畢。

PS:純的責(zé)任鏈模式在鏈上只會(huì)有一個(gè)處理器用于處理數(shù)據(jù),而管道模式上多個(gè)處理器都會(huì)處理數(shù)據(jù)。

何時(shí)使用管道模式

任務(wù)代碼較為復(fù)雜,需要拆分為多個(gè)子步驟時(shí),尤其是后續(xù)可能在任意位置添加新的子步驟、刪除舊的子步驟、交換子步驟順序,可以考慮使用管道模式。

愉快地使用管道模式

  • 背景回放

最開(kāi)始做模型平臺(tái)的時(shí)候,創(chuàng)建模型實(shí)例的功能,包括:“輸入數(shù)據(jù)校驗(yàn) -> 根據(jù)輸入創(chuàng)建模型實(shí)例 -> 保存模型實(shí)例到相關(guān) DB 表”總共三個(gè)步驟,也不算復(fù)雜,所以當(dāng)時(shí)的代碼大概是這樣的:

public class ModelServiceImpl implements ModelService {

    /**
     * 提交模型(構(gòu)建模型實(shí)例)
     */
    public CommonReponse buildModelInstance(InstanceBuildRequest request) {
        // 輸入數(shù)據(jù)校驗(yàn)
        validateInput(request);
        // 根據(jù)輸入創(chuàng)建模型實(shí)例
        ModelInstance instance = createModelInstance(request);
        // 保存實(shí)例到相關(guān) DB 表
        saveInstance(instance);
    }
}

然而沒(méi)有過(guò)多久,我們發(fā)現(xiàn)表單輸入數(shù)據(jù)的格式并不完全符合模型的輸入要求,于是我們要加入 “表單數(shù)據(jù)的預(yù)處理”。這功能還沒(méi)動(dòng)手呢,又有業(yè)務(wù)方提出自己也存在需要對(duì)數(shù)據(jù)進(jìn)行處理的情況(比如根據(jù)商家的表單輸入,生成一些其他業(yè)務(wù)數(shù)據(jù)作為模型輸入)。

所以在 “輸入數(shù)據(jù)校驗(yàn)” 之后,還需要加入 “表單輸入輸出預(yù)處理” 和 “業(yè)務(wù)方自定義數(shù)據(jù)處理(可選)”。這個(gè)時(shí)候我就面臨一個(gè)選擇:是否繼續(xù)通過(guò)在 buildModelInstance 中加入新的方法來(lái)實(shí)現(xiàn)這些新的處理步驟?好處就是可以當(dāng)下偷懶,但是壞處呢:

  1. ModelService 應(yīng)該只用來(lái)接收 HSF 請(qǐng)求,而不應(yīng)該承載業(yè)務(wù)邏輯,如果將 提交模型 的邏輯都寫(xiě)在這個(gè)類(lèi)當(dāng)中,違反了 單一職責(zé),而且后面會(huì)導(dǎo)致 類(lèi)代碼爆炸。

  2. 將來(lái)每加入一個(gè)新的處理步驟或者刪除某個(gè)步驟,我就要修改 buildModelInstance 這個(gè)本應(yīng)該非常內(nèi)聚的方法,違反了 開(kāi)閉原則。

所以,為了不給以后的自己挖坑,我覺(jué)得要思考一個(gè)萬(wàn)全的方案。這個(gè)時(shí)候,我小腦袋花開(kāi)始飛轉(zhuǎn),突然閃過(guò)了 Netty 中的 ChannelPipeline —— 對(duì)哦,管道模式,不就正是我需要的嘛!

管道模式的實(shí)現(xiàn)方式也是多種多樣,接下來(lái)基于前面的背景,我分享一下我目前基于 Spring 實(shí)現(xiàn)管道模式的 “最佳套路”。

  • 定義管道處理的上下文

/**
 * 傳遞到管道的上下文
 */
@Getter
@Setter
public class PipelineContext {

    /**
     * 處理開(kāi)始時(shí)間
     */
    private LocalDateTime startTime;

    /**
     * 處理結(jié)束時(shí)間
     */
    private LocalDateTime endTime;

    /**
     * 獲取數(shù)據(jù)名稱(chēng)
     */
    public String getName() {
        return this.getClass().getSimpleName();
    }
}
  • 定義上下文處理器

/**
 * 管道中的上下文處理器
 */
public interface ContextHandler {

    /**
     * 處理輸入的上下文數(shù)據(jù)
     *
     * @param context 處理時(shí)的上下文數(shù)據(jù)
     * @return 返回 true 則表示由下一個(gè) ContextHandler 繼續(xù)處理,返回 false 則表示處理結(jié)束
     */
    boolean handle(T context);
}

為了方便說(shuō)明,我們現(xiàn)在先定義出最早版 【提交模型邏輯】 的上下文和相關(guān)處理器:

/**
 * 模型實(shí)例構(gòu)建的上下文
 */
@Getter
@Setter
public class InstanceBuildContext extends PipelineContext {

    /**
     * 模型 id
     */
    private Long modelId;

    /**
     * 用戶(hù) id
     */
    private long userId;

    /**
     * 表單輸入
     */
    private Map formInput;

    /**
     * 保存模型實(shí)例完成后,記錄下 id
     */
    private Long instanceId;

    /**
     * 模型創(chuàng)建出錯(cuò)時(shí)的錯(cuò)誤信息
     */
    private String errorMsg;

    // 其他參數(shù)

    @Override
    public String getName() {
        return "模型實(shí)例構(gòu)建上下文";
    }
}

處理器 - 輸入數(shù)據(jù)校驗(yàn):

@Component
public class InputDataPreChecker implements ContextHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handle(InstanceBuildContext context) {
        logger.info("--輸入數(shù)據(jù)校驗(yàn)--");

        Map formInput = context.getFormInput();

        if (MapUtils.isEmpty(formInput)) {
            context.setErrorMsg("表單輸入數(shù)據(jù)不能為空");
            return false;
        }

        String instanceName = (String) formInput.get("instanceName");

        if (StringUtils.isBlank(instanceName)) {
            context.setErrorMsg("表單輸入數(shù)據(jù)必須包含實(shí)例名稱(chēng)");
            return false;
        }

        return true;
    }
}

處理器 - 根據(jù)輸入創(chuàng)建模型實(shí)例:

@Component
public class ModelInstanceCreator implements ContextHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handle(InstanceBuildContext context) {
        logger.info("--根據(jù)輸入數(shù)據(jù)創(chuàng)建模型實(shí)例--");

        // 假裝創(chuàng)建模型實(shí)例

        return true;
    }
}

處理器 - 保存模型實(shí)例到相關(guān)DB表:

@Component
public class ModelInstanceSaver implements ContextHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handle(InstanceBuildContext context) {
        logger.info("--保存模型實(shí)例到相關(guān)DB表--");

        // 假裝保存模型實(shí)例

        return true;
    }
}

到這里,有個(gè)問(wèn)題就出現(xiàn)了:應(yīng)該使用什么樣的方式,將同一種 Context 的 ContextHandler 串聯(lián)為管道呢?思考一下:

  1. 給 ContextHandler 加一個(gè) setNext 方法,每個(gè)實(shí)現(xiàn)類(lèi)必須指定其下一個(gè)處理器。缺點(diǎn)也很明顯,如果在當(dāng)前管道中間加入一個(gè)新的 ContextHandler,那么要?jiǎng)荼匾薷那耙粋€(gè) ContextHandler 的 setNext 方法;另外,代碼是寫(xiě)給人閱讀的,這樣做沒(méi)法一眼就直觀的知道整個(gè)管道的處理鏈路,還要進(jìn)入到每個(gè)相關(guān)的 ContextHandler 中去查看才知道。

  2. 給 ContextHandler 加上 @Order 注解,根據(jù) @Order 中給定的數(shù)字來(lái)確定每個(gè) ContextHandler 的序列,一開(kāi)始時(shí)每個(gè)數(shù)字間隔的可以大些(比如 10、20、30),后續(xù)加入新的 ContextHandler 時(shí),可以指定數(shù)字為 (11、21、31)這種,那么可以避免上面方案中要修改代碼的問(wèn)題,但是仍然無(wú)法避免要進(jìn)入每個(gè)相關(guān)的 ContextHandler 中去查看才能知道管道處理鏈路的問(wèn)題。

  3. 提前寫(xiě)好一份路由表,指定好 ”Context -> 管道“ 的映射(管道用 List 來(lái)表示),以及管道中處理器的順序 。Spring 來(lái)根據(jù)這份路由表,在啟動(dòng)時(shí)就構(gòu)建好一個(gè) Map,Map 的鍵為 Context 的類(lèi)型,值為 管道(即 List)。這樣的話(huà),如果想知道每個(gè)管道的處理鏈路,直接看這份路由表就行,一目了然。缺點(diǎn)嘛,就是每次加入新的 ContextHandler 時(shí),這份路由表也需要在對(duì)應(yīng)管道上進(jìn)行小改動(dòng) —— 但是如果能讓閱讀代碼更清晰,我覺(jué)得這樣的修改是值得的、可接受的~

  • 構(gòu)建管道路由表

基于 Spring 的 Java Bean 配置,我們可以很方便的構(gòu)建管道的路由表:

/**
 * 管道路由的配置
 */
@Configuration
public class PipelineRouteConfig implements ApplicationContextAware {

    /**
     * 數(shù)據(jù)類(lèi)型->管道中處理器類(lèi)型列表 的路由
     */
    private static final
    Map,
        List>>> PIPELINE_ROUTE_MAP = new HashMap<>(4);

    /*
     * 在這里配置各種上下文類(lèi)型對(duì)應(yīng)的處理管道:鍵為上下文類(lèi)型,值為處理器類(lèi)型的列表
     */
    static {
        PIPELINE_ROUTE_MAP.put(InstanceBuildContext.class,
                               Arrays.asList(
                                       InputDataPreChecker.class,
                                       ModelInstanceCreator.class,
                                       ModelInstanceSaver.class
                               ));

        // 將來(lái)其他 Context 的管道配置
    }

    /**
     * 在 Spring 啟動(dòng)時(shí),根據(jù)路由表生成對(duì)應(yīng)的管道映射關(guān)系
     */
    @Bean("pipelineRouteMap")
    public Map, List>> getHandlerPipelineMap() {
        return PIPELINE_ROUTE_MAP.entrySet()
                                 .stream()
                                 .collect(Collectors.toMap(Map.Entry::getKey, this::toPipeline));
    }

    /**
     * 根據(jù)給定的管道中 ContextHandler 的類(lèi)型的列表,構(gòu)建管道
     */
    private List> toPipeline(
            Map.Entry, List>>> entry) {
        return entry.getValue()
                    .stream()
                    .map(appContext::getBean)
                    .collect(Collectors.toList());
    }

    private ApplicationContext appContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
}
  • 定義管道執(zhí)行器

最后一步,定義管道執(zhí)行器。管道執(zhí)行器 根據(jù)傳入的上下文數(shù)據(jù)的類(lèi)型,找到其對(duì)應(yīng)的管道,然后將上下文數(shù)據(jù)放入管道中去進(jìn)行處理。

/**
 * 管道執(zhí)行器
 */
@Component
public class PipelineExecutor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 引用 PipelineRouteConfig 中的 pipelineRouteMap
     */
    @Resource
    private Map,
                List>> pipelineRouteMap;

    /**
     * 同步處理輸入的上下文數(shù)據(jù)
     * 如果處理時(shí)上下文數(shù)據(jù)流通到最后一個(gè)處理器且最后一個(gè)處理器返回 true,則返回 true,否則返回 false      *      * @param context 輸入的上下文數(shù)據(jù)      * @return 處理過(guò)程中管道是否暢通,暢通返回 true,不暢通返回 false      */     public boolean acceptSync(PipelineContext context) {         Objects.requireNonNull(context, "上下文數(shù)據(jù)不能為 null");         // 拿到數(shù)據(jù)類(lèi)型         Class dataType = context.getClass();         // 獲取數(shù)據(jù)處理管道         List> pipeline = pipelineRouteMap.get(dataType);         if (CollectionUtils.isEmpty(pipeline)) {             logger.error("{} 的管道為空", dataType.getSimpleName());             return false;         }         // 管道是否暢通         boolean lastSuccess = true;         for (ContextHandler handler : pipeline) {             try {                 // 當(dāng)前處理器處理數(shù)據(jù),并返回是否繼續(xù)向下處理                 lastSuccess = handler.handle(context);             } catch (Throwable ex) {                 lastSuccess = false;                 logger.error("[{}] 處理異常,handler={}", context.getName(), handler.getClass().getSimpleName(), ex);             }             // 不再向下處理             if (!lastSuccess) { break; }         }         return lastSuccess;     } }
  • 使用管道模式

此時(shí),我們可以將最開(kāi)始的 buildModelInstance 修改為:

public CommonResponse buildModelInstance(InstanceBuildRequest request) {
    InstanceBuildContext data = createPipelineData(request);
    boolean success = pipelineExecutor.acceptSync(data);

    // 創(chuàng)建模型實(shí)例成功
    if (success) {
        return CommonResponse.success(data.getInstanceId());
    }

    logger.error("創(chuàng)建模式實(shí)例失?。簕}", data.getErrorMsg());
    return CommonResponse.failed(data.getErrorMsg());
}

這個(gè)時(shí)候我們?cè)贋?InstanceBuildContext 加入新的兩個(gè) ContextHandler:FormInputPreprocessor(表單輸入數(shù)據(jù)預(yù)處理) 和 BizSideCustomProcessor(業(yè)務(wù)方自定義數(shù)據(jù)處理)。

@Component
public class FormInputPreprocessor implements ContextHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handle(InstanceBuildContext context) {
        logger.info("--表單輸入數(shù)據(jù)預(yù)處理--");

        // 假裝進(jìn)行表單輸入數(shù)據(jù)預(yù)處理

        return true;
    }
}
@Component
public class BizSideCustomProcessor implements ContextHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean handle(InstanceBuildContext context) {
        logger.info("--業(yè)務(wù)方自定義數(shù)據(jù)處理--");

        // 先判斷是否存在自定義數(shù)據(jù)處理,如果沒(méi)有,直接返回 true

        // 調(diào)用業(yè)務(wù)方的自定義的表單數(shù)據(jù)處理

        return true;
    }
}

此時(shí) buildModelInstance 不需要做任何修改,我們只需要在 “路由表” 里面,將這兩個(gè) ContextHandler 加入到 InstanceBuildContext 關(guān)聯(lián)的管道中,Spring 啟動(dòng)的時(shí)候,會(huì)自動(dòng)幫我們構(gòu)建好每種 Context 對(duì)應(yīng)的管道

總結(jié)

通過(guò)管道模式,我們大幅降低了系統(tǒng)的耦合度和提升了內(nèi)聚程度與擴(kuò)展性:

ModelService 只負(fù)責(zé)處理 HSF 請(qǐng)求,不用關(guān)心具體的業(yè)務(wù)邏輯 PipelineExecutor 只做執(zhí)行工作,不用關(guān)心具體的管道細(xì)節(jié) 每個(gè) ContextHandler 只負(fù)責(zé)自己那部分的業(yè)務(wù)邏輯,不需要知道管道的結(jié)構(gòu),與其他ContextHandler 的業(yè)務(wù)邏輯解耦 新增、刪除 或者 交換子步驟時(shí),都只需要操作路由表的配置,而不要修改原來(lái)的調(diào)用代碼

到此,相信大家對(duì)“基于Spring實(shí)現(xiàn)管道模式的方法步驟”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!


本文名稱(chēng):基于Spring實(shí)現(xiàn)管道模式的方法步驟
文章鏈接:http://weahome.cn/article/jpssig.html

其他資訊

在線(xiàn)咨詢(xún)

微信咨詢(xún)

電話(huà)咨詢(xún)

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部