天天看點

使用Spring 原生注解來快速實作 政策模式 + 工廠模式

前言

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

在這裡面我很自然而然的就用到了政策模式 + 工廠模式的方式,包裝内部實作細節,向外提供統一的調用方式,有效的減少if/else的業務代碼,使得代碼更容易維護,擴充。

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

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

業務場景

從原項目抽離出來的企業服務,承擔的是與外部企業互動的職責。不同企業,雖然會産生的互動行為是相同的,但是互動行為内部的實作邏輯各有不同,比如發送封包接口,不同企業可能封包格式會不同。

針對這種不同企業互動細節不同的場景,将與企業的互動行為抽象出來EntStrategy接口,根據服務消費者傳入的企業号選擇對應的實作類(政策類),邏輯簡化之後如下圖。

快速實作

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

我們的期望目标是,根據不同的企業編号,我們能夠快速找到對應的政策實作類去執行發送封包的操作。

Step 1 實作政策類

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

/**  
 * @param entNum 企業編号  
 */  
public void send(String entNum) {  
    // 根據不同的企業編号,我們能夠快速找到對應的政策實作類去執行發送封包的操作  
}      

現在我們先定義個EntStrategy接口

/**  
 * @author Richard_yyf  
 */  
public interface EntStrategy {  
  
    String getStuff();  
  
    void send();  
}      

三個政策類

DefaultStrategy

@Component  
public class DefaultStrategy  implements EntStrategy {  
    @Override  
    public String getStuff() {  
        return "其他企業";  
    }  
  
    @Override  
    public void send() {  
        System.out.println("發送預設标準的封包給對應企業");  
    }  
  
    @Override  
    public String toString() {  
        return getStuff();  
    }  
}      

EntAStrategy

@Component  
public class EntAStrategy implements EntStrategy {  
    @Override  
    public String getStuff() {  
        return "企業A";  
    }  
  
    @Override  
    public void send() {  
        System.out.println("發送A标準的封包給對應企業");  
    }  
  
    @Override  
    public String toString() {  
        return getStuff();  
    }  
}      

EntBStrategy

@Component  
public class EntBStrategy implements EntStrategy {  
    @Override  
    public String getStuff() {  
        return "企業B";  
    }  
  
    @Override  
    public void send() {  
        System.out.println("發送B标準的封包給對應企業");  
    }  
  
    @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值則為對應的政策實作類。

使用Spring 原生注解來快速實作 政策模式 + 工廠模式

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

/**  
 * @author Richard_yyf  
 */  
@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);  
    }  
  
    // 用這個方法模拟 企業代理服務 提供的Api  
    public static void send(String entNum) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);  
        context.getBean(EntStrategyHolder.class).getBy(entNum).send();  
    }  
}      

輸出結果

發送B标準的封包給對應企業  
發送預設标準的封包給對應企業      

Step 3 别名轉換

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

是以這裡還需要一步操作,将傳入的企業編号,轉義成對應的政策類的bean id。

實際上這一步的邏輯和你的實際業務是有很強的相關性的,因為在我業務裡面的entNum在實際上就是一種辨別,程式怎麼識别解析這個辨別,找到對應的政策實作類,應該是根據你的業務需求定制的。

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

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

添加EntAlias類

/**  
 * @author Richard_yyf  
 */  
@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  
  
....省略      

這裡注意哦,要實作對應getter和setter的,不然屬性會注入不進去的。

改寫一下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  
 */  
@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();  
    }  
}      

輸出結果

發送A标準的封包給對應企業  
發送B标準的封包給對應企業  
發送預設标準的封包給對應企業      

非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=其他企業, entAStrategy=企業A, entBStrategy=企業B}  
defaultStrategy:其他企業  
entAStrategy:企業A  
entBStrategy:企業B  
===== implementation List =====  
[其他企業, 企業A, 企業B]  
其他企業  
企業A  
企業B      
使用Spring 原生注解來快速實作 政策模式 + 工廠模式

可以看到entStrategyList被成功指派了。

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

結語

到這裡,整個實作過程已經介紹完了。

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

希望能對你有所啟發。

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

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

作者:Richard_Yi