SpringBoot如何通过Map实现天然的策略模式
前言
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式。
在Spring框架中,当你使用@Resource注解注入一个Map<String, T>或List时,Spring会自动将所有类型为T的bean收集到这个Map或者List集合中,其中:
Map
- Key是bean的名称
- Value是bean实例
List
- bean实例
底层机制解析
Spring的集合类型自动装配
Spring框架对集合类型的依赖注入有特殊处理:
- 当注入List时,会收集所有类型为T的bean
- 当注入Map<String, T>时,会收集所有类型为T的bean,并以bean名称作为key
@Resource注解的行为
@Resource注解默认按名称装配,但当目标是一个Map时,Spring会特殊处理:
- 如果Map的key是String类型,value是某个接口/类
- Spring会查找所有实现该接口/继承该类的bean
- 将这些bean以"bean名称->bean实例"的形式放入Map
实现原理
Spring在依赖注入时的处理流程:
- 发现字段/方法参数是Map<String, T>类型
- 在应用上下文中查找所有类型为T的bean
- 创建一个新的Map实例
- 遍历找到的所有bean,以bean名称作为key,bean实例作为value放入Map
- 将这个Map注入到目标字段/参数中
使用
直接使用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类型
在实际的开发中,我们希望Map中只是存储需要的Bean,并且Controller中可以根据beanName进行转发到不同的Service中,步骤如下:
定义策略接口
public interface PaymentStrategy {
void pay();
}
定义实现类
@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)定义的名称


自定义注解实现
- 自定义一个注解
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("使用银行卡支付");
}
}
- 意义 可以更好表示策略模式,让其他开发人员一眼可以看出
当前的Service使用了策略模式
结合List进行优化
在上面直接使用Map来注入Bean的场景下,如果Service(“xxName”)的名称和某个枚举或者常量一致的情况,并且会在多处使用,那么枚举或者常量变更后Service(“xxName”)没有修改到就容易出现问题(启动不会报错,容易忽略修改);
- 可以结合List的方式来解决这个问题,比如存在常量如下:
public interface PayWayConstants {
String ALI_PAY = "ALI";
String WX_PAY = "WX";
}
- 为
PaymentStrategy接口添加一个公共的方法:getWay(),改造后如下:
@Service(“ALI”)中不再需要写value值
// 接口
public interface PaymentStrategy {
String getWay();
void pay();
}
// 各个实现类
@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的注入调整,单独使用一个类来加载这些策略模式的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();
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
IDEA报错java.lang.OutOfMemoryError:Java heap space的解决办法
这篇文章主要给大家介绍了关于IDEA报错java.lang.OutOfMemoryError:Java heap space的解决办法,出现这个问题的主要原因是项目运行时的堆内存不足引起的报错,需要的朋友可以参考下2024-02-02
Java数据库连接PreparedStatement的使用详解
这篇文章主要介绍了Java数据库连接PreparedStatement的使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-08-08
Springboot 如何指定获取出 yml文件里面的配置值
这篇文章主要介绍了Springboot 如何指定获取出 yml文件里面的配置值操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07
cookie+mybatis+servlet实现免登录时长两天半的整体流程
这篇文章主要介绍了cookie+mybatis+servlet实现免登录时长两天半,主要用到的技术有session、cookie、转发、重定向、filter、和servlet,最重要的还是具体的来运用它们在前端页面真正的搭建出一个应用,通过这个练习,对我们所学的web知识做一个整合,需要的朋友可以参考下2022-10-10
解决java.sql.SQLException:The server time zone value &apo
这篇文章主要介绍了解决java.sql.SQLException:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-03-03
解决springboot 实体类String转Date类型的坑
这篇文章主要介绍了解决springboot 实体类String转Date类型的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-10-10


最新评论