曰本美女∴一区二区特级A级黄色大片, 国产亚洲精品美女久久久久久2025, 页岩实心砖-高密市宏伟建材有限公司, 午夜小视频在线观看欧美日韩手机在线,国产人妻奶水一区二区,国产玉足,妺妺窝人体色WWW网站孕妇,色综合天天综合网中文伊,成人在线麻豆网观看

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

關(guān)于編程模式的總結(jié)與思考

OSC開源社區(qū) ? 來源: 大淘寶技術(shù) ? 2024-01-03 10:14 ? 次閱讀

淘寶創(chuàng)新業(yè)務(wù)的優(yōu)化迭代是非常高頻且迅速的,在這過程中要求技術(shù)也必須是快且穩(wěn)的,而為了適應(yīng)這種快速變化的節(jié)奏,我們?cè)陧?xiàng)目開發(fā)過程中采用了一些面向拓展以及敏捷開發(fā)的設(shè)計(jì),本文旨在總結(jié)并思考其中一些通用的編程模式。

前言

靜心守護(hù)業(yè)務(wù)是淘寶今年4月份啟動(dòng)的創(chuàng)新項(xiàng)目,項(xiàng)目的核心邏輯是通過敲木魚、冥想、盤手串等療愈玩法為用戶帶來內(nèi)心寧靜的同時(shí)推動(dòng)文物的保護(hù)與修復(fù),進(jìn)一步弘揚(yáng)我們的傳統(tǒng)文化。

作為創(chuàng)新項(xiàng)目,業(yè)務(wù)形態(tài)與產(chǎn)品方案的優(yōu)化迭代是非常高頻且迅速的:項(xiàng)目從4月底投入開發(fā)到7月份最終外灰,整體方案經(jīng)歷過大的推倒重建,也經(jīng)歷過多輪小型重構(gòu)優(yōu)化,項(xiàng)目上線后也在做持續(xù)的迭代優(yōu)化甚至改版升級(jí)。

模式清單

基于Spring容器與反射的策略模式

策略模式是一種經(jīng)典的行為設(shè)計(jì)模式,它的本質(zhì)是定義一系列算法, 并將每種算法分別放入獨(dú)立的類中, 以使算法的對(duì)象能夠相互替換,后續(xù)也能根據(jù)需要靈活拓展出新的算法。這里推薦的是一種基于Spring容器和反射結(jié)合的策略模式,這種模式的核心思路是:每個(gè)策略模式的實(shí)現(xiàn)都是一個(gè)bean,在Spring容器啟動(dòng)時(shí)基于反射獲取每個(gè)策略場(chǎng)景的接口類型,并基于該接口類型再獲取此類型的所有策略實(shí)現(xiàn)bean并記錄到一個(gè)map(key為該策略bean的唯一標(biāo)識(shí)符,value為bean對(duì)象)中,后續(xù)可以自定義路由策略來從該map中獲取bean對(duì)象并使用相應(yīng)的策略。

模式解構(gòu)

模式具體實(shí)現(xiàn)方式大致如下面的UML類圖所描述的:

07fb23a4-a964-11ee-8b88-92fbcf53809c.png

其中涉及的各個(gè)組件及作用分別為:

Handler(interface):策略的頂層接口,定義的type方法表示策略唯一標(biāo)識(shí)的獲取方式。

HandlerFactory(abstract class):策略工廠的抽象實(shí)現(xiàn),封裝了反射獲取Spring bean并維護(hù)策略與其標(biāo)識(shí)映射的邏輯,但不感知策略的真實(shí)類型。

AbstractHandler(interface or abstracr class):各個(gè)具體場(chǎng)景下的策略接口定義,該接口定義了具體場(chǎng)景下策略所需要完成的行為。如果各個(gè)具體策略實(shí)現(xiàn)有可復(fù)用的邏輯,可以結(jié)合模版方法模式在該接口內(nèi)定義模版方法,如果模板方法依賴外部bean注入,則該接口的類型需要為abstract class,否則為interface即可。

HandlerImpl(class):各個(gè)場(chǎng)景下策略接口的具體實(shí)現(xiàn),承載主要的業(yè)務(wù)邏輯,也可以根據(jù)需要橫向拓展。

HandlerFactoryImpl(class):策略工廠的具體實(shí)現(xiàn),感知具體場(chǎng)景策略接口的類型,如果有定制的策略路由邏輯也可以在此實(shí)現(xiàn)。

這種模式的主要優(yōu)點(diǎn)有:

策略標(biāo)識(shí)維護(hù)自動(dòng)化:策略實(shí)現(xiàn)與標(biāo)識(shí)之間的映射關(guān)系完全委托給Spring容器進(jìn)行維護(hù)(在HandlerFactory中封裝,每個(gè)場(chǎng)景的策略工廠直接繼承該類即可,無需重復(fù)實(shí)現(xiàn)),后續(xù)新增策略不用再手動(dòng)修改關(guān)系映射。

場(chǎng)景維度維護(hù)標(biāo)識(shí)映射:HandlerFactory中在掃描策略bean時(shí)是按照AbstractHandler的類型來分類維護(hù)的,從而避免了不同場(chǎng)景的同名策略發(fā)生沖突。

策略接口按場(chǎng)景靈活定義:具體場(chǎng)景的策略行為定義在AbstractHandler中,在這里可以根據(jù)真實(shí)的業(yè)務(wù)需求靈活定義行為,甚至也可以結(jié)合其他設(shè)計(jì)模式做進(jìn)一步抽象處理,在提供靈活拓展的同時(shí)減少重復(fù)代碼。

實(shí)踐案例分析

該模式在靜心守護(hù)項(xiàng)目中的許多功能模塊都有使用,下面以稱號(hào)解鎖模塊為例來介紹其實(shí)際應(yīng)用。

我們先簡單了解下該模塊的業(yè)務(wù)背景:靜心守護(hù)的成就體系中有一類是稱號(hào),如下圖。用戶可以通過多種行為去解鎖不同類型的稱號(hào),比如說通過參與主玩法(敲木魚、冥想、盤手串),主玩法參與達(dá)到一定次數(shù)后即可解鎖特定類型的稱號(hào)。當(dāng)然后續(xù)也可能會(huì)有其他種類的稱號(hào):比如簽到類(按照用戶簽到天數(shù)解鎖)、捐贈(zèng)類(按照用戶捐贈(zèng)項(xiàng)目的行為解鎖),所以對(duì)于稱號(hào)的解鎖操作應(yīng)該是面向未來可持續(xù)拓展的。

基于這樣的思考,我選擇使用上面的策略模式去實(shí)現(xiàn)稱號(hào)解鎖模塊。該模塊的核心類圖組織如下:

083ae908-a964-11ee-8b88-92fbcf53809c.png

下面是其中部分核心代碼的分析解讀:

public interface Handler {
    /**
     * handler類型
     *
     * @return
     */
    T type();
}
如上文所說,Handler是策略的頂層抽象,它只定義了type方法,該方法用于獲取策略的標(biāo)識(shí),標(biāo)識(shí)的類型支持子接口定義。
@Slf4j
public abstract class HandlerFactory> implements InitializingBean, ApplicationContextAware {
    private Map handlerMap;


    private ApplicationContext appContext;


    /**
     * 根據(jù) type 獲得對(duì)應(yīng)的handler
     *
     * @param type
     * @return
     */
    public H getHandler(T type) {
        return handlerMap.get(type);
    }


    /**
     * 根據(jù) type 獲得對(duì)應(yīng)的handler,支持返回默認(rèn)
     *
     * @param type
     * @param defaultHandler
     * @return
     */
    public H getHandlerOrDefault(T type, H defaultHandler) {
        return handlerMap.getOrDefault(type, defaultHandler);
    }


    /**
     * 反射獲取泛型參數(shù)handler類型
     *
     * @return handler類型
     */
    @SuppressWarnings("unchecked")
    protected Class getHandlerType() {
        Type type = ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        //策略接口使用了范型參數(shù)
        if (type instanceof ParameterizedTypeImpl) {
            return (Class) ((ParameterizedTypeImpl)type).getRawType();
        } else {
            return (Class) type;
        }
    }


    @Override
    public void afterPropertiesSet() {
        // 獲取所有 H 類型的 handlers
        Collection handlers = appContext.getBeansOfType(getHandlerType()).values();


        handlerMap = Maps.newHashMapWithExpectedSize(handlers.size());


        for (final H handler : handlers) {
            log.info("HandlerFactory {}, {}", this.getClass().getCanonicalName(), handler.type());
            handlerMap.put(handler.type(), handler);
        }
        log.info("handlerMap:{}", JSON.toJSONString(handlerMap));


    }


    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        this.appContext = applicationContext;
    }
}

HandlerFactory在前面也提到過,是策略工廠的抽象實(shí)現(xiàn),封裝了反射獲取具體場(chǎng)景策略接口類型,并查找策略bean在內(nèi)存中維護(hù)策略與其標(biāo)識(shí)的映射關(guān)系,后續(xù)可以直接通過標(biāo)識(shí)或者對(duì)應(yīng)的策略實(shí)現(xiàn)。這里有二個(gè)細(xì)節(jié):

為什么HandlerFactory是abstract class?其實(shí)可以看到該類并沒有任何抽象方法,直接將其定義為class也不會(huì)有什么問題。這里將其定義為abstract class主要是起到實(shí)例創(chuàng)建的約束作用,因?yàn)槲覀儗?duì)該類的定義是工廠的抽象實(shí)現(xiàn),只希望針對(duì)具體場(chǎng)景來創(chuàng)建實(shí)例,針對(duì)該工廠本身創(chuàng)建實(shí)例其實(shí)是沒有任何實(shí)際意義的。

getHandlerType方法使用了@SuppressWarnings注解并標(biāo)記了unchecked。這里也確實(shí)是存在潛在風(fēng)險(xiǎn)的,因?yàn)門ype類型轉(zhuǎn)Class類型屬于向下類型轉(zhuǎn)換,是存在風(fēng)險(xiǎn)的,可能其實(shí)際類型并非Class而是其他類型,那么此處強(qiáng)轉(zhuǎn)就會(huì)出錯(cuò)。這里處理了兩種最通用的情況:AbstractHandler是帶范型的class和最普通的class。

@Component
public class TitleUnlockHandlerFactory
        extends HandlerFactory> {}
TitleUnlockHandlerFactory是策略工廠的具體實(shí)現(xiàn),由于不需要在此定制策略的路由邏輯,所以只聲明了相關(guān)的參數(shù)類型,而沒有對(duì)父類的方法做什么覆蓋。
public abstract class BaseTitleUnlockHandler implements Handler {


    @Resource
    private UserTitleTairManager userTitleTairManager;


    @Resource
    private AchievementCountManager achievementCountManager;


    @Resource
    private UserUnreadAchievementTairManager userUnreadAchievementTairManager;


    ......


    /**
     * 解鎖稱號(hào)
     *
     * @param params
     * @return
     */
    public @CheckForNull TitleUnlockResult unlockTitles(T params) {
        TitleUnlockResult titleUnlockResult = this.doUnlock(params);
        if (null == titleUnlockResult) {
            return null;
        }


        List titleAchievements = titleUnlockResult.getUnlockedTitles();
        if (CollectionUtils.isEmpty(titleAchievements)) {
            titleUnlockResult.setUnlockedTitles(new ArrayList<>());
            return titleUnlockResult;
        }


        //基于注入的bean和計(jì)算出的稱號(hào)列表進(jìn)行后置操作,如:更新成就計(jì)數(shù)、更新用戶稱號(hào)緩存、更新用戶未讀成就等
        ......


        return titleUnlockResult;
    }


    /**
     * 計(jì)算出要解鎖的稱號(hào)
     *
     * @param param
     * @return
     */
    protected abstract TitleUnlockResult doUnlock(T param);


    @Override
    public abstract String type();


}

BaseTitleUnlockHandler定義了稱號(hào)解鎖行為,并且在此確定了策略標(biāo)識(shí)的類型為String。此外,該類是一個(gè)abstract class,是因?yàn)樵擃惗x了一個(gè)模版方法unlockTitles,在該方法里封裝了稱號(hào)解鎖所要進(jìn)行的一些公共操作,比如更新用戶的稱號(hào)計(jì)數(shù)、用戶的稱號(hào)緩存數(shù)據(jù)等,這些都依賴于注入的一些外部bean,而interface不支持非靜態(tài)成員變量,所以該類通過abstract class來定義。具體的稱號(hào)解鎖行為通過doUnlock定義,這也是該策略的具體實(shí)現(xiàn)類需要實(shí)現(xiàn)的方法。

另外也許你還注意到了doUnlock方法的行參是一個(gè)范型參數(shù)T,因?yàn)槲覀兛紤]到了不同類型稱號(hào)解鎖所需要的參數(shù)可能是不同的,因此在場(chǎng)景抽象接口側(cè)只依賴于稱號(hào)解鎖的公共參數(shù)類型,而在策略接口具體實(shí)現(xiàn)側(cè)才與該類型策略的具體參數(shù)類型進(jìn)行耦合。

@Component
public class GameplayTitleUnlockHandler extends BaseTitleUnlockHandler {


    @Resource
    private BlessTitleAchievementDiamondConfig blessTitleAchievementDiamondConfig;


    @Resource
    private UserTitleTairManager userTitleTairManager;


    @Override
    protected TitleUnlockResult doUnlock(GameplayTitleUnlockParams params) {
        //獲取稱號(hào)元數(shù)據(jù)
        List titleMetadata = blessTitleAchievementDiamondConfig.getTitleMetadata();


        if (CollectionUtils.isEmpty(titleMetadata)) {
            return null;
        }


        List titleAchievements = new ArrayList<>();


        Result result = userTitleTairManager.queryRawCache(params.getUserId());


        //用戶稱號(hào)數(shù)據(jù)查詢異常
        if (null == result || !result.isSuccess()) {
            return null;
        }


        if (Objects.equals(result.getRc(), ResultCode.SUCCESS)) {
            //解鎖新稱號(hào)
            titleAchievements = unlockNewTitles(params, titleMetadata);


        } else if (Objects.equals(result.getRc(), ResultCode.DATANOTEXSITS)) {
            //初始化歷史稱號(hào)
            titleAchievements = initHistoricalTitles(params, titleMetadata);


        }


        TitleUnlockResult titleUnlockResult = new TitleUnlockResult();
        titleUnlockResult.setUserTitleCache(result);
        titleUnlockResult.setUnlockedTitles(titleAchievements);
        return titleUnlockResult;
    }


    @Override
    public String type() {
        return TitleType.GAMEPLAY;
    }


    ......
}

上面是一個(gè)策略的具體實(shí)現(xiàn)類的大致示例,可以看到該實(shí)現(xiàn)類核心明確了以下信息:

策略標(biāo)識(shí):給出了type方法的具體實(shí)現(xiàn),返回了一個(gè)策略標(biāo)識(shí)的常量

策略處理邏輯:此處是玩法類稱號(hào)解鎖的業(yè)務(wù)邏輯,讀者無需關(guān)注其細(xì)節(jié)

稱號(hào)解鎖行參:給出了玩法類稱號(hào)解鎖所需的真實(shí)參數(shù)類型

抽象疲勞度管控體系

在我們的業(yè)務(wù)需求中經(jīng)常會(huì)遇到涉及疲勞度管控相關(guān)的邏輯,比如每日簽到允許用戶每天完成1次、首頁項(xiàng)目進(jìn)展彈窗要求對(duì)所有用戶只彈1次、首頁限時(shí)回訪任務(wù)入口則要對(duì)用戶每天都展示一次,但用戶累計(jì)完成3次后便不再展示......因此我們?cè)O(shè)計(jì)了一套疲勞度管控的模式,以降低后續(xù)諸如上述涉及疲勞度管控相關(guān)需求的開發(fā)成本。

自頂向下的視角

這套疲勞度管控體系的類層次大致如下圖: 0856ab3e-a964-11ee-8b88-92fbcf53809c.png ? 接下來我們自頂向下逐層進(jìn)行介紹:

FatigueLimiter(interface):FatigueLimiter是最頂層抽象的疲勞度管控接口,它定義了疲勞度管控相關(guān)的行為,比如:疲勞度的查詢、疲勞度清空、疲勞度增加、是否達(dá)到疲勞度限制的判斷等。

BaseFatigueLdbLimiter(abstract class):疲勞度數(shù)據(jù)的存儲(chǔ)方案可以是多種多樣的,在我們項(xiàng)目中主要利用ldb進(jìn)行疲勞度存儲(chǔ),而BaseFatigueLdbLimiter正是基于ldb【注:阿里內(nèi)部自研的一款持久化k-v數(shù)據(jù)庫,讀者可將其理解為類似level db的項(xiàng)目】對(duì)疲勞度數(shù)據(jù)進(jìn)行管控的抽象實(shí)現(xiàn),它封裝了ldb相關(guān)的操作,并基于ldb的數(shù)據(jù)操作實(shí)現(xiàn)了FatigueLimiter的疲勞度管控方法。但它并不感知具體業(yè)務(wù)的身份和邏輯,因此定義了幾個(gè)業(yè)務(wù)相關(guān)的方法交給下層去實(shí)現(xiàn),分別是:

scene:標(biāo)識(shí)具體業(yè)務(wù)的場(chǎng)景,會(huì)利用該方法返回值去構(gòu)造Ldb存儲(chǔ)的key

buildCustomKey:對(duì)Ldb存儲(chǔ)key的定制邏輯

getExpireSeconds:對(duì)應(yīng)著Ldb存儲(chǔ)kv失效時(shí)間,對(duì)應(yīng)著疲勞度的管控周期

Ldb周期性疲勞度管控的解決方案層(abstract class):在這一層提供了多種周期的開箱即用的疲勞度管控實(shí)現(xiàn)類,如BaseFatigueDailyLimiter提供的是天級(jí)別的疲勞度管控能力,BaseFatigueNoCycleLimiter則表示疲勞度永不過期,而BaseFatigueCycleLimiter則支持用戶實(shí)現(xiàn)cycle方法定制疲勞度周期。

業(yè)務(wù)場(chǎng)景層:這一層則是各個(gè)業(yè)務(wù)場(chǎng)景對(duì)疲勞度管控的具體實(shí)現(xiàn),實(shí)現(xiàn)類只需要實(shí)現(xiàn)scene方法來聲明業(yè)務(wù)場(chǎng)景的身份標(biāo)識(shí),隨后繼承對(duì)應(yīng)的解決方案,即可實(shí)現(xiàn)快速的疲勞度管控。比如上面的DailyWishSignLimiter就對(duì)應(yīng)著本篇開頭我們所說的“每日簽到允許用戶每天完成1次”,這就要求為用戶的簽到行為以天維度構(gòu)建key同時(shí)失效時(shí)間也為1天,因此直接繼承解決方案層的BaseFatigueDailyLimiter即可。其代碼實(shí)現(xiàn)非常簡單,如下:

@Component
public class DailyWishSignLimiter extends BaseFatigueLdbDailyLimiter {


    @Override
    protected String scene() {
        return LimiterScene.dailyWish;
    }
}

有一個(gè)“異類”

也許你注意到了上面的類層次圖中有一個(gè)“異類”——HomeEnterGuideLimiter。它其實(shí)就是我們?cè)谏衔恼f的“首頁限時(shí)回訪任務(wù)入口則要對(duì)用戶每天都展示一次,但用戶累計(jì)完成3次后便不再展示”,它的邏輯其實(shí)也很簡單:因?yàn)樗?條管控條件,所以需要繼承2個(gè)管控周期的解決方案——天維度和永久維度,最后實(shí)際使用的類再聚合了天維度和永久維度的實(shí)現(xiàn)類(每個(gè)實(shí)現(xiàn)類對(duì)應(yīng)ldb的一類key)并實(shí)現(xiàn)了頂層的疲勞度管控接口,標(biāo)識(shí)這也是一個(gè)疲勞度管理器。它們的代碼如下:

/**
 * 首頁入口引導(dǎo)限時(shí)任務(wù)-天級(jí)疲勞度管控
 *
 */
@Component
public class HomeEnterGuideDailyLimiter extends BaseFatigueLdbDailyLimiter {


    @Override
    protected String scene() {
        return LimiterScene.homeEnterGuide;
    }
}


/**
 * 首頁入口引導(dǎo)限時(shí)任務(wù)-總次數(shù)疲勞度管控
 *
 */
@Component
public class HomeEnterGuideNoCycleLimiter extends BaseFatigueLdbNoCycleLimiter {


    @Override
    protected String scene() {
        return LimiterScene.homeEnterGuide;
    }


    @Override
    protected int maxSize() {
        return 3;
    }
}


/**
 * 首頁入口引導(dǎo)限時(shí)任務(wù)-疲勞度服務(wù)
 *
 */
@Component
public class HomeEnterGuideLimiter implements FatigueLimiter {


    @Resource
    private FatigueLimiter homeEnterGuideDailyLimiter;


    @Resource
    private FatigueLimiter homeEnterGuideNoCycleLimiter;


    @Override
    public boolean isLimit(String customKey) {
        return homeEnterGuideNoCycleLimiter.isLimit(customKey) || homeEnterGuideDailyLimiter.isLimit(customKey);
    }


    @Override
    public Integer incrLimit(String customKey) {
        homeEnterGuideDailyLimiter.incrLimit(customKey);
        return homeEnterGuideNoCycleLimiter.incrLimit(customKey);
    }


    @Override
    public boolean isLimit(Integer fatigue) {
        throw new UnsupportedOperationException();
    }


    @Override
    public Map batchQueryLimit(List keys) {
        throw new UnsupportedOperationException();
    }


    @Override
    public void removeLimit(String customKey) {
        homeEnterGuideDailyLimiter.removeLimit(customKey);
        homeEnterGuideNoCycleLimiter.removeLimit(customKey);
    }


    @Override
    public Integer queryLimit(String customKey) {
        throw new UnsupportedOperationException();
    }


    /**
     * 查詢首頁限時(shí)任務(wù)的每日疲勞度
     *
     * @param customKey 用戶自定義key
     * @return 疲勞度計(jì)數(shù)
     */
    public Integer queryDailyLimit(String customKey) {
        return homeEnterGuideDailyLimiter.queryLimit(customKey);
    }


    /**
     * 查詢首頁限時(shí)任務(wù)的全周期疲勞度
     *
     * @param customKey 用戶自定義key
     * @return 疲勞度計(jì)數(shù)
     */
    public Integer queryNoCycleLimit(String customKey) {
        return homeEnterGuideNoCycleLimiter.queryLimit(customKey);
    }
}

函數(shù)式行為參數(shù)化

Java 21在今年9月份發(fā)布了,而距離Java 8發(fā)布已經(jīng)過去9年多了,但也許,我是說也許......我們有些同學(xué)對(duì)Java 8還是不太熟悉......

再談行為參數(shù)化

最早聽到“行為參數(shù)化”這個(gè)詞是在經(jīng)典的Java技術(shù)書籍《Java 8實(shí)戰(zhàn)》中。在此書中,作者以一個(gè)篩選蘋果的案例,基于行為參數(shù)化的思維一步步優(yōu)化重構(gòu)代碼,在提升代碼抽象能力的同時(shí),保證了代碼的簡潔性和可讀性,而其中的秘密武器就是Java 8所引入的Lambda表達(dá)式和函數(shù)式接口。Java 8發(fā)布已經(jīng)9年,對(duì)于Lambda表達(dá)式,大多數(shù)同學(xué)都已經(jīng)耳熟能詳,但函數(shù)式接口也許有同學(xué)不知道代表著什么。簡單來說,如果一個(gè)接口,它只有一個(gè)沒有被實(shí)現(xiàn)的方法,那它就是函數(shù)式接口。java.lang.function包下定義JDK提供的一系列函數(shù)式接口。如果一個(gè)接口是函數(shù)式接口,推薦用@FunctionalInterface注解來顯式標(biāo)明。那函數(shù)式接口有什么用呢?如果一個(gè)方法的行參里有函數(shù)式接口,那么函數(shù)式接口對(duì)應(yīng)的參數(shù)可以支持傳遞Lambda表達(dá)式或者方法引用。 那何為“行為參數(shù)化”?直觀地來說就是將行為作為方法/函數(shù)的參數(shù)來進(jìn)行傳遞。在Java 8之前,這可以通過匿名類實(shí)現(xiàn),而在Java 8以后,可以基于函數(shù)式特性來實(shí)現(xiàn)行為參數(shù)化,即方法參數(shù)定義為函數(shù)式接口,在具體傳參時(shí)使用Lambda表達(dá)式/方法。相比匿名類,后者在簡潔性上有極大的提升。 在我們的日常開發(fā)中,如果我們看到兩個(gè)方法的結(jié)構(gòu)十分相似,只有其中部分行為存在差別,那么就可以考慮采用函數(shù)式的行為參數(shù)化來重構(gòu)優(yōu)化這段代碼,將其中存在差異的行為抽象成參數(shù),從而減少重復(fù)代碼。

從實(shí)踐中來,到代碼中去

下面給出一個(gè)例子。在靜心守護(hù)項(xiàng)目中,我們基于ldb維護(hù)了用戶未讀成就的列表,在用戶進(jìn)入到個(gè)人成就頁時(shí),會(huì)查詢未讀成就數(shù)據(jù),并對(duì)未讀的成就在成就列表進(jìn)行置頂以及加紅點(diǎn)展示。下面是對(duì)用戶未讀成就列表進(jìn)行新增和清除的兩個(gè)方法:

/**
 * 清除未讀成就
 *
 * @param uid             用戶ID
 * @param achievementType 需要清除未讀成就列表的成就類型
 * @return
 */
public boolean clearUnreadAchievements(long uid, Set achievementTypes) {


    if (CollectionUtils.isEmpty(achievementTypes)) {
        return true;
    }


    Result ldbRes = super.rawGet(buildKey(uid), false);


    //用戶稱號(hào)數(shù)據(jù)查詢失敗
    if (Objects.isNull(ldbRes)) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
        return false;
    }


    boolean success = false;


    ResultCode resultCode = ldbRes.getRc();


    //不存在用戶稱號(hào)數(shù)據(jù)則進(jìn)行初始化
    if (Objects.equals(resultCode, ResultCode.DATANOTEXSITS)) {
    UserUnreadAchievementsCache userUnreadAchievementsCache = new UserUnreadAchievementsCache();
        achievementTypes.forEach(type -> clearCertainTypeIds(userUnreadAchievementsCache, type));
        success = putCache(uid, userUnreadAchievementsCache, DEFAULT_VERSION);


    } else if (Objects.equals(resultCode, ResultCode.SUCCESS)) {


        DataEntry ldbEntry = ldbRes.getValue();


        //存在新數(shù)據(jù)則對(duì)其進(jìn)行更新
        if (Objects.nonNull(ldbEntry)) {
            Object data = ldbEntry.getValue();


            if (data instanceof String) {
                UserUnreadAchievementsCache userUnreadAchievementsCache = JSON.parseObject(String.valueOf(data), UserUnreadAchievementsCache.class);
                achievementTypes.forEach(type -> clearCertainTypeIds(userUnreadAchievementsCache, type))
                success = putCache(uid, userUnreadAchievementsCache, ldbEntry.getVersion());
            }
        }
    }
    //緩存解鎖的稱號(hào)失敗
    if (!success) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
    }
    return success;
}
/**
 * 寫入新的未讀成就
 *
 * @param uid                  用戶ID
 * @param achievementTypeIdMap 需要新增的成就類型和成就ID列表的映射
 * @return
 */
public boolean writeUnreadAchievements(long uid, Map> achievementTypeIdMap) {


    if (MapUtils.isEmpty(achievementTypeIdMap)) {
        return true;
    }


    Result ldbRes = super.rawGet(buildKey(uid), false);


    //用戶稱號(hào)數(shù)據(jù)查詢失敗
    if (Objects.isNull(ldbRes)) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
        return false;
    }


    boolean success = false;


    ResultCode resultCode = ldbRes.getRc();


    //不存在用戶稱號(hào)數(shù)據(jù)則進(jìn)行初始化
    if (Objects.equals(resultCode, ResultCode.DATANOTEXSITS)) {
    UserUnreadAchievementsCache userUnreadAchievementsCache = new UserUnreadAchievementsCache();
        achievementTypeIdMap.forEach((key, value) -> updateCertainTypeIds(userUnreadAchievementsCache, key, value));
        success = putCache(uid, userUnreadAchievementsCache, DEFAULT_VERSION);


    } else if (Objects.equals(resultCode, ResultCode.SUCCESS)) {


        DataEntry ldbEntry = ldbRes.getValue();


        //存在新數(shù)據(jù)則對(duì)其進(jìn)行更新
        if (Objects.nonNull(ldbEntry)) {
            Object data = ldbEntry.getValue();


            if (data instanceof String) {
                UserUnreadAchievementsCache userUnreadAchievementsCache = JSON.parseObject(String.valueOf(data), UserUnreadAchievementsCache.class);
                achievementTypeIdMap.forEach((key, value) -> updateCertainTypeIds(oldCache, key, value));
                success = putCache(uid, userUnreadAchievementsCache, ldbEntry.getVersion());
            }
        }
    }
    //緩存解鎖的稱號(hào)失敗
    if (!success) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
    }
    return success;
}

從結(jié)構(gòu)上看,上面兩段代碼其實(shí)是非常類似的:整個(gè)結(jié)構(gòu)都是先判空,然后查詢歷史的未讀成就數(shù)據(jù),如果數(shù)據(jù)未初始化,則進(jìn)行初始化,如果已經(jīng)初始化,則對(duì)數(shù)據(jù)進(jìn)行更新。只不過寫入/清除對(duì)數(shù)據(jù)的初始化和更新邏輯并不相同。因此可以將數(shù)據(jù)初始化和更新抽象為行為參數(shù),將剩余部分提取為公共方法,基于這樣的思路重構(gòu)后的代碼如下:

/**
 * 創(chuàng)建or更新緩存
 *
 * @param uid               用戶ID
 * @param initCacheSupplier 緩存初始化策略
 * @param updater           緩存更新策略
 * @return
 */
private boolean upsertCache(long uid, Supplier initCacheSupplier,
                            Function updater) {


    Result ldbRes = super.rawGet(buildKey(uid), false);


    //用戶稱號(hào)數(shù)據(jù)查詢失敗
    if (Objects.isNull(ldbRes)) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
        return false;
    }


    boolean success = false;


    ResultCode resultCode = ldbRes.getRc();


    //不存在用戶稱號(hào)數(shù)據(jù)則進(jìn)行初始化
    if (Objects.equals(resultCode, ResultCode.DATANOTEXSITS)) {


        UserUnreadAchievementsCache userUnreadAchievementsCache = initCacheSupplier.get();
        success = putCache(uid, userUnreadAchievementsCache, DEFAULT_VERSION);


    } else if (Objects.equals(resultCode, ResultCode.SUCCESS)) {


        DataEntry ldbEntry = ldbRes.getValue();


        //存在新數(shù)據(jù)則對(duì)其進(jìn)行更新
        if (Objects.nonNull(ldbEntry)) {
            Object data = ldbEntry.getValue();


            if (data instanceof String) {
                UserUnreadAchievementsCache userUnreadAchievementsCache = JSON.parseObject(String.valueOf(data), UserUnreadAchievementsCache.class);
                userUnreadAchievementsCache = updater.apply(userUnreadAchievementsCache);
                success = putCache(uid, userUnreadAchievementsCache, ldbEntry.getVersion());
            }
        }
    }
    //緩存解鎖的稱號(hào)失敗
    if (!success) {
        recordErrorCode(InteractErrorCode.UNREAD_ACHIEVEMENT_UPSERT_ERROR, ExceptionBizParams.builder().uid(uid).build());
    }
    return success;
}


/**
 * 寫入新的未讀成就
 *
 * @param uid                  用戶ID
 * @param achievementTypeIdMap 需要新增的成就類型和成就ID列表的映射
 * @return
 */
public boolean writeUnreadAchievements(long uid, Map> achievementTypeIdMap) {


    if (MapUtils.isEmpty(achievementTypeIdMap)) {
        return true;
    }


    return upsertCache(uid,
            () -> {
                UserUnreadAchievementsCache userUnreadAchievementsCache = new UserUnreadAchievementsCache();
                achievementTypeIdMap.forEach((key, value) -> updateCertainTypeIds(userUnreadAchievementsCache, key, value));
                return userUnreadAchievementsCache;
            },
            oldCache -> {
                achievementTypeIdMap.forEach((key, value) -> updateCertainTypeIds(oldCache, key, value));
                return oldCache;
            }
    );
}


/**
 * 清除未讀成就
 *
 * @param uid             用戶ID
 * @param achievementType 需要清除未讀成就列表的成就類型
 * @return
 */
public boolean clearUnreadAchievements(long uid, Set achievementTypes) {


    if (CollectionUtils.isEmpty(achievementTypes)) {
        return true;
    }


    return upsertCache(uid,
            () -> {
                UserUnreadAchievementsCache userUnreadAchievementsCache = new UserUnreadAchievementsCache();
                achievementTypes.forEach(type -> clearCertainTypeIds(userUnreadAchievementsCache, type));
                return userUnreadAchievementsCache;
            },
            oldCache -> {
                achievementTypes.forEach(type -> clearCertainTypeIds(oldCache, type));
                return oldCache;
            }
    );
}

重構(gòu)的核心是提取了upsert方法,該方法將緩存數(shù)據(jù)的初始化和更新策略以函數(shù)式接口進(jìn)行定義,從而支持從調(diào)用側(cè)進(jìn)行透?jìng)?,避免了模板方法的重?fù)編寫。這是一個(gè)拋磚引玉的例子,在日常開發(fā)中,我們可以更多地嘗試用函數(shù)式編程的思維去思考和重構(gòu)代碼,也許會(huì)發(fā)現(xiàn)另一個(gè)神奇的編程世界。

切面編程的一些實(shí)踐

AOP想必大家都已經(jīng)十分熟悉了,在此便不再贅述其基本概念,而是開門見山直接分享一些AOP在靜心守護(hù)項(xiàng)目中的實(shí)際應(yīng)用。

服務(wù)層異常統(tǒng)一收口

靜心守護(hù)項(xiàng)目采用了在阿里系統(tǒng)中常用的service-manager-dao的分層模式,其中service層是距離終端最近的一層。為了防止下層預(yù)期外的異常拋到終端,我們需要在service層對(duì)異常進(jìn)行統(tǒng)一攔截并且記錄,同時(shí)最好將相關(guān)的錯(cuò)誤碼、請(qǐng)求參數(shù)以及traceId都一并記下,便于問題排查。這個(gè)場(chǎng)景就非常適合使用AOP。在引入AOP之前,我們需要對(duì)每個(gè)service中面向終端的方法都進(jìn)行異常攔截和監(jiān)控日志打印的操作。比方說下面這個(gè)類,它有3個(gè)面向終端mtop【注:阿里內(nèi)部自研的API網(wǎng)關(guān)平臺(tái)】服務(wù)的方法(api具體參數(shù)和名稱做了模糊化處理),這3個(gè)方法都采用了同樣的try-catch結(jié)構(gòu)來進(jìn)行異常捕捉和監(jiān)控日志打印,其中存在大量的重復(fù)代碼,而更糟糕的事,如果后續(xù)增加新的方法,這樣的重復(fù)代碼還會(huì)不斷增加。

@Slf4j
@HSFProvider(serviceInterface = MtopBlessHomeService.class)
public class MtopBlessHomeServiceImpl implements MtopBlessHomeService {


    //依賴的bean注入
  ......


    @Override
    public MtopResult entranceA(EntranceARequest request) {
        try {
            startDiagnose(request.getUserId());


            //該入口下的業(yè)務(wù)邏輯
            ......


        } catch (InteractBizException e) {
            log.error("Service invoke fail. Method name:{}, params:{}, errorCode:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceA", buildMethodParamsStr(request), e.getErrCode(), EagleEye.getTraceId());
            recordErrorCode(e);
            return MtopUtils.errMtopResult(e.getErrCode(), e.getErrMsg());
        } catch (Exception e) {
            log.error("Service invoke fail. Method name:{}, params:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceA", buildMethodParamsStr(request), EagleEye.getTraceId(), e);
            recordErrorCode(InteractErrorCode.SYSTEM_ERROR, ExceptionBizParams.builder().build());
            return MtopUtils.sysErrMtopResult();
        } finally {
            DiagnoseClient.end();
        }
    }


    @Override
    public MtopResult entranceB(EntranceBRequest request) {
        try {
            startDiagnose(request.getUserId());


            //該入口下的業(yè)務(wù)邏輯
            ......


        } catch (InteractBizException e) {
            log.error("Service invoke fail. Method name:{}, params:{}, errorCode:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceB", buildMethodParamsStr(request), e.getErrCode(), EagleEye.getTraceId());
            recordErrorCode(e);
            return MtopUtils.errMtopResult(e.getErrCode(), e.getErrMsg());
        } catch (Exception e) {
            log.error("Service invoke fail. Method name:{}, params:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceB", buildMethodParamsStr(request), EagleEye.getTraceId(), e);
            recordErrorCode(InteractErrorCode.SYSTEM_ERROR, ExceptionBizParams.builder().build());
            return MtopUtils.sysErrMtopResult();
        } finally {
            DiagnoseClient.end();
        }
    }


    @Override
    public MtopResult entranceC(EntranceCRequest request) {
        try {
            startDiagnose(query.getUserId());


            //該入口下的業(yè)務(wù)邏輯
            ......


        } catch (InteractBizException e) {
            log.error("Service invoke fail. Method name:{}, params:{}, errorCode:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceC", buildMethodParamsStr(request), e.getErrCode(), EagleEye.getTraceId());
            recordErrorCode(e);
            return MtopUtils.errMtopResult(e.getErrCode(), e.getErrMsg());
        } catch (Exception e) {
            log.error("Service invoke fail. Method name:{}, params:{}, trace:{}",
                    "MtopBlessHomeServiceImpl.entranceC", buildMethodParamsStr(request), EagleEye.getTraceId(), e);
            recordErrorCode(InteractErrorCode.SYSTEM_ERROR, ExceptionBizParams.builder().build());
            return MtopUtils.sysErrMtopResult();
        } finally {
            DiagnoseClient.end();
        }    
    }


}
看到這樣重復(fù)的代碼結(jié)構(gòu)而只是局部行為的不同,也許我們可以考慮著用上一節(jié)的函數(shù)式行為參數(shù)化進(jìn)行重構(gòu):將重復(fù)的代碼結(jié)構(gòu)抽取為公共的工具方法,將對(duì)manager層的調(diào)用抽象為行為參數(shù)。但在上述場(chǎng)景下,這種做法還是存在一些弊端:

每個(gè)服務(wù)的方法還是需要顯式調(diào)用工具類方法

為了保證監(jiān)控信息的齊全,還需要在參數(shù)里手動(dòng)透?jìng)饕恍┍O(jiān)控相關(guān)的信息

而AOP則不存在這些問題:AOP基于動(dòng)態(tài)代理實(shí)現(xiàn),在實(shí)現(xiàn)上述邏輯時(shí)對(duì)服務(wù)層的代碼編寫完全透明。此外,AOP還封裝了調(diào)用端方法的各種元信息,可以輕松實(shí)現(xiàn)各種監(jiān)控信息的自動(dòng)化打印。下面是我們提供的AOP切面。其中值得注意的點(diǎn)是切點(diǎn)的選擇要盡量準(zhǔn)確,避免增強(qiáng)了不必要的方法。下面我們選擇的切點(diǎn)是mtop包下所有Impl結(jié)尾類的public方法。

@Aspect
@Component
@Slf4j
public class MtopServiceAspect {


    /**
     * MtopService層服務(wù)
     */
    @Pointcut("execution(public com.taobao.mtop.common.MtopResult com.taobao.gaia.veyron.bless.service.mtop.*Impl.*(..))")
    public void mtopService(){}


    /**
     * 對(duì)mtop服務(wù)進(jìn)行增強(qiáng)
     *
     * @param pjp 接入點(diǎn)
     * @return
     * @throws Throwable
     */
    @Around("com.taobao.gaia.veyron.bless.aspect.MtopServiceAspect.mtopService()")
    public Object enhanceService(ProceedingJoinPoint pjp) throws Throwable {
        try {
            startDiagnose(pjp);
            return pjp.proceed();
        } catch (InteractBizException e) {
            log.error("Service invoke fail. Method name:{}, params:{}, errorCode:{}, trace:{}",
                    AspectUtils.extractMethodName(pjp), buildMethodParamsStr(pjp), e.getErrCode(), EagleEye.getTraceId());
            recordErrorCode(e);
            return MtopUtils.errMtopResult(e.getErrCode(), e.getErrMsg());
        } catch (Exception e) {
            log.error("Service invoke fail. Method name:{}, params:{}, trace:{}",
                    AspectUtils.extractMethodName(pjp), buildMethodParamsStr(pjp), EagleEye.getTraceId(), e);
            recordErrorCode(InteractErrorCode.SYSTEM_ERROR, ExceptionBizParams.builder().build());
            return MtopUtils.sysErrMtopResult();
        } finally {
            DiagnoseClient.end();
        }
    }


}

存在這樣一個(gè)切面后,service層的代碼就可以變得非常簡潔:只需要純粹專注于業(yè)務(wù)邏輯。同樣以剛才的MtopBlessHomeServiceImpl類為例,在AOP改寫后的代碼里可以去除掉原先異常收口和監(jiān)控相關(guān)的內(nèi)容,而僅保留業(yè)務(wù)邏輯部分,代碼簡潔性大大提升。

@Slf4j
@HSFProvider(serviceInterface = MtopBlessHomeService.class)
public class MtopBlessHomeServiceImpl implements MtopBlessHomeService {


    //依賴的bean注入
  ......


    @Override
    public MtopResult entranceA(EntranceARequest request) {
        //業(yè)務(wù)邏輯
        ......
    }


    @Override
    public MtopResult entranceB(EntranceBRequest request) {
        //業(yè)務(wù)邏輯
        ......
    }


    @Override
    public MtopResult entranceC(EntranceCRequest request) {
        //業(yè)務(wù)邏輯
        ......
    }


}

切點(diǎn)選擇的策略

除了服務(wù)層以外,我們還想對(duì)數(shù)據(jù)訪問層進(jìn)行監(jiān)控,監(jiān)控項(xiàng)目中各種數(shù)據(jù)存儲(chǔ)工具的RT以及成功率相關(guān)指標(biāo),并且監(jiān)控粒度要盡可能地貼近業(yè)務(wù)維度(整體的數(shù)據(jù)訪問監(jiān)控直接通過eagleeye查看即可),便于具體問題的定位排查。這種面向?qū)蛹?jí)別的邏輯定制,我們很自然而然地想到了AOP,這也正是它可以大顯身手的場(chǎng)景。 這節(jié)核心想要分享的則是切點(diǎn)的選擇。靜心守護(hù)項(xiàng)目的數(shù)據(jù)存儲(chǔ)主要依賴于Tair【注:阿里內(nèi)部自研的高性能K-V存儲(chǔ)系統(tǒng)。根據(jù)存儲(chǔ)介質(zhì)和使用場(chǎng)景不同又分為LDB、MDB、RDB】、Lindorm【注:阿里內(nèi)部自研的大規(guī)模云原生多模數(shù)據(jù)庫服務(wù)】和Mysql,這三種存儲(chǔ)工具在代碼中的使用各不相同,導(dǎo)致切點(diǎn)的選擇策略也大相徑庭。

目標(biāo)對(duì)象規(guī)律分布

如果我們要選擇增強(qiáng)的對(duì)象在項(xiàng)目中分布的非常規(guī)律,那么我們往往可以直接利用Spring AOP的PointCut語法來選擇切點(diǎn)。以靜心守護(hù)項(xiàng)目中的Mysql數(shù)據(jù)訪問對(duì)象為例:我們使用的ORM框架是mybatis,并且主要的用法是注解模式,所有的SQL邏輯都放在一個(gè)DAO包下,每個(gè)業(yè)務(wù)場(chǎng)景定義一個(gè)DAO結(jié)尾的Mapper接口,接口下的每個(gè)方法都對(duì)應(yīng)著一種數(shù)據(jù)訪問的方式。因此在切點(diǎn)選擇時(shí),我們可以直接選擇DAO包下以DAO結(jié)尾的類,并選擇其中public方法即可準(zhǔn)確織入所有滿足條件的切點(diǎn)。

@Pointcut("execution(public * com.taobao.gaia.serverless.veyron.bless.dao.*DAO.*(..))")
public void charityProjectDataAccess() {
}

這樣實(shí)現(xiàn)的監(jiān)控粒度是具體到每個(gè)DAO對(duì)象-方法級(jí)別的粒度,監(jiān)控效果如下:

08798438-a964-11ee-8b88-92fbcf53809c.png

一個(gè)失效案例

靜心守護(hù)項(xiàng)目中對(duì)tair的使用方式是:通過一個(gè)抽象類對(duì)tair的各種基礎(chǔ)操作進(jìn)行封裝(包括參數(shù)校驗(yàn)、響應(yīng)判空、異常處理等),但將具體tair實(shí)例相關(guān)的參數(shù)設(shè)置行為抽象化,由實(shí)現(xiàn)類決定。各個(gè)業(yè)務(wù)場(chǎng)景的tair管理類最終會(huì)基于抽象類封裝的基礎(chǔ)操作來對(duì)tair進(jìn)行數(shù)據(jù)訪問。 如下圖,AbstractLdbManager是封裝 08ad9b10-a964-11ee-8b88-92fbcf53809c.png

由于各個(gè)業(yè)務(wù)場(chǎng)景的tair管理實(shí)現(xiàn)類分散在各個(gè)業(yè)務(wù)包下,想要對(duì)它們進(jìn)行統(tǒng)一切入比較困難。因此我們選擇對(duì)抽象類進(jìn)行切入。但這樣就會(huì)遇到一個(gè)同類調(diào)用導(dǎo)致AOP失效的問題:抽象類本身不會(huì)有實(shí)例對(duì)象,因此基于CGLIB創(chuàng)建代理對(duì)象后,代理對(duì)象本質(zhì)上調(diào)用的還是各個(gè)業(yè)務(wù)場(chǎng)景tair管理類的對(duì)象,而在使用這些對(duì)象時(shí),我們不會(huì)直接調(diào)用tair抽象類封裝的數(shù)據(jù)訪問方法,而是調(diào)用這些業(yè)務(wù)tair管理對(duì)象進(jìn)一步封裝的帶業(yè)務(wù)語義的方法,基于這些方法再去調(diào)用tair抽象類的數(shù)據(jù)訪問方法。這種同類方法間接調(diào)用最終就導(dǎo)致了抽象類的方法沒有如期被增強(qiáng)。文字描述興許有些繞,可以參考下面的圖:

08c986a4-a964-11ee-8b88-92fbcf53809c.png

我們選擇的解決方法則是從上面的MultiClusterTairManager入手,這個(gè)類是tair為我們提供的TairManger的一種默認(rèn)實(shí)現(xiàn),我們之前的做法是為該類實(shí)例化一個(gè)bean,然后提供給所有業(yè)務(wù)Tair管理類使用,也就是說所有業(yè)務(wù)Tair管理類使用的TairManager都是同一個(gè)bean實(shí)例(因?yàn)闃I(yè)務(wù)流量沒那么大,一個(gè)tair實(shí)例暫時(shí)綽綽有余)。那么我們可以自己提供一個(gè)TairManager的實(shí)現(xiàn),基于繼承+組合MultiClusterTairManager的方式,只對(duì)我們項(xiàng)目內(nèi)用到數(shù)據(jù)訪問操作進(jìn)行重寫,并委托給原先的MultiClusterTairManager bean進(jìn)行處理。這樣我們可以在設(shè)置AOP切點(diǎn)時(shí)選擇對(duì)自己實(shí)現(xiàn)的TairManager的所有方法做增強(qiáng),進(jìn)而避開上面的問題。經(jīng)過這樣改寫后,上面的兩張圖會(huì)演變成下面這樣:

08c986a4-a964-11ee-8b88-92fbcf53809c.png

08fefed8-a964-11ee-8b88-92fbcf53809c.png

基于注解切入

還有一種場(chǎng)景是我們要增強(qiáng)的方法分布毫無規(guī)律,可能都在同一個(gè)類中,但方法的名稱毫無規(guī)律,也無法簡單通過private或者public來區(qū)別。針對(duì)這樣的場(chǎng)景,我們的做法是自定義注解,專門用于標(biāo)識(shí)需要做增強(qiáng)的方法。比如靜心守護(hù)項(xiàng)目中l(wèi)indorm相關(guān)的數(shù)據(jù)操作就是這樣。我們定義注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VeyronJoinPoint {}

并將該注解標(biāo)識(shí)在需要增強(qiáng)的方法上,隨后通過下面的方式描述切點(diǎn),即可獲取到所有需要增強(qiáng)的方法。

@Pointcut("@annotation(com.taobao.gaia.serverless.veyron.aspect.VeyronJoinPoint)")
public void lindormDataAccess() {}

上面的方法也有進(jìn)一步改良的空間:在注解內(nèi)增加屬性來描述具體的業(yè)務(wù)場(chǎng)景,不同的切面根據(jù)業(yè)務(wù)場(chǎng)景來對(duì)捕獲的方法進(jìn)行過濾,只留下當(dāng)前業(yè)務(wù)場(chǎng)景所需要的方法。不然按照現(xiàn)有的做法,如果新的切面也要基于注解來尋找切點(diǎn),那只能定義新的注解,否則會(huì)與原先注解產(chǎn)生沖突。

總結(jié)

業(yè)務(wù)需求千變?nèi)f化,對(duì)應(yīng)的解法也見仁見智。在研發(fā)過程中對(duì)各種變化中不變的部分進(jìn)行總結(jié),從中提取出自己的模式與方法論進(jìn)行整理沉淀,會(huì)讓我們以后跑的更快。也正應(yīng)了學(xué)生時(shí)期,老師常說的那句話:“我們要把厚厚的書本讀薄才能裝進(jìn)腦子里?!?/p>

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3674

    瀏覽量

    94740
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4365

    瀏覽量

    63940
  • 容器
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    22338
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    340

    瀏覽量

    14817

原文標(biāo)題:關(guān)于編程模式的總結(jié)與思考

文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    關(guān)于C++模板總結(jié)

    大家好,今天給大家分享一篇關(guān)于 C++ 模板總結(jié)概述。
    發(fā)表于 09-19 11:55 ?627次閱讀

    編程思考還是打字

    有些人的工作需要大量的思考,還有些人則只是敲敲代碼而已。其實(shí)這兩種人從事的是非常不同的工作,需要采取截然不同的方式進(jìn)行管理。 有時(shí)編程就是打字“我們首先是個(gè)打字員,其次才是程序員”。很多業(yè)務(wù)
    發(fā)表于 12-16 17:22

    關(guān)于USB的知識(shí)總結(jié)

    關(guān)于USB的知識(shí)總結(jié)協(xié)議版本USB 協(xié)議版本有USB1.1、USB2.0,而目前公布的最新USB協(xié)議USB3.0,主要由于數(shù)據(jù)線的增加USB3.0 數(shù)據(jù)傳輸速度有了很大的提高。對(duì)于 USB1.1
    發(fā)表于 08-05 06:16

    關(guān)于Arduino的認(rèn)識(shí)與思考不看肯定后悔

    關(guān)于Arduino的認(rèn)識(shí)與思考不看肯定后悔
    發(fā)表于 09-26 07:28

    ARM嵌入式系統(tǒng)的問題總結(jié)分析

    摘要: 本文是作者關(guān)于嵌入式系統(tǒng)一些基本問題的思考總結(jié)。主要是從嵌入式處理器與硬件、ARM處
    發(fā)表于 11-17 18:28 ?846次閱讀

    DXP關(guān)于板層說明及總結(jié)

    DXP關(guān)于板層說明及總結(jié)DXP-設(shè)置板層(D+K )在PCB編輯 Design->layer Stack Manager(層管理)
    發(fā)表于 01-11 14:56 ?0次下載

    集成電路發(fā)展思考

    關(guān)于集成電路的一些個(gè)人的看法與思考總結(jié),個(gè)人觀點(diǎn),僅供參考
    發(fā)表于 05-20 14:47 ?4次下載

    Linux下的網(wǎng)絡(luò)編程總結(jié)

    linux開發(fā)編程教程資料——Linux下的網(wǎng)絡(luò)編程總結(jié),感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Java NIO (中文版)編程總結(jié)

    Java NIO 編程總結(jié)
    發(fā)表于 09-21 11:17 ?0次下載

    關(guān)于Linux下多線程編程技術(shù)學(xué)習(xí)總結(jié)

    Linux下多線程編程技術(shù) 作為一個(gè)IT人員,不斷的學(xué)習(xí)和總結(jié)是我們這個(gè)職業(yè)習(xí)慣,所以我會(huì)將每個(gè)階段的學(xué)習(xí)都會(huì)通過一點(diǎn)的總結(jié)來記錄和檢測(cè)自己的學(xué)習(xí)效果,今天為大家總結(jié)
    發(fā)表于 04-22 03:12 ?2333次閱讀
    <b class='flag-5'>關(guān)于</b>Linux下多線程<b class='flag-5'>編程</b>技術(shù)學(xué)習(xí)<b class='flag-5'>總結(jié)</b>

    AT燒錄軟件Progisp和使用手冊(cè)和對(duì)于ISP編程進(jìn)入不了編程模式總結(jié)

    本文的主要內(nèi)容詳細(xì)介紹的是AT系列燒錄軟件Progisp和使用手冊(cè)和對(duì)于ISP編程進(jìn)入不了編程模式總結(jié)
    發(fā)表于 05-31 14:17 ?43次下載
    AT燒錄軟件Progisp和使用手冊(cè)和對(duì)于ISP<b class='flag-5'>編程</b>進(jìn)入不了<b class='flag-5'>編程</b><b class='flag-5'>模式</b>的<b class='flag-5'>總結(jié)</b>

    事件總線模式知識(shí)總結(jié)

    經(jīng)過對(duì)多個(gè)有關(guān)事件總線模式的文檔介紹的閱讀,對(duì)事件總線模式有了一定的了解,并作出如下總結(jié)
    發(fā)表于 09-22 10:32 ?1914次閱讀

    關(guān)于risc-v啟動(dòng)部分的思考

    關(guān)于risc-v啟動(dòng)部分思考 1.本文說明 1.1 risc-v的誕生的時(shí)代背景 1.2 發(fā)展現(xiàn)狀 2.risc-v 的芯片boot過程 2.1 risc-v的啟動(dòng)模式 2.2 risc-v的啟動(dòng)
    的頭像 發(fā)表于 12-28 10:25 ?5909次閱讀
    <b class='flag-5'>關(guān)于</b>risc-v啟動(dòng)部分的<b class='flag-5'>思考</b>

    C 語言編程習(xí)慣總結(jié)

    編程習(xí)慣的培養(yǎng)需要的是一個(gè)長期的過程,需要不斷地總結(jié),積累,并且我們需要從意識(shí)上認(rèn)識(shí)其重要性,一個(gè)良好的編程習(xí)慣對(duì)于我們能力的...
    發(fā)表于 01-26 17:15 ?0次下載
    C 語言<b class='flag-5'>編程</b>習(xí)慣<b class='flag-5'>總結(jié)</b>

    單片機(jī)編程實(shí)例總結(jié)

    單片機(jī)編程實(shí)例總結(jié)
    的頭像 發(fā)表于 01-16 09:17 ?1442次閱讀