如何在工厂/策略模式运行时创建的对象中实现@Autowired服务注入?
在Spring中使用策略模式的优雅解决方案
嘿,这问题我太有发言权了——Spring里用策略模式完全没问题!你遇到的空指针问题,根本不是策略模式的锅,只是因为你手动new出来的SmartAction不在Spring容器的管理范围内,所以@Autowired自然没法帮你注入依赖。
先聊聊你提到的两个方案为啥不够优雅:
- 把所有Action都声明成Service:虽然能解决注入问题,但如果你的Action需要根据动态业务逻辑创建不同实例,或者Action数量很多,这种方式会让灵活性大打折扣;
- 把依赖服务作为方法参数传入:这会导致方法参数越来越多,违反单一职责原则,而且每次调用都要传依赖,代码会变得很臃肿。
下面给你几个更优雅的解决方案,按推荐程度排序:
方案一:用策略工厂封装(最推荐)
这是策略模式结合Spring的最佳实践,把所有策略实现交给Spring管理,再通过一个工厂类统一获取,完全符合开闭原则。
步骤1:给所有Action实现类加Spring注解
先让Spring接管这些策略类:
public interface Action { void doSomething(); } @Component // 加@Component让Spring管理 public class StupidAction implements Action{ @Autowired private MyService myService; public void doSomething(){ myService.changeSomething(); } } @Component // 同样加注解 public class SmartAction implements Action{ @Autowired private OtherService otherService; public void doSomething(){ otherService.doSmartThing(); } }
步骤2:创建策略工厂
Spring会自动把所有Action类型的Bean注入到Map中,key是Bean的默认名称(类名首字母小写),value是实例:
@Component public class ActionFactory { private final Map<String, Action> actionMap; // Spring 4.3+支持无@Autowired的构造方法注入 public ActionFactory(Map<String, Action> actionMap) { this.actionMap = actionMap; } public Action getAction(String actionType) { Action action = actionMap.get(actionType); if (action == null) { throw new IllegalArgumentException("不存在对应的Action类型: " + actionType); } return action; } }
步骤3:在控制器中使用工厂
@RestController @RequestMapping(path = "/") public class WorkflowController { @Autowired private ActionFactory actionFactory; @GetMapping("") ResponseEntity someAPI() { // 这里可以根据业务逻辑动态传入actionType,比如从请求参数、数据库获取 Action action = actionFactory.getAction("smartAction"); action.doSomething(); return ResponseEntity.ok().build(); } }
以后新增Action实现,只需要给类加@Component,完全不用修改工厂和控制器代码,完美适配开闭原则。
方案二:用ApplicationContext直接获取Bean
如果你的场景比较简单,不想写工厂类,可以直接在控制器中注入ApplicationContext来获取Bean:
@RestController @RequestMapping(path = "/") public class WorkflowController { @Autowired private ApplicationContext context; @GetMapping("") ResponseEntity someAPI() { // 根据类型获取Bean,要是有多个同类型Bean,可以用getBean(String name, Class<T> type) Action action = context.getBean(SmartAction.class); action.doSomething(); return ResponseEntity.ok().build(); } }
注意:这种方式适合简单场景,不要在业务代码中大量使用,不然会让代码和Spring容器耦合度太高。
方案三:用ObjectFactory/Provider获取原型Bean
如果你的Action是有状态的(比如需要每次创建新实例),可以用ObjectFactory或者javax.inject.Provider来延迟获取实例:
@RestController @RequestMapping(path = "/") public class WorkflowController { @Autowired private ObjectFactory<SmartAction> smartActionFactory; @GetMapping("") ResponseEntity someAPI() { // 每次调用getObject()都会创建新实例(前提是SmartAction的作用域是prototype) Action action = smartActionFactory.getObject(); action.doSomething(); return ResponseEntity.ok().build(); } }
记得给有状态的Action加上原型作用域:
@Component @Scope("prototype") public class SmartAction implements Action{ // ... }
关键注意点
- 所有策略实现类必须被Spring管理:要么加
@Component/@Service,要么在配置类中用@Bean声明; - 如果Action是有状态的,一定要设置作用域为
prototype,避免单例Bean的线程安全问题; - 尽量用工厂模式封装策略获取逻辑,减少代码耦合,提升可维护性。
内容的提问来源于stack exchange,提问作者ym_wp
相关产品推荐
相关产品推荐

