← 返回题目列表

策略模式在 Spring 项目中如何落地?

高频 中等 第 9 / 26 题 更新于 2026/07/28
策略模式Spring依赖注入工程实践

简化版

在 Spring 中落地策略模式,通常是让每个策略实现同一个接口并注册成 Bean,再通过 List<Strategy>Map<String, Strategy> 注入到工厂中,按业务类型选择策略执行。这样新增策略时主流程基本不用改。

详细版

Spring 很适合承载策略模式,因为它可以自动管理策略对象的生命周期和依赖。

常见做法是:

  1. 定义策略接口,例如 NotifyStrategy
  2. 每种通知方式实现接口,例如短信、邮件、站内信;
  3. 每个策略类加 @Component
  4. 工厂注入所有策略 Bean,构建类型到策略的 Map;
  5. 业务服务只调用工厂获取策略并执行。

这种做法的优势是:

  • 策略对象由容器管理;
  • 新增策略只需新增 Bean;
  • 策略可以继续注入自己的依赖;
  • 主流程保持稳定。

需要注意的是,Spring Bean 默认是单例,策略类不要保存某次请求的临时状态。请求参数应该通过方法入参传递。

完整版教学

一、为什么 Spring 和策略模式很搭

策略模式的一个实际问题是:策略对象从哪里来?

在普通 Java 中,你可能需要手动 new 策略,再放进工厂。Spring 提供了依赖注入和 Bean 管理能力,可以让策略类自然地成为容器中的组件。

这带来几个好处:

  • 策略可以自动注入 DAO、远程客户端、配置类;
  • 工厂不用关心策略如何创建;
  • 新增策略时只要加一个实现类;
  • 可以在启动时检查策略是否重复注册。

所以在 Spring 项目里,策略模式常常和依赖注入一起使用。

二、以通知渠道为例设计接口

假设系统支持多种通知渠道:

  • 短信;
  • 邮件;
  • 站内信;
  • Webhook。

可以定义统一接口:

public interface NotifyStrategy {
    String channel();
    void send(NotifyRequest request);
}

channel() 用来标识策略类型,send() 执行具体发送逻辑。

每个策略实现自己的渠道:

@Component
public class SmsNotifyStrategy implements NotifyStrategy {
    public String channel() {
        return "sms";
    }

    public void send(NotifyRequest request) {
        // 调用短信服务
    }
}

三、用 List 注入所有策略

Spring 可以把同一接口的所有 Bean 注入成一个列表:

@Component
public class NotifyStrategyFactory {
    private final Map<String, NotifyStrategy> strategyMap;

    public NotifyStrategyFactory(List<NotifyStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(NotifyStrategy::channel, s -> s));
    }

    public NotifyStrategy get(String channel) {
        NotifyStrategy strategy = strategyMap.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("unsupported channel: " + channel);
        }
        return strategy;
    }
}

业务服务使用时:

public void notify(NotifyRequest request) {
    NotifyStrategy strategy = factory.get(request.getChannel());
    strategy.send(request);
}

主流程非常稳定:拿策略,执行策略。

四、也可以利用 Bean 名称做映射

Spring 还支持直接注入:

private final Map<String, NotifyStrategy> strategyMap;

这个 Map 的 key 默认是 Bean 名称。比如 smsNotifyStrategy

这种方式少写一个 channel() 方法,但它把业务类型和 Bean 名称绑定在一起。面试中可以说:如果业务码需要清晰稳定,推荐策略自己暴露 type/channel,不要强依赖 Bean 名。

五、Spring 策略模式常见坑

第一,策略 Bean 默认单例。

不要这样写:

private NotifyRequest currentRequest;

如果多个请求并发进来,会产生线程安全问题。请求级数据应该放在方法参数里。

第二,策略选择不要散落。

不要每个 Service 都自己筛选策略,否则后续维护会变复杂。统一工厂更清晰。

第三,异常和默认策略要明确。

未知渠道是报错、忽略、还是走默认通知,要根据业务语义决定,不能随意吞掉。

第四,策略数量多时要考虑可观测性。

比如记录实际命中的策略、耗时、失败原因,排查线上问题会方便很多。

六、容器发现与业务 key 映射

系统有短信、邮件、站内信 3 个 Bean,启动时构建 3 项不可变映射;一次请求只按 channel key 查找并执行目标 Bean。

Spring beans -> collect -> key() uniqueness -> strategyMap -> request lookup

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性容器解决实例装配,不会替你定义 key 冲突、兜底、幂等和业务错误语义。
适用边界Spring Bean 默认单例,因此策略实现应无请求级可变字段;外部资源依赖仍通过构造器注入。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:容器解决实例装配,不会替你定义 key 冲突、兜底、幂等和业务错误语义。

八、常见误区与追问

  • 误区:给每个策略加 @Component 就算完整落地。 还需稳定接口、唯一 key、选择器、未知类型处理、监控和测试。
  • 误区:用了策略模式,系统里就不该再出现任何条件判断。 策略消除的是反复扩张的业务算法分支;选择策略、校验输入和兜底仍可能需要有限判断。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:为什么推荐构造器注入策略集合? 依赖完整且便于测试,也能在启动阶段统一校验映射。
  • 追问:策略对象可以保存本次请求的可变状态吗? 容器单例策略通常不应保存请求状态,应把订单、用户等数据作为参数传入,否则并发调用会相互污染。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

Spring 落地策略模式的关键词是“接口 + 多实现 Bean + 工厂 Map”。策略对象交给容器管理,业务服务只按类型取策略并执行;同时要注意单例无状态、类型映射清晰、异常兜底明确。