多模块Spring应用中@Autowired注入失效问题求助
你的核心问题在于:手动new出来的策略类实例不在Spring容器的管理范围内,所以Spring不会处理这些实例中的@Autowired注解,导致依赖对象全部为null。同时,当前的GameStrategySelector实现需要手动维护策略列表,不符合开闭原则,我们可以通过Spring的特性来优化它。
一、解决@Autowired为null的问题
1. 让Spring管理所有策略类
确保所有实现BuildGameStrategy的类都被Spring识别为Bean,比如保持LocalPlayerGameStrategy上的@Component注解,MultiPlayerGameStrategy和ErrorGameTypeStrategy也需要添加@Component(如果它们有依赖的话):
@Component public class MultiPlayerGameStrategy implements BuildGameStrategy { // 依赖注入和业务逻辑 } @Component public class ErrorGameTypeStrategy implements BuildGameStrategy { @Override public void buildGame(GameDto gameDto) { // 处理无效游戏类型的逻辑 } @Override public boolean applies(GameDto gameDto) { return false; // 永远不会被过滤选中,作为默认兜底策略 } }
2. 让Spring自动注入策略列表
修改GameStrategySelector,不再手动new实例,而是通过构造注入让Spring自动收集所有BuildGameStrategy类型的Bean:
@Service public class GameStrategySelector { private final List<BuildGameStrategy> strategyList; private final ErrorGameTypeStrategy errorStrategy; // 构造注入:Spring会自动把所有BuildGameStrategy类型的Bean注入到strategyList中 public GameStrategySelector(List<BuildGameStrategy> strategyList, ErrorGameTypeStrategy errorStrategy) { this.strategyList = strategyList; this.errorStrategy = errorStrategy; } public BuildGameStrategy chooseStrategy(GameDto gameDto) { return strategyList .stream() .filter(strategy -> strategy.applies(gameDto)) .findFirst() .orElse(errorStrategy); // 使用Spring管理的兜底策略,避免手动new } }
3. 替换字段注入为构造注入(推荐)
Spring官方推荐使用构造注入而非字段注入,它能保证依赖在Bean实例化时就被注入,避免null风险,同时让Bean不可变、线程安全:
@Component 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; } // 业务方法实现不变 }
二、更优的StrategySelector实现思路
1. 利用自定义注解标记策略适用类型
我们可以给每个策略类添加自定义注解,明确它支持的游戏类型,避免在applies()方法中硬编码判断逻辑,同时让策略匹配更清晰:
第一步:定义自定义注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component // 让Spring自动扫描带此注解的类 public @interface GameStrategy { PlayerType[] supportedPlayerTypes(); }
第二步:给策略类添加注解
@GameStrategy(supportedPlayerTypes = PlayerType.LOCAL) public class LocalPlayerGameStrategy implements BuildGameStrategy { // 依赖注入和业务逻辑 @Override public boolean applies(GameDto gameDto) { // 通过注解信息判断匹配性,避免硬编码 PlayerType[] supportedTypes = this.getClass().getAnnotation(GameStrategy.class).supportedPlayerTypes(); return gameDto.getPlayers().stream() .allMatch(p -> Arrays.stream(supportedTypes) .anyMatch(type -> type.name().equals(p.getPlayerType()))); } }
2. 策略优先级控制
如果存在多个策略可能匹配同一游戏类型,可以使用@Order注解指定优先级,Spring注入列表时会按顺序排列:
@Order(1) // 优先级更高,会被优先选中 @GameStrategy(supportedPlayerTypes = PlayerType.LOCAL) public class LocalPlayerGameStrategy implements BuildGameStrategy { // ... }
3. 简化主类注解
@SpringBootApplication已经包含了@Configuration、@EnableAutoConfiguration、@ComponentScan,所以你的主类可以简化:
@SpringBootApplication @EnableMethodSecurity @EntityScan(basePackages="com.rydzwr.tictactoe") @EnableJpaRepositories(basePackages="com.rydzwr.tictactoe") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
如果database模块的包不在com.rydzwr.tictactoe下,需要把对应的包添加到@ComponentScan、@EntityScan、@EnableJpaRepositories的basePackages中,比如:
@ComponentScan(basePackages={"com.rydzwr.tictactoe", "com.rydzwr.database"})
内容的提问来源于stack exchange,提问作者Kacper Rydzyński

