【问题标题】:How to autowire a service in Spring Boot and pass dynamic parameters to the constructor如何在 Spring Boot 中自动装配服务并将动态参数传递给构造函数
【发布时间】:2022-01-26 08:46:21
【问题描述】:

我正在开发一个 Spring Boot 项目,但在尝试自动装配服务时遇到了一些问题。这是我当前的代码:

@Component
public class StealFromPocket implements Command {
    private Integer gameId;
    private Player playerFrom;

    @Autowired
    GameService gameService;

    public StealFromPocket(Integer gameId, Player playerFrom) {
        this.gameId = gameId;
        this.playerFrom = playerFrom;
    }

    @Override
    public void execute() {
        gameService.findPlayersByGameId(gameId).forEach(player -> {
            new StealCoinCommand(playerFrom, player).execute();
        });

    }
}

构造函数参数gameIdplayerFrom 在运行时是已知的,因此我无法对它们进行硬编码或将它们存储在配置文件中。

这是 StealFromPocket 通过反射从 service 实例化的地方(completeClassName 将根据用户交互而变化。在本例中,它将是 org.springframework...StealFromPocket

Class<?> clazz = Class.forName(completeClassName);
Object card = clazz.getDeclaredConstructor(Integer.class, Player.class).newInstance(gameId, playerFrom);
Method method = clazz.getDeclaredMethod("execute");
method.invoke(card);

在尝试构建项目时,我收到以下错误:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in org.springframework.ntfh.cardlogic.abilitycard.rogue.RobarBolsillos required a bean of type 'java.lang.Integer' that could not be found.


Action:

Consider defining a bean of type 'java.lang.Integer' in your configuration.

我认为问题在于我没有将GameService 作为构造函数参数传递,但由于我的项目的性质,这些服务不能作为参数传递,因为它们会根据通过反射实例化的类而有所不同。

【问题讨论】:

  • 你可以在 GameService 中创建一个函数来获取卡片对象,而不是调用“method.invoke”,然后你可以移动到该函数内部的逻辑并在之后调用它,如“gameService.invoke(card)”实例化卡片对象。这允许您删除 Command 类中的 GameService 字段
  • 非常感谢您的回答。拥有单独类的目的是解耦这两个组件的逻辑,因为将有大约 50 个类,如 StealFromPocket,每个类都有自己的逻辑和服务依赖项,因此将它们全部放在 GameService 类中就可以了很大,很难理解。
  • 当您尝试使用 new 关键字或通过反射进行实例化时,我不认为 spring 可以注入自动装配的 bean,但您可以尝试从“GameService”字段中删除“@Autowired”,但将其保留为该类中的字段,之后您接受 2 个构造函数参数,即“gameId”和“playerForm”
  • 当你调用“newInstance”方法时编译器不应该抱怨实例化,因为“GameService”不再有“@Autowired”并且不在构造函数中,所以你可以在之后从“applicationContext”获取那个bean实例化卡片并调用 "card.setGameService(gameService)" ,您在此对象中设置了 "gameService"。所以你现在可以调用方法调用了。让我知道这是否有效
  • 您可能需要将“setGameService”方法添加到您的“Command”接口中

标签: java spring spring-boot constructor autowired


【解决方案1】:

我通过更改类的实现来解决此问题,以便execute 方法接收相应的参数,并且自动装配字段是StealFromPocket 类的唯一属性。现在看起来像这样:

@Component
public class StealFromPocket {

    @Autowired
    private GameService gameService;

    public void execute(Player playerFrom) {
        Integer gameId = playerFrom.getGame().getId();
        gameService.findPlayersByGameId(gameId).forEach(player -> {
            new StealCoinCommand(playerFrom, player).execute();
        });
    }
}

GameService.java 中,我执行以下操作:

// Get the class from its name
Class<?> clazz = Class.forName(completeClassName);
// Instantiate an object of the class
Object cardCommand = clazz.getDeclaredConstructor().newInstance();
// Autowire the new object's dependencies (services used inside)
AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
factory.autowireBean(cardCommand);
factory.initializeBean(cardCommand, className);
// Get a reference to the method "execute", that receives 2 parameters
Method method = clazz.getDeclaredMethod("execute", Player.class);
// Execute the method with the parameters
method.invoke(cardCommand, playerFrom);

【讨论】:

  • factory.autowireBean(cardCommand); factory.initializeBean(cardCommand, className);如果这些行允许注入自动装配的 bean,那么您的实现似乎是正确的。
  • 是的,现在一切正常,我不得不使用这些方法手动注入 bean,因为我使用 Java 的 new 关键字创建了一个 cardCommand 对象,它不会告诉 spring 注入自动依赖。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-04
  • 2015-02-20
  • 1970-01-01
  • 2013-06-07
  • 2014-09-29
  • 1970-01-01
  • 2018-04-13
相关资源
最近更新 更多