這陣子在作項目組重構的工做,工做中的一部分就是就目前代碼庫中與企業交互的邏輯抽離出來,單獨作一個微服務,實現企業交互邏輯的關注點分離。java
在這裏面我很天然而然的就用到了策略模式 + 工廠模式的方式,包裝內部實現細節,向外提供統一的調用方式,有效的減小if/else
的業務代碼,使得代碼更容易維護,擴展。spring
以前看過一些文章,是使用自定義註解+自動BeanProcessor的方式來實現,我的感受有點麻煩。由於Spring原生就提供相似的特性。app
本篇旨在介紹在實際的業務場景,如何藉助Spring IoC 依賴注入的特性,使用Spring 原生註解 來快速實現 策略模式 + 工廠模式。但願可以對你有啓發。框架
從原項目抽離出來的企業服務,承擔的是與外部企業交互的職責。不一樣企業,雖然會產生的交互行爲是相同的,可是交互行爲內部的實現邏輯各有不一樣,好比發送報文接口,不一樣企業可能報文格式會不一樣。ide
針對這種不一樣企業交互細節不一樣的場景,將與企業的交互行爲抽象出來EntStrategy
接口,根據服務消費者傳入的企業號選擇對應的實現類(策略類),邏輯簡化以後以下圖。微服務
如今讓咱們用快速用一個DEMO實現上述場景。this
咱們的指望目標是,根據不一樣的企業編號,咱們可以快速找到對應的策略實現類去執行發送報文的操做。spa
假設咱們如今對外提供的服務Api是這樣的,設計
/** * @param entNum 企業編號 */ public void send(String entNum) { // 根據不一樣的企業編號,咱們可以快速找到對應的策略實現類去執行發送報文的操做 }
如今咱們先定義個EntStrategy
接口3d
/** * @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 "其餘企業"; } @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(); } }
下面的設計是消除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值則爲對應的策略實現類。
到這一步,實際上咱們的指望功能大部分已經實現了,先讓用一個簡單的啓動類試一下。
/** * @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); } // 用這個方法模擬 企業代理服務 提供的Api public static void send(String entNum) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class); context.getBean(EntStrategyHolder.class).getBy(entNum).send(); } }
輸出結果
發送B標準的報文給對應企業 發送默認標準的報文給對應企業
你們眼睛若是稍微利索的點的話,會發現我上面啓動類裏面的企業編號entNum
填的其實是bean id
的值。那在實際業務中確定是不會這樣的,怎麼可能把一個企業編號定義的這麼奇怪呢。
因此這裏還須要一步操做,將傳入的企業編號,轉義成對應的策略類的bean id
。
實際上這一步的邏輯和你的實際業務是有很強的相關性的,由於在我業務裏面的entNum
在實際上就是一種標識,程序怎麼識別解析這個標識,找到對應的策略實現類,應該是根據你的業務需求定製的。
我這裏把這一步也寫出來,主要是想給你們提供一種思路。
由於個人微服務是用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 ....省略
這裏注意哦,要實現對應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 * @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(); } }
輸出結果
發送A標準的報文給對應企業 發送B標準的報文給對應企業 發送默認標準的報文給對應企業
上面的代碼中我採用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依賴注入的特性,實現了策略實現類的註冊過程(這一步本身實現會須要不少工做,而且代碼不會很好看)。
實際上除了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
能夠看到entStrategyList
被成功賦值了。
只不過這個特性我暫時沒有找到應用場景,因此單獨拿出來講一下。
到這裏,整個實現過程已經介紹完了。
過程當中用了到Spring最經常使用的一些註解,經過Spring IoC依賴注入的特性,實現了策略實現類的註冊過程,最終實現了整個功能。
但願能對你有所啓發。
另外,若是你對上述Spring IoC 是如何對Map
和List
變量進行賦值感興趣的話,我會在下一篇文章中講解相關的源碼和調試技巧。
咱們搞技術的,知其然更應知其因此然嘛。
本文由博客一文多發平臺 OpenWrite 發佈!