SpringBoot中如何基于请求参数选择服务实现?(工厂模式实践)
如何在SpringBoot中根据请求参数动态选择服务实现类
这是个非常典型的Spring场景问题,你的痛点我完全感同身受——手动实例化服务类会完全丧失Spring依赖注入的优势(比如实现类里的其他Bean依赖没法被自动注入),而硬编码多个自动装配的实现类又会让代码随着业务扩展变得越来越臃肿。
下面给你几个优雅的解决方案,既充分利用Spring的特性,又能灵活根据请求参数选择对应的服务实现,还符合开闭原则(新增实现类无需修改原有代码):
方案1:利用Spring自动注入Map实现策略模式
Spring有个非常实用的特性:当你注入Map<String, 接口类型>时,它会自动把所有该接口的实现类Bean注入到这个Map中,key是Bean的名称(默认是类名首字母小写,也可以通过@Service注解指定自定义名称),value就是对应的Bean实例。
步骤1:改造服务实现类
给每个PizzaService实现类添加@Service注解,并指定与请求参数type对应的Bean名称:
public abstract class PizzaService { String ingredients; public abstract void prepareIngredients(String extraIngredient); public void bakePizza() { System.out.println("baking pizza with " + ingredients); } } // Bean名称设为"cheese",对应请求参数type=cheese @Service("cheese") public class CheezePizza extends PizzaService { // 这里可以放心使用@Autowired注入其他依赖,Spring会自动处理 // @Autowired // private IngredientService ingredientService; @Override public void prepareIngredients(String extraIngredient) { ingredients = "Cheese " + extraIngredient; } } // Bean名称设为"vegetable",对应请求参数type=vegetable @Service("vegetable") public class VegetablePizza extends PizzaService { @Override public void prepareIngredients(String extraIngredient) { ingredients = "Vegetable " + extraIngredient; } }
步骤2:在Controller中注入Map并动态获取服务
直接在Controller中注入Map<String, PizzaService>,然后根据请求参数type从Map中获取对应的服务实现:
@RestController public class RestControllerTest { // Spring自动注入所有PizzaService实现类,key是Bean名称,value是实例 private final Map<String, PizzaService> pizzaServiceMap; // 推荐使用构造注入(比@Autowired更优雅,也利于单元测试) public RestControllerTest(Map<String, PizzaService> pizzaServiceMap) { this.pizzaServiceMap = pizzaServiceMap; } @GetMapping("/bake-pizza") public void bakePizza(@RequestParam("type") String type, @RequestParam("extra") String extra) { // 根据type获取对应的服务实现 PizzaService pizzaService = pizzaServiceMap.get(type); // 处理不支持的pizza类型 if (pizzaService == null) { throw new IllegalArgumentException("不支持的披萨类型: " + type); } pizzaService.prepareIngredients(extra); pizzaService.bakePizza(); } }
方案2:封装工厂类解耦(更适合复杂场景)
如果你的业务逻辑比较复杂,或者希望Controller更专注于请求处理,可以封装一个工厂类来统一管理服务实例的获取:
步骤1:创建Pizza服务工厂
@Component public class PizzaServiceFactory { private final Map<String, PizzaService> pizzaServiceMap; public PizzaServiceFactory(Map<String, PizzaService> pizzaServiceMap) { this.pizzaServiceMap = pizzaServiceMap; } public PizzaService getPizzaService(String type) { PizzaService service = pizzaServiceMap.get(type); if (service == null) { throw new IllegalArgumentException("不支持的披萨类型: " + type); } return service; } }
步骤2:在Controller中使用工厂类
@RestController public class RestControllerTest { private final PizzaServiceFactory pizzaServiceFactory; public RestControllerTest(PizzaServiceFactory pizzaServiceFactory) { this.pizzaServiceFactory = pizzaServiceFactory; } @GetMapping("/bake-pizza") public void bakePizza(@RequestParam("type") String type, @RequestParam("extra") String extra) { PizzaService pizzaService = pizzaServiceFactory.getPizzaService(type); pizzaService.prepareIngredients(extra); pizzaService.bakePizza(); } }
为什么这两种方案更好?
- 充分利用Spring依赖注入:所有服务实现类都是Spring管理的Bean,里面的
@Autowired依赖会被正确注入,不会出现手动实例化导致的空指针问题。 - 符合开闭原则:后续新增披萨类型时,只需要新增一个
PizzaService实现类并标注@Service("new-type"),无需修改Controller或工厂类的代码。 - 代码更简洁:避免了大量的
if-else判断,也不需要硬编码多个自动装配的字段。
内容的提问来源于stack exchange,提问作者lemzki
相关产品推荐
相关产品推荐

