[純乾貨] 如何用Spring 原生註解 快速實現策略模式+工廠模式
前言
這陣子在做專案組重構的工作,工作中的一部分就是就目前程式碼庫中與企業互動的邏輯抽離出來,單獨做一個微服務,實現企業互動邏輯的關注點分離。
在這裡面我很自然而然的就用到了策略模式 + 工廠模式的方式,包裝內部實現細節,向外提供統一的呼叫方式,有效的減少if/else
的業務程式碼,使得程式碼更容易維護,擴充套件。
之前看過一些文章,是使用自定義註解+自動BeanProcessor的方式來實現,個人感覺有點麻煩。因為Spring原生就提供類似的特性。
本篇旨在介紹在實際的業務場景,如何藉助Spring IoC 依賴注入的特性,使用Spring 原生註解 來快速實現 策略模式 + 工廠模式。希望能夠對你有啟發。
業務場景
從原專案抽離出來的企業服務,承擔的是與外部企業互動的職責。不同企業,雖然會產生的互動行為是相同的,但是互動行為內部的實現邏輯各有不同,比如傳送報文介面,不同企業可能報文格式會不同。
針對這種不同企業互動細節不同的場景,將與企業的互動行為抽象出來EntStrategy
介面,根據服務消費者傳入的企業號選擇對應的實現類(策略類),邏輯簡化之後如下圖。
快速實現
現在讓我們用快速用一個DEMO實現上述場景。
我們的期望目標是,根據不同的企業編號,我們能夠快速找到對應的策略實現類去執行傳送報文的操作。
Step 1 實現策略類
假設我們現在對外提供的服務Api是這樣的,
/**
* @param entNum 企業編號
*/
public void send(String entNum) {
// 根據不同的企業編號,我們能夠快速找到對應的策略實現類去執行傳送報文的操作
}
複製程式碼
現在我們先定義個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 "其他企業";
}
@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值則為對應的策略實現類。
到這一步,實際上我們的期望功能大部分已經實現了,先讓用一個簡單的啟動類試一下。
/**
* @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標準的報文給對應企業
傳送預設標準的報文給對應企業
複製程式碼
Step 3 別名轉換
大家眼睛如果稍微利索的點的話,會發現我上面啟動類裡面的企業編號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
上面的程式碼中我採用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
複製程式碼
可以看到entStrategyList
被成功賦值了。
只不過這個特性我暫時沒有找到應用場景,所以單獨拿出來說一下。
結語
到這裡,整個實現過程已經介紹完了。
過程中用了到Spring最常用的一些註解,通過Spring IoC依賴注入的特性,實現了策略實現類的註冊過程,最終實現了整個功能。
希望能對你有所啟發。
另外,如果你對上述Spring IoC 是如何對Map
和List
變數進行賦值感興趣的話,我會在下一篇文章中講解相關的原始碼和除錯技巧。
我們搞技術的,知其然更應知其所以然嘛。
本文由部落格一文多發平臺 OpenWrite 釋出!