[英]How does Spring IoC actually work in a minimal example?
A vast majority of tutorials deal with the degenerate case that there is only exactly one Implementation for the interface to be injected.绝大多数教程都处理退化情况,即要注入的接口只有一个实现。 However, I am at a loss and so far have not found any lead on how to build an application whereof several specialised parts provide several different implementations of common interfaces to be injected into common parts (aka Strategy Pattern, aka Inversion of Control).
但是,我不知所措,到目前为止还没有找到关于如何构建应用程序的任何线索,其中几个专门部分提供了要注入到公共部分(又名策略模式,又名控制反转)的公共接口的几种不同实现。
In my real-life situation I have a Tomcat server with one application deployed thereon, wherein several parts provide different interfaces to the outside world.在我的现实生活中,我有一个 Tomcat 服务器,上面部署了一个应用程序,其中几个部分为外部世界提供了不同的接口。 In this application, defining a
@Bean
for a common interface
in one specialised @Configuration
always results in other specialised parts receiving the same @Bean
even though their (only seemingly?) independent @Configuration
s define a different @Bean
.在此应用程序中,在一个专门的
@Configuration
为公共interface
定义@Bean
总是会导致其他专门部分接收相同的@Bean
即使它们(只是看似?)独立的@Configuration
定义了不同的@Bean
。
For a minimal example, I have attempted to write a Spring-boot application which exhibits the same behaviour and has the same general architecture:作为一个最小的例子,我试图编写一个 Spring-boot 应用程序,它表现出相同的行为并具有相同的通用架构:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@FunctionalInterface
interface Service { boolean test(); }
class CommonProcess {
@Autowired
Service service;
public boolean test() { return this.service.test(); }
}
@Configuration
class BaseConfig {
@Bean
CommonProcess commonProcess() { return new CommonProcess(); }
}
@Configuration
class ConfigA {
@Bean
CommandLineRunner processA() {
return new CommandLineRunner() {
@Autowired
private CommonProcess process;
@Override
public void run(String... args) throws Exception {
System.out.println(this.process.test());
}
};
}
@Bean
Service service() { return () -> false; }
}
@Configuration
class ConfigB {
@Bean
CommandLineRunner processB() {
return new CommandLineRunner() {
@Autowired
private CommonProcess process;
@Override
public void run(String... args) throws Exception {
System.out.println(this.process.test());
}
};
}
@Bean
Service service() { return () -> true; }
}
@SpringBootConfiguration
@Import(value = { BaseConfig.class, ConfigA.class, ConfigB.class })
class App {
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(App.class, args)));
}
}
The intent behind this code is as follows:这段代码背后的意图如下:
ConfigA
and ConfigB
import BaseConfig, because their processes use the same CommonProcess
. ConfigA
和ConfigB
导入 BaseConfig,因为它们的进程使用相同的CommonProcess
。ConfigA
and ConfigB
define their specific, specialised implementation of Service
to provide a common value from different sources ConfigA
和ConfigB
定义了它们特定的、专门的Service
实现,以提供来自不同来源的通用值App
here is a stand-in for the Servlet I would deploy on a Tomcat server.App
是我将在 Tomcat 服务器上部署的 Servlet 的替代品。 Obviously, App has to know (provide) all the interfaces the Server should provide, so App has to @Import
both ConfigA
and ConfigB
.@Import
既ConfigA
和ConfigB
。 Now, the following behaviours can be observed:现在,可以观察到以下行为:
false false
or true true
but never the expected false true
or true false
;false false
或true true
但不会打印预期的false true
或true false
;@Import
from App
will predictably result in the App not running anything.App
删除@Import
将可预见地导致 App 不运行任何东西。 Whereas the expected behaviour would be:而预期的行为是:
CommonProcess
is called from ConfigA
it uses the service
of ConfigA
CommonProcess
从所谓ConfigA
它使用的service
的ConfigA
CommonProcess
is called from ConfigB
it uses the service
of ConfigB
CommonProcess
从所谓ConfigB
它使用的service
的ConfigB
Question: What is the canonical way to produce the expected behaviour?问题:产生预期行为的规范方式是什么?
(annotation-based solution preferred) (首选基于注释的解决方案)
For reference a working example in plain Java:参考一个纯 Java 的工作示例:
import java.util.Arrays;
import java.util.List;
@FunctionalInterface
interface Service { boolean test(); }
class CommonProcess {
public static final CommonProcess INSTANCE = new CommonProcess();
public boolean test(Service service) { return service.test(); }
}
class ProcessA implements Runnable {
// specific project knows generic project -> no need to inject
private static final CommonProcess commonProcess = CommonProcess.INSTANCE;
private static final Service service = () -> false;
public void run() {
// generic project does not know specific project -> specifics are injected
System.out.println(this.commonProcess.test(this.service));
}
}
class ProcessB implements Runnable {
// specific project knows generic project -> no need to inject
private static final CommonProcess commonProcess = CommonProcess.INSTANCE;
private static final Service service = () -> true;
public void run() {
// generic project does not know specific project -> specifics are injected
System.out.println(this.commonProcess.test(this.service));
}
}
class PlainApp {
private static final List<Runnable> processes = Arrays.asList(new ProcessA(), new ProcessB());
public static void main(String[] args) {
for (Runnable process : processes)
process.run();
}
}
Here the output is indeed as expected false true
.这里的输出确实是预期的
false true
。
You're overthinking Spring IoC and confusing @Configuration
with ApplicationContext
(the actual IoC container).您过度考虑 Spring IoC 并将
@Configuration
与ApplicationContext
(实际的 IoC 容器)混淆。
@Configuration
is processed in scope of an already existing container. @Configuration
在现有容器的范围内处理。 And the docs once stated: 文档曾经说过:
@Import
represents JavaConfig's equivalent of XML configuration's<import/>
element.@Import
表示 JavaConfig 相当于 XML 配置的<import/>
元素。 One configuration class can import any number of other configuration classes, and their bean definitions will be processed as if locally defined.一个配置类可以导入任意数量的其他配置类,它们的 bean 定义将像本地定义一样进行处理。
That is, all imported and discovered @Configurations get loaded into the same container.也就是说,所有导入和发现的 @Configurations 都被加载到同一个容器中。
After that all singleton beans are created.之后创建所有单例bean。 Then they are wired together.
然后将它们连接在一起。
Within a container you can have multiple beans of the same type but not with the same name .在一个容器中,您可以拥有多个相同类型但名称不同的bean。 In a JavaConfig, a bean name is derived from either the factory method name or the class name.
在 JavaConfig 中,bean 名称源自工厂方法名称或类名称。 In case of
Service
there's just one name, service
, and hence just one bean of type Service
.在
Service
的情况下,只有一个名称service
,因此只有一个Service
类型的 bean。 If you look closely you'll see a startup message along the lines " Overriding bean definition for bean 'service' with a different definition: replacing [factoryBeanName=ConfigA; factoryMethodName=service; defined in ConfigA] with [factoryBeanName=ConfigB; factoryMethodName=service; defined in ConfigB]
"如果您仔细观察,您会看到一条启动消息
Overriding bean definition for bean 'service' with a different definition: replacing [factoryBeanName=ConfigA; factoryMethodName=service; defined in ConfigA] with [factoryBeanName=ConfigB; factoryMethodName=service; defined in ConfigB]
"
The one and only service
is then wired in everywhere it's needed (in commonProcess
, configA
and configB
).然后将唯一的
service
连接到需要它的任何地方(在commonProcess
、 configA
和configB
)。
In your specific case you can solve it by passing Service
to CommonProcess.test()
like in your plain Java version, and giving a unique name to each Service
instance (eg serviceA
and serviceB
):在您的特定情况下,您可以通过将
Service
传递给CommonProcess.test()
来解决它,就像在您的纯 Java 版本中一样,并为每个Service
实例(例如serviceA
和serviceB
)提供一个唯一的名称:
@FunctionalInterface
interface Service {
boolean test();
}
class CommonProcess {
public boolean test(Service service) {
return service.test();
}
}
@Configuration
class BaseConfig {
@Bean
CommonProcess commonProcess() {
return new CommonProcess();
}
}
@Configuration
class ConfigA {
@Bean
CommandLineRunner processA(@Named("serviceA") Service service) {
return new CommandLineRunner() {
@Autowired
private CommonProcess process;
@Override
public void run(String... args) throws Exception {
System.out.println(this.process.test(service));
}
};
}
@Bean
Service serviceA() {
return () -> false;
}
}
@Configuration
class ConfigB {
@Bean
CommandLineRunner processB(@Named("serviceB") Service service) {
return new CommandLineRunner() {
@Autowired
private CommonProcess process;
@Override
public void run(String... args) throws Exception {
System.out.println(this.process.test(service));
}
@Bean
Service serviceB() {
return () -> true;
}
};
}
@Autowired
ApplicationContext applicationContext;
@PostConstruct
public void printBeans() {
System.out.println(Arrays.asList(applicationContext.getBeanDefinitionNames()));
}
@Bean
Service serviceB() {
return () -> true;
}
}
@SpringBootConfiguration
@Import(value = { BaseConfig.class, ConfigA.class, ConfigB.class })
class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
I would also suggest looking into bean scopes , especially the factory scope.我还建议查看bean 范围,尤其是工厂范围。
And finally, Spring Boot supports a hierarchy of ApplicationContext's , which essentially allows you to create sub-applications within one executable.最后,Spring Boot 支持ApplicationContext 的层次结构,它本质上允许您在一个可执行文件中创建子应用程序。 This way
ConfigA
and ConfigB
can each have it's own Service
instance named service
.这样
ConfigA
和ConfigB
可以各自拥有自己的名为service
的Service
实例。 This functionality is rarely used.此功能很少使用。
@SpringBootConfiguration
@Import(value = { BaseConfig.class })
class App {
public static void main(String[] args) {
SpringApplicationBuilder app = new SpringApplicationBuilder(App.class);
app.child(ConfigA.class).run(args);
app.child(ConfigB.class).run(args);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.