Spring Boot 多模块应用中 @Autowired 失效的解决方案

在 spring boot 多模块项目中,手动使用 new 创建策略类实例会导致 spring 容器无法管理其生命周期,从而使得 @autowired 注入失败

;正确做法是让所有策略类成为 spring bean,并通过构造函数注入依赖列表。

Spring Boot 的依赖注入(DI)机制仅对由 Spring 容器创建和管理的 Bean 生效。当你在 GameStrategySelector 中直接使用 new LocalPlayerGameStrategy() 实例化策略类时,该对象完全脱离 Spring 上下文——即使类上标注了 @Component,也不会被自动装配其字段(如 gameService、userService 等),最终导致空指针异常。

✅ 正确做法:让策略类成为托管 Bean,并依赖构造注入

首先,确保所有策略实现类均被 Spring 扫描并注册为 Bean:

@Component  // 必须保留,使 Spring 能识别并托管
public class LocalPlayerGameStrategy implements BuildGameStrategy {
    private final GameService gameService;
    private final UserService userService;
    private final PlayerService playerService;

    // 构造函数注入 —— 推荐方式,保证不可变性与可测性
    public LocalPlayerGameStrategy(GameService gameService,
                                   UserService userService,
                                   PlayerService playerService) {
        this.gameService = gameService;
        this.userService = userService;
        this.playerService = playerService;
    }

    @Override
    @Transactional
    public void buildGame(GameDto gameDto) {
        // ... 实现逻辑保持不变
    }

    @Override
    public boolean applies(GameDto gameDto) {
        return gameDto.getPlayers().stream()
                .allMatch(p -> p.getPlayerType().equals(PlayerType.LOCAL.name()));
    }
}

同理,请为 MultiPlayerGameStrategy 和其他策略类添加构造注入,并移除所有 @Autowired 字段注解(避免与构造注入混用)。

接着,重构 GameStrategySelector,放弃手动 new 实例,改用 Spring 自动注入所有 BuildGameStrategy 实现:

@Service
public class GameStrategySelector {

    private final List strategyList;

    // Spring 会自动收集所有 @Component/@Service 实现 BuildGameStrategy 的 Bean
    public GameStrategySelector(List allStrategies) {
        this.strategyList = allStrategies;
    }

    public BuildGameStrategy chooseStrategy(GameDto gameDto) {
        return strategyList.stream()
                .filter(strategy -> strategy.applies(gameDto))
                .findFirst()
                .orElseGet(ErrorGameTypeStrategy::new); // 使用方法引用更安全
    }
}
? 提示:ErrorGameTypeStrategy 若也需依赖注入(如日志服务),同样应声明为 @Component 并采用构造注入;若仅为无状态占位符,可保留 new 创建(因其不依赖其他 Bean)。

? 补充验证要点

  • 确保 database 模块中的 @Service / @Repository 类位于 @ComponentScan 和 @EnableJpaRepositories 指定的包路径下(如 com.rydzwr.tictactoe.database),否则跨模块扫描会失败;
  • 若 game 模块未显式依赖 database 模块(Maven/Gradle),请检查 pom.xml 或 build.gradle 是否已正确定义
  • 不建议在主启动类重复添加 @EnableAutoConfiguration(@SpringBootApplication 已包含),冗余注解虽不影响功能,但降低可维护性。

✅ 总结

问题根源 解决方案
手动 new 实例绕过 Spring 容器 改用构造函数注入 List
字段级 @Autowired 与构造注入混用 统一使用构造注入,提升健壮性与测试友好性
跨模块 Bean 扫描遗漏 核实 @ComponentScan、@EntityScan、@EnableJpaRepositories 的 basePackages 是否覆盖全部模块包路径

遵循以上改造后,所有策略类将作为标准 Spring Bean 参与依赖注入,@Autowired(或更优的构造注入)即可正常工作。