文章目录
在实际开发中,我们经常会遇到需要根据不同的业务类型选择不同处理逻辑的情况。比如:
- 根据用户选择的支付方式(支付宝、微信、银联)执行不同的支付逻辑
- 根据报告类型(PDF、Word、Excel)生成不同格式的文件
- 根据大模型名称(GPT、Claude、Qwen)调用不同模型服务
这时候,使用 策略模式(Strategy Pattern) 是一个非常合适的选择。
本文将介绍如何利用 Spring 框架中 @Resource
注入 Map 的特性,优雅地实现策略模式。
✅ 一、什么是策略模式?
策略模式是一种行为设计模式,它允许定义一系列算法,并将每个算法封装起来,使它们可以互换使用。
通俗点说:
我们有一组“相似”的处理实现类,通过传入不同的参数,动态选择其中某一个实现类来执行。
✅ 二、Spring 如何注入 Map?
使用@Resource自动将容器中所有 SomeService
类型的 Bean 收集起来
Spring 提供了一个强大的能力:
当你声明如下字段时:
@Resource
private Map<String, SomeService> someServiceMap;
Spring 会自动将容器中所有 SomeService
类型的 Bean 收集起来,并以 Bean 名称 -> Bean 实例 的形式放入这个 Map 中。
这为我们实现策略模式提供了极大的便利。
为什么用 @Resource
而不是 @Autowired
?
注解 | 说明 |
---|---|
@Resource | 默认按 名称 注入(name 属性),也可以指定 name 。适合用来注入 Map、List 等集合类型。 |
@Autowired | 默认按 类型 注入,不能直接用于 Map 注入多个同类型 Bean |
所以在这里,我们要注入的是:
Map<String, LlmHandlerService>
只有 @Resource
可以自动识别并注入这些 Bean 到 Map 中。
✅ 三、完整实现步骤
1️⃣ 定义统一接口
public interface ReportGenerator {
void generate();
}
2️⃣ 编写多个实现类并注册为 Spring Bean
@Service("pdfReportGenerator")
public class PdfReportGenerator implements ReportGenerator {
@Override
public void generate() {
System.out.println("Generating PDF report...");
}
}
@Service("excelReportGenerator")
public class ExcelReportGenerator implements ReportGenerator {
@Override
public void generate() {
System.out.println("Generating Excel report...");
}
}
@Service("wordReportGenerator")
public class WordReportGenerator implements ReportGenerator {
@Override
public void generate() {
System.out.println("Generating Word report...");
}
}
3️⃣ 创建策略管理器(核心)
@Component
public class ReportGenerationStrategyManager {
@Resource
private Map<String, ReportGenerator> reportGeneratorMap;
public ReportGenerator getStrategy(String type) {
ReportGenerator strategy = reportGeneratorMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported report type: " + type);
}
return strategy;
}
}
4️⃣ 使用方式
@RestController
@RequestMapping("/report")
public class ReportController {
@Autowired
private ReportGenerationStrategyManager strategyManager;
@GetMapping("/{type}")
public String generateReport(@PathVariable String type) {
ReportGenerator generator = strategyManager.getStrategy(type);
generator.generate();
return "Report generated with type: " + type;
}
}
✅ 四、调用测试
访问以下 URL:
GET /report/pdf
GET /report/excel
GET /report/word
输出结果分别为:
Generating PDF report...
Generating Excel report...
Generating Word report...
如果传入不支持的类型,如 /report/html
,会抛出异常:
IllegalArgumentException: Unsupported report type: html
✅ 五、优化建议(推荐做法)
虽然上面的方式已经足够简洁,但为了增强可读性和安全性,我们可以进一步优化:
✅ 使用枚举代替字符串参数
public enum ReportType {
PDF,
EXCEL,
WORD
}
然后修改策略管理器:
public ReportGenerator getStrategy(ReportType type) {
String beanName = type.name().toLowerCase() + "ReportGenerator";
ReportGenerator strategy = reportGeneratorMap.get(beanName);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported report type: " + type);
}
return strategy;
}
这样我们在调用时就可以使用枚举,避免拼写错误:
generator = strategyManager.getStrategy(ReportType.PDF);
✅ 六、优势总结
优势 | 描述 |
---|---|
✅ 简洁易维护 | 不需要硬编码 switch-case 或 if-else 判断 |
✅ 可扩展性强 | 新增策略只需添加新实现类,无需改动已有代码 |
✅ 高内聚低耦合 | 所有策略统一管理,解耦业务逻辑与实现细节 |
✅ 强类型安全 | 推荐结合枚举使用,避免字符串拼写错误 |
✅ 七、适用场景
- 多种支付方式切换
- 多种消息通知渠道(短信、邮件、企业微信)
- 多语言翻译适配
- 多个 AI 模型服务调用
- 多个数据源适配器
✅ 八、注意事项
- 确保每个策略类都加上了
@Service
并指定了唯一的 bean name - 推荐显式命名 bean,避免 Spring 自动生成的名称不可控
- 如果策略类较多,建议配合日志打印加载情况,方便排查问题
- 可以考虑加入缓存机制或懒加载策略