SpringBoot如何通过Map实现天然的策略模式

 更新时间:2026年03月02日 09:52:04   作者:一恍过去  
文章介绍了如何在Spring框架中使用@Resource注解和Map集合来实现策略模式,并详细解释了Spring如何处理集合类型的依赖注入,通过这种方式,可以在运行时动态地选择算法的行为,使得代码更加灵活和可扩展

前言

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在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中使用@ResourceMap<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的解决办法

    这篇文章主要给大家介绍了关于IDEA报错java.lang.OutOfMemoryError:Java heap space的解决办法,出现这个问题的主要原因是项目运行时的堆内存不足引起的报错,需要的朋友可以参考下
    2024-02-02
  • Java数据库连接PreparedStatement的使用详解

    Java数据库连接PreparedStatement的使用详解

    这篇文章主要介绍了Java数据库连接PreparedStatement的使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Springboot 如何指定获取出 yml文件里面的配置值

    Springboot 如何指定获取出 yml文件里面的配置值

    这篇文章主要介绍了Springboot 如何指定获取出 yml文件里面的配置值操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • SpringBoot前后端分离实现个人博客系统

    SpringBoot前后端分离实现个人博客系统

    这篇文章主要为大家详细介绍了使用springboot+mybatis+前端vue,使用前后端分离架构实现的个人博客系统,感兴趣的小伙伴可以动手尝试一下
    2022-06-06
  • 详解如何使用Mybatis的拦截器

    详解如何使用Mybatis的拦截器

    MyBatis 拦截器是 MyBatis 提供的一个强大特性,它允许你在 MyBatis 执行其核心逻辑的关键节点插入自定义逻辑,从而改变 MyBatis 的默认行为,本文给大家详细介绍了如何使用Mybatis的拦截器,需要的朋友可以参考下
    2024-03-03
  • cookie+mybatis+servlet实现免登录时长两天半的整体流程

    cookie+mybatis+servlet实现免登录时长两天半的整体流程

    这篇文章主要介绍了cookie+mybatis+servlet实现免登录时长两天半,主要用到的技术有session、cookie、转发、重定向、filter、和servlet,最重要的还是具体的来运用它们在前端页面真正的搭建出一个应用,通过这个练习,对我们所学的web知识做一个整合,需要的朋友可以参考下
    2022-10-10
  • 解决java.sql.SQLException:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized问题

    解决java.sql.SQLException:The server time zone value &apo

    这篇文章主要介绍了解决java.sql.SQLException:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • java控制台输出百分比进度条示例

    java控制台输出百分比进度条示例

    这篇文章主要介绍了java控制台输出百分比进度条示例,需要的朋友可以参考下
    2014-04-04
  • 解决springboot 实体类String转Date类型的坑

    解决springboot 实体类String转Date类型的坑

    这篇文章主要介绍了解决springboot 实体类String转Date类型的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java中使用Socket发送Java对象实例

    Java中使用Socket发送Java对象实例

    这篇文章主要介绍了Java中使用Socket发送Java对象实例,本文使用对象流直接发送对象,本文同时给出代码实例,需要的朋友可以参考下
    2015-05-05

最新评论