SpringBoot如何通過Map實現(xiàn)天然的策略模式
前言
策略模式是一種行為設(shè)計模式,它允許在運行時選擇算法的行為。在Spring框架中,我們可以利用@Resource注解和Map集合來優(yōu)雅地實現(xiàn)策略模式。
在Spring框架中,當(dāng)你使用@Resource注解注入一個Map<String, T>或List時,Spring會自動將所有類型為T的bean收集到這個Map或者List集合中,其中:
Map
- Key是bean的名稱
- Value是bean實例
List
- bean實例
底層機制解析
Spring的集合類型自動裝配
Spring框架對集合類型的依賴注入有特殊處理:
- 當(dāng)注入List時,會收集所有類型為T的bean
- 當(dāng)注入Map<String, T>時,會收集所有類型為T的bean,并以bean名稱作為key
@Resource注解的行為
@Resource注解默認(rèn)按名稱裝配,但當(dāng)目標(biāo)是一個Map時,Spring會特殊處理:
- 如果Map的key是String類型,value是某個接口/類
- Spring會查找所有實現(xiàn)該接口/繼承該類的bean
- 將這些bean以"bean名稱->bean實例"的形式放入Map
實現(xiàn)原理
Spring在依賴注入時的處理流程:
- 發(fā)現(xiàn)字段/方法參數(shù)是Map<String, T>類型
- 在應(yīng)用上下文中查找所有類型為T的bean
- 創(chuàng)建一個新的Map實例
- 遍歷找到的所有bean,以bean名稱作為key,bean實例作為value放入Map
- 將這個Map注入到目標(biāo)字段/參數(shù)中
使用
直接使用Map<String,T>
我們直接定義一個Controller,并且在Controller中使用@Resource和Map<String,T>
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private Map<String, Object> beanMap = new ConcurrentHashMap<>();
public void beanMap() {
System.out.println(beanMap.size());
}
}
驗證:
可以看到map中存了項目中所有的bean對象

指定Map中的bean類型
在實際的開發(fā)中,我們希望Map中只是存儲需要的Bean,并且Controller中可以根據(jù)beanName進(jìn)行轉(zhuǎn)發(fā)到不同的Service中,步驟如下:
定義策略接口
public interface PaymentStrategy {
void pay();
}
定義實現(xiàn)類
@Service("ALI")
@Slf4j
public class AliStrategyService implements PaymentStrategy {
@Override
public void pay() {
log.info("使用支付寶支付");
}
}
@Service("WX")
@Slf4j
public class WxStrategyService implements PaymentStrategy {
@Override
public void pay() {
log.info("使用微信支付");
}
}
策略使用
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private Map<String, PaymentStrategy> beanMap = new ConcurrentHashMap<>();
public void beanMap() {
PaymentStrategy wx = beanMap.get("WX");
wx.pay();
PaymentStrategy ali = beanMap.get("ALI");
ali.pay();
}
}
驗證
可以看到map中,就只有兩個Bean,并且key就是我們通過@Service(value)定義的名稱


自定義注解實現(xiàn)
- 自定義一個注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface PaymentType {
String value();
}
- 注解替換:將原有的
@Service(value)替換為@PaymentType (value),比如:
@PaymentType("CARD")
@Slf4j
public class CardStrategyService implements PaymentStrategy {
@Override
public void pay() {
log.info("使用銀行卡支付");
}
}
- 意義 可以更好表示策略模式,讓其他開發(fā)人員一眼可以看出
當(dāng)前的Service使用了策略模式
結(jié)合List進(jìn)行優(yōu)化
在上面直接使用Map來注入Bean的場景下,如果Service(“xxName”)的名稱和某個枚舉或者常量一致的情況,并且會在多處使用,那么枚舉或者常量變更后Service(“xxName”)沒有修改到就容易出現(xiàn)問題(啟動不會報錯,容易忽略修改);
- 可以結(jié)合List的方式來解決這個問題,比如存在常量如下:
public interface PayWayConstants {
String ALI_PAY = "ALI";
String WX_PAY = "WX";
}
- 為
PaymentStrategy接口添加一個公共的方法:getWay(),改造后如下:
@Service(“ALI”)中不再需要寫value值
// 接口
public interface PaymentStrategy {
String getWay();
void pay();
}
// 各個實現(xiàn)類
@Service
@Slf4j
public class AliStrategyService implements PaymentStrategy {
@Override
public String getWay() {
return PayWayConstants.ALI_PAY;
}
@Override
public void pay() {
log.info("使用支付寶支付");
}
}
@Service
@Slf4j
public class WxStrategyService implements PaymentStrategy {
@Override
public String getWay() {
return PayWayConstants.WX_PAY;
}
@Override
public void pay() {
log.info("使用微信支付");
}
}
- Bean的注入調(diào)整,單獨使用一個類來加載這些策略模式的Bean對象
@Component
public class PaymentStrategyComponent {
private Map<String, PaymentStrategy> beanMap = new ConcurrentHashMap<>();
@Resource
private List<PaymentStrategy> list;
@PostConstruct
public void init() {
for (PaymentStrategy service : list) {
beanMap.put(service.getWay(), service);
log.info("Registered: {}", service.getWay());
}
}
public PaymentStrategy getPaymentStrategy(String way) {
return beanMap.get(way);
}
}
- 策略使用,使用時就可以使用常量來代替字符串的形式,這樣便于后期常量或者枚舉值的變更
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private PaymentStrategyComponent strategyComponent;
public void beanMap() {
PaymentStrategy wx = strategyComponent.getPaymentStrategy(PayWayConstants.WX_PAY);
wx.pay();
PaymentStrategy ali = strategyComponent.getPaymentStrategy(PayWayConstants.ALI_PAY);
ali.pay();
}
}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- SpringBoot 對象轉(zhuǎn)換 MapStruct的實現(xiàn)小結(jié)
- SpringBoot整合通用mapper實現(xiàn)泛型BaseController增刪改方式
- SpringBoot 模糊映射(Ambiguous mapping)報錯解決指南
- SpringBoot3集成MapstructPlus的實現(xiàn)
- SpringBoot注解@MapperScan的實現(xiàn)
- 在SpringBoot中通過@Value注入Map和List并使用YAML配置的詳細(xì)教程
- SpringBoot之@Controller和@RequestMapping的實現(xiàn)原理解讀
相關(guān)文章
MyBatis-Plus實現(xiàn)條件查詢的三種格式例舉詳解
本文主要介紹了MyBatis-Plus三中條件查詢格式的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
基于SpringBoot?使用?Flink?收發(fā)Kafka消息的示例詳解
這篇文章主要介紹了基于SpringBoot?使用?Flink?收發(fā)Kafka消息,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01
idea創(chuàng)建Spring項目的方法步驟(圖文)
這篇文章主要介紹了idea創(chuàng)建Spring項目的方法步驟(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
Spring boot 4 搞懂MyBatis-Plus的用法解析
MyBatis-Plus是MyBatis的增強工具,提供了CRUD操作和自動填充等功能,通過繼承BaseMapper接口,可以快速實現(xiàn)數(shù)據(jù)庫操作,MyBatis-Plus提供了多種插件,如分頁、樂觀鎖、多租戶等,以增強系統(tǒng)的功能和性能,本文介紹Spring boot 4 搞懂MyBatis-Plus的用法,感興趣的朋友一起看看吧2026-01-01
SpringMVC使用hibernate-validator進(jìn)行參數(shù)校驗最佳實踐記錄
這篇文章主要介紹了SpringMVC使用hibernate-validator進(jìn)行參數(shù)校驗最佳實踐,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05
Mybatis實現(xiàn)Mapper動態(tài)代理方式詳解
這篇文章主要為大家詳細(xì)介紹了Mybatis實現(xiàn)Mapper動態(tài)代理方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
最新log4j2遠(yuǎn)程代碼執(zhí)行漏洞(附解決方法)
Apache?Log4j2?遠(yuǎn)程代碼執(zhí)行漏洞攻擊代碼,該漏洞利用無需特殊配置,經(jīng)多方驗證,Apache?Struts2、Apache?Solr、Apache?Druid、Apache?Flink等均受影響,本文就介紹一下解決方法2021-12-12

