策略模式在Spring Boot中的实战应用:动态数据源切换方案

本文通过"业务算法动态切换不同数据源"的典型场景,系统介绍了策略模式的核心概念与 Spring Boot 落地实践。文章从模式定义、角色构成出发,给出了完整的多文件项目结构和可直接运行的代码示例,重点演示了如何利用 Spring 的依赖注入特性实现策略的自动注册与运行时切换,彻底消除 if-else 分支。同时展示了新增数据源时如何做到零侵入扩展,完美践行开闭原则,提供了一套清晰、优雅、可复用的设计模板。

一、策略模式(Strategy Pattern)介绍

定义:策略模式定义了一系列算法(或功能),将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用它的客户端。

核心角色(对应你的案例)

  • 抽象策略(Strategy) :定义统一的业务接口(如数据拉取方法 fetchData)。
  • 具体策略(ConcreteStrategy) :实现接口的具体类(如 DBSourceApiSource)。
  • 上下文(Context) :持有策略引用并执行算法的类(如业务算法服务 AlgorithmContext)。

为什么用在这里(优点)

  • 消除臃肿的 if-else :切换数据源通过类型字符串直接从 Map 获取,业务逻辑内无任何条件分支。
  • 开闭原则:新增第 3 种数据源(如 Redis)只需新增类加注解,业务算法和调用方零修改。
  • 便于单元测试:可以轻松注入 Mock 策略类进行算法逻辑测试,无需依赖真实数据库或网络。

二、项目文件目录结构(Spring Boot 标准)

复制代码
src/main/java/com/example/demo/
├── DemoApplication.java                 // Spring Boot 启动类
├── strategy/                            // 策略包
│   ├── Strategy.java                    // 抽象策略接口
│   ├── DBSource.java                    // 具体策略A(数据库)
│   └── ApiSource.java                   // 具体策略B(外部API)
├── service/                             // 业务服务包
│   └── AlgorithmContext.java            // 上下文(核心业务算法)
└── controller/                          // 控制器包
    └── TestController.java              // REST接口(接收前端切换参数)

三、完整代码实现(每个类单独文件)

1. DemoApplication.java(启动类)
java 复制代码
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
2. strategy/Strategy.java(抽象策略接口)
java 复制代码
package com.example.demo.strategy;

public interface Strategy {
    // 统一的数据拉取契约
    String fetchData(String key);
}
3. strategy/DBSource.java(数据库具体策略)

注意@Component("db") 指定 Bean 名称,Spring 会将其自动注册到策略 Map 中,Key 为 "db"

java 复制代码
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

@Component("db")
public class DBSource implements Strategy {
    @Override
    public String fetchData(String key) {
        // 实际开发中此处放 JDBC / MyBatis / JPA 查询代码
        return "【DB数据库】内容 for key: " + key;
    }
}
4. strategy/ApiSource.java(API 接口具体策略)
java 复制代码
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

@Component("api")
public class ApiSource implements Strategy {
    @Override
    public String fetchData(String key) {
        // 实际开发中此处放 RestTemplate / WebClient / HttpClient 调用
        return "【API接口】内容 for key: " + key;
    }
}
5. service/AlgorithmContext.java(上下文 / 核心业务算法)

核心逻辑 :Spring 会自动将所有 Strategy 实现类注入到 Map<String, Strategy> 中。算法根据传入的 type(如 "db")直接获取策略,彻底消灭 if-else

java 复制代码
package com.example.demo.service;

import com.example.demo.strategy.Strategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class AlgorithmContext {

    @Autowired
    private Map<String, Strategy> strategyMap; // Key是@Component指定的名称,Value是策略实例

    public String executeAlgorithm(String type, String key) {
        // 1. 根据传入类型(如 "db")从Map中获取策略
        Strategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("未知的数据源类型: " + type);
        }

        // 2. 拉取原始数据(面向接口多态调用)
        String rawData = strategy.fetchData(key);

        // 3. 核心业务逻辑(假设:转大写 + 计算字符串长度)
        String result = "【算法结果】" + rawData.toUpperCase() + ",长度=" + rawData.length();

        // 4. 返回结果给调用方(Controller)
        return result;
    }
}
6. controller/TestController.java(RESTful 控制器)

前端只需传 type=dbtype=api,即可动态切换数据源。

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.AlgorithmContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Autowired
    private AlgorithmContext algorithmContext;

    @GetMapping("/compute")
    public String compute(
            @RequestParam String type,  // 前端传入 "db" 或 "api"
            @RequestParam String key    // 查询键值
    ) {
        return algorithmContext.executeAlgorithm(type, key);
    }
}

四、运行测试

启动 Spring Boot 应用后,在浏览器或 Postman 中访问:

  • 切换数据库源

    GET http://localhost:8080/compute?type=db&key=user_001

    输出:【算法结果】【DB数据库】内容 FOR KEY: USER_001,长度=25

  • 切换 API 源

    GET http://localhost:8080/compute?type=api&key=order_999

    输出:【算法结果】【API接口】内容 FOR KEY: ORDER_999,长度=26


五、如何扩展第 3 种数据源(演示开闭原则)

假设未来要接入 Redis 数据源,无需修改任何已有代码:

  1. 新建 strategy/RedisSource.java
java 复制代码
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

@Component("redis")  // 新类型
public class RedisSource implements Strategy {
    @Override
    public String fetchData(String key) {
        return "【Redis缓存】内容 for key: " + key;
    }
}
  1. 重启应用 ,前端直接传 type=redisAlgorithmContextController 一行代码都不用改,自动生效。