如何在不使用 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 实例也可以自动装配”开头的部分)
我目前正在开发一个 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 实例也可以自动装配”开头的部分)