【问题标题】:How to autowire in non Spring objects without using the ApplicationContext to get Beans by name如何在非 Spring 对象中自动装配而不使用 ApplicationContext 按名称获取 Bean
【发布时间】:2020-11-22 08:03:13
【问题描述】:

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

命令界面:

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)
         */
    }
}

Spring ApplicationContext 包装器:

@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自动装配。 \

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

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

亲切的问候
银河系

【问题讨论】:

    标签: java spring spring-boot autowired


    【解决方案1】:

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

    @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(以“Even typed Map instances can be autowired”开头的部分)

    【讨论】:

      猜你喜欢
      • 2012-08-03
      • 2015-08-04
      • 2016-04-07
      • 1970-01-01
      • 2011-05-25
      • 2012-08-11
      • 2011-05-05
      • 2021-12-29
      • 2012-03-29
      相关资源
      最近更新 更多