如何在不使用 ApplicationContext 按名称获取 Bean 的情况下自动装配非 Spring 对象

How to autowire in non Spring objects without using the ApplicationContext to get Beans by name

我目前正在开发一个 Spring 引导项目,但在自动装配方面遇到了一些问题。 首先,这是程序的功能:
有一个外部程序,用户可以在其中键入命令。这些命令在我使用的库(包装 API 的库)中触发一个事件,我的代码需要处理该事件。此事件包含原始命令文本,如“帮助”,我将其与可用命令列表相匹配。如果找到匹配的命令,它将被执行(带有所有事件数据,因为它可能包含特定信息)。
我有一个命令接口和该接口的基本实现。程序可以处理的每个命令都是该基本实现的子class。在我使用 Spring 之前,我只是将文本与工厂中的命令匹配,并将合适的命令作为新对象返回 (比如 return new Help();)。由于所有命令都是该接口实现的子class,它们都有一个执行命令的方法。每个对数据库的请求都发生在另一个 class 中,其中包含从不同命令执行方法中调用的静态方法。
现在有了 Spring,我有机会使用处理所有数据库通信的存储库,所以我不需要依赖这样的 class。但是,对于能够使用自动装配存储库的命令,它们需要由 Spring 控制。目前,我使用 ApplicationContext 通过名称接收 bean,并将所有命令定义为 @Configuration class 中的 @Bean。 (即使示例命令只使用一个,也有多个存储库!)

命令界面:

public interface Command {

    /*  Executes the command and forwards the event received by the library
     *  to the execution method which uses its information.
     */
    void execute(SomeEvent event);

    // other methods...
}

基本命令实现:

@Service
public class CommandImpl implements Command {

    // Can be used for unknown commands, does not do anything.
    @Override
    public void execute(SomeEvent event) {
        // subclasses contain the information, this one does not do anything
    }

    // other interface method implementations...
}

一个简单的命令实现:

public class Help extends CommandImpl {

    // SomeRepository refers to an Interface which extends a Spring JPA Repository like CrudRepository
    private final SomeRepository repo;

    @Autowired
    public Help(SomeRepository repo) {
        this.repo = repo;
    }

    @Override
    public void execute(SomeEvent event) {
        /* gets data from the SomeEvent object (like caller) and uses 
         * SomeRepository to receive database settings and permissions.
         * Displays help for the specific caller (only commands he/she can use 
         * based on the permissions received from the repository)
         */
    }
}

SpringApplicationContext包装器:

@Component
public class SpringContext implements ApplicationContextAware {

    private static ApplicationContext context;

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringContext.class);

    /*
     * Get a Spring-controlled object of a command implementation by name.
     */
    public static Command getCommandBean(String name) {
        Command command;
        try {
            command = context.getBean(name, Command.class);
        } catch (NoSuchBeanDefinitionException e) {
            LOGGER.error("The command \"" + name + "\" does not exist or is not defined as a Bean!");

            // returning the base implementation as that will do nothing on execution.
            command =  new CommandImpl();
        }

        return command;
    }

    /*
     * Spring controlled method which provides the ApplicationContext on startup.
     */
    @Override
    public void setApplicationContext(@NotNull ApplicationContext context) throws BeansException {
        SpringContext.context = context;
    }
}

不同命令的 Bean 配置:

@Configuration
public class BeanConfig {

    @Bean
    @Autowired
    Help help(SomeRepository repo) {
        return new Help(repo);
    }

    // other commands defined as @Bean...
}

由库事件触发并执行合适命令的侦听器:

@Service
public class CommandListener extends SomeLibraryListenerAdapter {

    
    @Override
    public void onSomeEvent(@NotNull final SomeEvent event) {
        // perform some checks if event contains a command and if caller is authorized to use it...
        // execute command
        final String commandName = identifyCommand(...); // commandName is a guaranteed match to some command when it arrives here
        final Command command = SpringContext.getCommandBean(commandName);
        command.execute(event);
    }
}

我读到最好不要使用 ApplicationContext,因为它违反了 Spring 的依赖注入原则,但我不太明白如何避免使用它。此外,在我看来,因为我确实可以控制所有这些命令,所以必须有一种方法可以让我以某种方式使用 Spring,这样我就不需要使用 ApplicationContext 来获取具有所需自动装配的合适 bean。 \

我试图将命令 classes 标记为 @Service 并在其字段上使用 @Autowired 因为我不能使用构造函数,因为这样工厂就需要知道哪个命令需要哪些存储库。另一个问题是我不能再使用 new 因此工厂的想法不起作用。
我也读到过 BeanFactory 但我并没有真正理解它背后的概念。我读到的有关它的文本定义了 XML 文件中实体的值,我认为这对我不起作用,因为我不使用值对象,而是使用命令实现。
我看到的最后一件事是可以使用的 @Configurable 注释,但这需要 AspectJ 和某种编织,这对于这样的基本功能来说似乎有点矫枉过正。但是,如果没有其他方法,我可能会继续使用 ApplicationContext 而不是使用该解决方案。

所以总而言之,我的问题是如何重构我的代码以在不使用 ApplicationContext 的情况下对我的命令使用自动装配,而是使用非常简单和干净的 Spring 注释;或者一些正确方向的提示,例如 pattern/functionality 在这里使用。 如果没有办法,那么我很高兴能简短地解释一下为什么它不起作用。

亲切的问候
Galaxaisio

考虑到 Help 和 CommandImpl 的其他子类被配置为 Spring beans ,下面的代码将达到目的。

@Service
public class CommandListener extends SomeLibraryListenerAdapter {

    @Autowired
    Map<String,Command> commandMap;    

    @Override
    public void onSomeEvent(@NotNull final SomeEvent event) {
        // perform some checks if event contains a command and if caller is authorized to use it...
        // execute command
        final String commandName = identifyCommand(...); // commandName is a guaranteed match to some command when it arrives here
        final Command command = commandMap.get(commandName);
        command.execute(event);
    }
}

此处 Map 的键是 bean 名称。

参考:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation(以“即使是类型化的 Map 实例也可以自动装配”开头的部分)