[純干貨] 如何用Spring 原生注解 快速實(shí)現(xiàn)策略模式+工廠模式

undefined

前言

這陣子在做項(xiàng)目組重構(gòu)的工作蛇耀,工作中的一部分就是就目前代碼庫中與企業(yè)交互的邏輯抽離出來,單獨(dú)做一個微服務(wù)课幕,實(shí)現(xiàn)企業(yè)交互邏輯的關(guān)注點(diǎn)分離。

在這里面我很自然而然的就用到了策略模式 + 工廠模式的方式五垮,包裝內(nèi)部實(shí)現(xiàn)細(xì)節(jié)乍惊,向外提供統(tǒng)一的調(diào)用方式,有效的減少if/else的業(yè)務(wù)代碼放仗,使得代碼更容易維護(hù)润绎,擴(kuò)展。

之前看過一些文章匙监,是使用自定義注解+自動BeanProcessor的方式來實(shí)現(xiàn)凡橱,個人感覺有點(diǎn)麻煩。因?yàn)镾pring原生就提供類似的特性亭姥。

本篇旨在介紹在實(shí)際的業(yè)務(wù)場景,如何借助Spring IoC 依賴注入的特性顾稀,使用Spring 原生注解 來快速實(shí)現(xiàn) 策略模式 + 工廠模式达罗。希望能夠?qū)δ阌袉l(fā)。

業(yè)務(wù)場景

從原項(xiàng)目抽離出來的企業(yè)服務(wù)静秆,承擔(dān)的是與外部企業(yè)交互的職責(zé)粮揉。不同企業(yè),雖然會產(chǎn)生的交互行為是相同的抚笔,但是交互行為內(nèi)部的實(shí)現(xiàn)邏輯各有不同扶认,比如發(fā)送報(bào)文接口,不同企業(yè)可能報(bào)文格式會不同殊橙。

針對這種不同企業(yè)交互細(xì)節(jié)不同的場景辐宾,將與企業(yè)的交互行為抽象出來EntStrategy接口,根據(jù)服務(wù)消費(fèi)者傳入的企業(yè)號選擇對應(yīng)的實(shí)現(xiàn)類(策略類)膨蛮,邏輯簡化之后如下圖叠纹。

spring_ent_demo.png

快速實(shí)現(xiàn)

現(xiàn)在讓我們用快速用一個DEMO實(shí)現(xiàn)上述場景。

我們的期望目標(biāo)是敞葛,根據(jù)不同的企業(yè)編號誉察,我們能夠快速找到對應(yīng)的策略實(shí)現(xiàn)類去執(zhí)行發(fā)送報(bào)文的操作。

Step 1 實(shí)現(xiàn)策略類

假設(shè)我們現(xiàn)在對外提供的服務(wù)Api是這樣的惹谐,

/**
 * @param entNum 企業(yè)編號
 */
public void send(String entNum) {
    // 根據(jù)不同的企業(yè)編號持偏,我們能夠快速找到對應(yīng)的策略實(shí)現(xiàn)類去執(zhí)行發(fā)送報(bào)文的操作
}

現(xiàn)在我們先定義個EntStrategy接口

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/23
 */
public interface EntStrategy {

    String getStuff();

    void send();
}

三個策略類

DefaultStrategy

@Component
public class DefaultStrategy  implements EntStrategy {
    @Override
    public String getStuff() {
        return "其他企業(yè)";
    }

    @Override
    public void send() {
        System.out.println("發(fā)送默認(rèn)標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)");
    }

    @Override
    public String toString() {
        return getStuff();
    }
}

EntAStrategy

@Component
public class EntAStrategy implements EntStrategy {
    @Override
    public String getStuff() {
        return "企業(yè)A";
    }

    @Override
    public void send() {
        System.out.println("發(fā)送A標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)");
    }

    @Override
    public String toString() {
        return getStuff();
    }
}

EntBStrategy

@Component
public class EntBStrategy implements EntStrategy {
    @Override
    public String getStuff() {
        return "企業(yè)B";
    }

    @Override
    public void send() {
        System.out.println("發(fā)送B標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)");
    }

    @Override
    public String toString() {
        return getStuff();
    }
}

Step 2 借助Spring 強(qiáng)大的依賴注入

下面的設(shè)計(jì)是消除if/else的關(guān)鍵代碼驼卖,這里我定義了一個EntStrategyHolder來當(dāng)做工廠類。

@Component
public class EntStrategyHolder {

    // 關(guān)鍵功能 Spring 會自動將 EntStrategy 接口的類注入到這個Map中
    @Autowired
    private Map<String, EntStrategy> entStrategyMap;

    public EntStrategy getBy(String entNum) {
        return entStrategyMap.get(entNum);
    }
}

這一步的關(guān)鍵就是鸿秆, Spring 會自動將 EntStrategy 接口的實(shí)現(xiàn)類注入到這個Map中款慨。前提是你這個實(shí)現(xiàn)類得是交給Spring 容器管理的。

這個Map的key值就是你的 bean id谬莹,你可以用@Component("value")的方式設(shè)置檩奠,像我上面直接用默認(rèn)的方式的話,就是首字母小寫附帽。value值則為對應(yīng)的策略實(shí)現(xiàn)類埠戳。

image.png

到這一步,實(shí)際上我們的期望功能大部分已經(jīng)實(shí)現(xiàn)了蕉扮,先讓用一個簡單的啟動類試一下整胃。

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/23
 */
@Configuration
@ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
public class Boostrap {

    public static void main(String[] args) {
        String entNum = "entBStrategy";
        send(entNum);
        entNum = "defaultStrategy";
        send(entNum);
    }

    // 用這個方法模擬 企業(yè)代理服務(wù) 提供的Api
    public static void send(String entNum) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        context.getBean(EntStrategyHolder.class).getBy(entNum).send();
    }
}

輸出結(jié)果

發(fā)送B標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)
發(fā)送默認(rèn)標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)

Step 3 別名轉(zhuǎn)換

大家眼睛如果稍微利索的點(diǎn)的話,會發(fā)現(xiàn)我上面啟動類里面的企業(yè)編號entNum填的實(shí)際上是bean id的值喳钟。那在實(shí)際業(yè)務(wù)中肯定是不會這樣的屁使,怎么可能把一個企業(yè)編號定義的這么奇怪呢。

所以這里還需要一步操作奔则,將傳入的企業(yè)編號蛮寂,轉(zhuǎn)義成對應(yīng)的策略類的bean id

實(shí)際上這一步的邏輯和你的實(shí)際業(yè)務(wù)是有很強(qiáng)的相關(guān)性的易茬,因?yàn)樵谖覙I(yè)務(wù)里面的entNum在實(shí)際上就是一種標(biāo)識酬蹋,程序怎么識別解析這個標(biāo)識,找到對應(yīng)的策略實(shí)現(xiàn)類抽莱,應(yīng)該是根據(jù)你的業(yè)務(wù)需求定制的范抓。

我這里把這一步也寫出來,主要是想給大家提供一種思路食铐。

因?yàn)槲业奈⒎?wù)是用SpringBoot做基礎(chǔ)框架的匕垫,所以我借助SpringBoot 外部化配置的一些特性實(shí)現(xiàn)了這種方式。

添加EntAlias

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/15
 */
@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "ent")
public class EntAlias {

    private HashMap<String, String> aliasMap;
    
    public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";

    public HashMap<String, String> getAliasMap() {
        return aliasMap;
    }

    public void setAliasMap(HashMap<String, String > aliasMap) {
        this.aliasMap = aliasMap;
    }

    String of(String entNum) {
        return aliasMap.get(entNum);
    }
}

在對應(yīng)配置文件application.yml中配置:

ent:
  aliasMap:
    entA: entAStrategy
    entB: entBStrategy

....省略

這里注意哦虐呻,要實(shí)現(xiàn)對應(yīng)gettersetter的象泵,不然屬性會注入不進(jìn)去的。

改寫一下EntStrategyHolder

@Component
public class EntStrategyHolder {
    
    @Autowired
    private EntAlias entAlias;

    // 關(guān)鍵功能 Spring 會自動將 EntStrategy 接口的類注入到這個Map中
    @Autowired
    private Map<String, EntStrategy> entStrategyMap;

    // 找不到對應(yīng)的策略類铃慷,使用默認(rèn)的
    public EntStrategy getBy(String entNum) {
        String name = entAlias.of(entNum);
        if (name == null) {
            return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME);
        }
        EntStrategy entStrategy = entStrategyMap.get(name);
        if (entStrategy == null) {
            return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME);
        }
        return entStrategy;
    }
}

現(xiàn)在我們再啟動一下看看:

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/23
 */
@Configuration
@ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
public class Boostrap {
    public static void main(String[] args) {
        String entNum = "entA";
        send(entNum);
        entNum = "entB";
        send(entNum);
        entNum = "entC";
        send(entNum);
    }
    
    public static void send(String entNum) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        context.getBean(EntStrategyHolder.class).getBy(entNum).send();
    }
}

輸出結(jié)果

發(fā)送A標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)
發(fā)送B標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)
發(fā)送默認(rèn)標(biāo)準(zhǔn)的報(bào)文給對應(yīng)企業(yè)

非SpringBoot

上面的代碼中我采用SpringBoot的特性单芜,通過yml文件來管理別名轉(zhuǎn)化,是為了讓代碼看起來更美觀犁柜。如果是Spring框架的話洲鸠。我會這樣去實(shí)現(xiàn)。(只是參考)

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/23
 */
public class EntAlias {

    private static Map<String, String> aliasMap;

    private static final String ENTA_STATEGY_NAME = "entAStrategy";
    private static final String ENTB_STATEGY_NAME = "entBStrategy";
    public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";

    static {
        // 這個別名容器怎么注冊別名、初始化扒腕,有很多種方式绢淀。
        aliasMap = new LinkedHashMap<>();
        aliasMap.put("entA", ENTA_STATEGY_NAME);
        aliasMap.put("entB", ENTB_STATEGY_NAME);
    }

    public static String of(String entNum) {
        return aliasMap.get(entNum);
    }
}

Spring IoC 的依賴注入

這里我想再談一下上面的第二個步驟,第二個步驟的核心就是通過Spring IoC依賴注入的特性瘾腰,實(shí)現(xiàn)了策略實(shí)現(xiàn)類的注冊過程(這一步自己實(shí)現(xiàn)會需要很多工作皆的,并且代碼不會很好看)。

實(shí)際上除了Map這種變量類型蹋盆,Spring 還能給List 變量進(jìn)行自動裝配费薄。比如下面的代碼。

@Component
public class EntStrategyHolder {

    @Autowired
    private Map<String, EntStrategy> entStrategyMap;

    @Autowired
    private List<EntStrategy> entStrategyList;

    public EntStrategy getBy(String entNum) {
        return entStrategyMap.get(entNum);
    }

    public void print() {
        System.out.println("===== implementation Map =====");
        System.out.println(entStrategyMap);
        entStrategyMap.forEach((name, impl)-> {
            System.out.println(name + ":" + impl.getStuff());
        });
        System.out.println("===== implementation List =====");
        System.out.println(entStrategyList);
        entStrategyList.forEach(impl-> System.out.println(impl.getStuff()));
    }
}

啟動類

@Configuration
@ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
public class Boostrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        context.getBean(EntStrategyHolder.class).print();
    }
}

輸出結(jié)果

===== implementation Map =====
{defaultStrategy=其他企業(yè), entAStrategy=企業(yè)A, entBStrategy=企業(yè)B}
defaultStrategy:其他企業(yè)
entAStrategy:企業(yè)A
entBStrategy:企業(yè)B
===== implementation List =====
[其他企業(yè), 企業(yè)A, 企業(yè)B]
其他企業(yè)
企業(yè)A
企業(yè)B
image.png

可以看到entStrategyList被成功賦值了栖雾。

只不過這個特性我暫時沒有找到應(yīng)用場景楞抡,所以單獨(dú)拿出來說一下。

結(jié)語

到這里析藕,整個實(shí)現(xiàn)過程已經(jīng)介紹完了召廷。

過程中用了到Spring最常用的一些注解,通過Spring IoC依賴注入的特性账胧,實(shí)現(xiàn)了策略實(shí)現(xiàn)類的注冊過程竞慢,最終實(shí)現(xiàn)了整個功能。

希望能對你有所啟發(fā)治泥。

另外筹煮,如果你對上述Spring IoC 是如何對MapList變量進(jìn)行賦值感興趣的話,我會在下一篇文章中講解相關(guān)的源碼和調(diào)試技巧车摄。

我們搞技術(shù)的寺谤,知其然更應(yīng)知其所以然嘛。

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布吮播!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市眼俊,隨后出現(xiàn)的幾起案子意狠,更是在濱河造成了極大的恐慌,老刑警劉巖疮胖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件环戈,死亡現(xiàn)場離奇詭異,居然都是意外死亡澎灸,警方通過查閱死者的電腦和手機(jī)院塞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來性昭,“玉大人拦止,你說我怎么就攤上這事。” “怎么了汹族?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵萧求,是天一觀的道長。 經(jīng)常有香客問我顶瞒,道長夸政,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任榴徐,我火速辦了婚禮守问,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坑资。我一直安慰自己耗帕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布盐茎。 她就那樣靜靜地躺著兴垦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪字柠。 梳的紋絲不亂的頭發(fā)上探越,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音窑业,去河邊找鬼钦幔。 笑死,一個胖子當(dāng)著我的面吹牛常柄,可吹牛的內(nèi)容都是我干的鲤氢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼西潘,長吁一口氣:“原來是場噩夢啊……” “哼卷玉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起喷市,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤相种,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后品姓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寝并,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年腹备,在試婚紗的時候發(fā)現(xiàn)自己被綠了衬潦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡植酥,死狀恐怖镀岛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤哎媚,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布喇伯,位于F島的核電站,受9級特大地震影響拨与,放射性物質(zhì)發(fā)生泄漏稻据。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一买喧、第九天 我趴在偏房一處隱蔽的房頂上張望捻悯。 院中可真熱鬧,春花似錦淤毛、人聲如沸今缚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姓言。三九已至,卻和暖如春蔗蹋,著一層夾襖步出監(jiān)牢的瞬間何荚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工猪杭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留餐塘,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓皂吮,卻偏偏與公主長得像戒傻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蜂筹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345