[純干貨] 如何用Spring 原生注解 快速實現策略模式+工廠模式

undefined

前言

這陣子在做項目組重構的工作,工作中的一部分就是就目前代碼庫中與企業(yè)交互的邏輯抽離出來,單獨做一個微服務,實現企業(yè)交互邏輯的關注點分離。

在這里面我很自然而然的就用到了策略模式 + 工廠模式的方式,包裝內部實現細節(jié),向外提供統(tǒng)一的調用方式,有效的減少if/else的業(yè)務代碼,使得代碼更容易維護,擴展。

之前看過一些文章,是使用自定義注解+自動BeanProcessor的方式來實現,個人感覺有點麻煩。因為Spring原生就提供類似的特性。

本篇旨在介紹在實際的業(yè)務場景,如何借助Spring IoC 依賴注入的特性,使用Spring 原生注解 來快速實現 策略模式 + 工廠模式。希望能夠對你有啟發(fā)。

業(yè)務場景

從原項目抽離出來的企業(yè)服務,承擔的是與外部企業(yè)交互的職責。不同企業(yè),雖然會產生的交互行為是相同的,但是交互行為內部的實現邏輯各有不同,比如發(fā)送報文接口,不同企業(yè)可能報文格式會不同。

針對這種不同企業(yè)交互細節(jié)不同的場景,將與企業(yè)的交互行為抽象出來EntStrategy接口,根據服務消費者傳入的企業(yè)號選擇對應的實現類(策略類),邏輯簡化之后如下圖。

spring_ent_demo.png

快速實現

現在讓我們用快速用一個DEMO實現上述場景。

我們的期望目標是,根據不同的企業(yè)編號,我們能夠快速找到對應的策略實現類去執(zhí)行發(fā)送報文的操作。

Step 1 實現策略類

假設我們現在對外提供的服務Api是這樣的,

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

現在我們先定義個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ā)送默認標準的報文給對應企業(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標準的報文給對應企業(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標準的報文給對應企業(yè)");
    }

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

Step 2 借助Spring 強大的依賴注入

下面的設計是消除if/else的關鍵代碼,這里我定義了一個EntStrategyHolder來當做工廠類。

@Component
public class EntStrategyHolder {

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

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

這一步的關鍵就是, Spring 會自動將 EntStrategy 接口的實現類注入到這個Map中。前提是你這個實現類得是交給Spring 容器管理的。

這個Map的key值就是你的 bean id,你可以用@Component("value")的方式設置,像我上面直接用默認的方式的話,就是首字母小寫。value值則為對應的策略實現類。

image.png

到這一步,實際上我們的期望功能大部分已經實現了,先讓用一個簡單的啟動類試一下。

/**
 * @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è)代理服務 提供的Api
    public static void send(String entNum) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        context.getBean(EntStrategyHolder.class).getBy(entNum).send();
    }
}

輸出結果

發(fā)送B標準的報文給對應企業(yè)
發(fā)送默認標準的報文給對應企業(yè)

Step 3 別名轉換

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

所以這里還需要一步操作,將傳入的企業(yè)編號,轉義成對應的策略類的bean id。

實際上這一步的邏輯和你的實際業(yè)務是有很強的相關性的,因為在我業(yè)務里面的entNum在實際上就是一種標識,程序怎么識別解析這個標識,找到對應的策略實現類,應該是根據你的業(yè)務需求定制的。

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

因為我的微服務是用SpringBoot做基礎框架的,所以我借助SpringBoot 外部化配置的一些特性實現了這種方式。

添加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);
    }
}

在對應配置文件application.yml中配置:

ent:
  aliasMap:
    entA: entAStrategy
    entB: entBStrategy

....省略

這里注意哦,要實現對應gettersetter的,不然屬性會注入不進去的。

改寫一下EntStrategyHolder

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

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

    // 找不到對應的策略類,使用默認的
    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;
    }
}

現在我們再啟動一下看看:

/**
 * @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();
    }
}

輸出結果

發(fā)送A標準的報文給對應企業(yè)
發(fā)送B標準的報文給對應企業(yè)
發(fā)送默認標準的報文給對應企業(yè)

非SpringBoot

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

/**
 * @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依賴注入的特性,實現了策略實現類的注冊過程(這一步自己實現會需要很多工作,并且代碼不會很好看)。

實際上除了Map這種變量類型,Spring 還能給List 變量進行自動裝配。比如下面的代碼。

@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();
    }
}

輸出結果

===== 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被成功賦值了。

只不過這個特性我暫時沒有找到應用場景,所以單獨拿出來說一下。

結語

到這里,整個實現過程已經介紹完了。

過程中用了到Spring最常用的一些注解,通過Spring IoC依賴注入的特性,實現了策略實現類的注冊過程,最終實現了整個功能。

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

另外,如果你對上述Spring IoC 是如何對MapList變量進行賦值感興趣的話,我會在下一篇文章中講解相關的源碼和調試技巧。

我們搞技術的,知其然更應知其所以然嘛。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容