gpt4 book ai didi

java - 在一个最小的例子中,Spring IoC 实际上是如何工作的?

转载 作者:行者123 更新时间:2023-11-30 05:59:26 26 4
gpt4 key购买 nike

绝大多数教程都处理退化的情况,即只有一个实现用于要注入(inject)的接口(interface)。然而,我很茫然,到目前为止还没有找到任何关于如何构建应用程序的线索,其中几个专用部分提供了要注入(inject)公共(public)部分的公共(public)接口(interface)的几种不同实现(又名策略模式,又名控制反转)。

在我的现实生活中,我有一台 Tomcat 服务器,上面部署了一个应用程序,其中多个部分向外界提供不同的接口(interface)。在此应用程序中,在一个专用 @Configuration 中为通用接口(interface)定义一个@Bean始终会导致其他专用部分接收相同的 @Bean 即使它们(只是表面上?)独立的 @Configuration 定义了不同的 @Bean

举一个最小的例子,我尝试编写一个 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)));
}
}

此代码背后的意图如下:

  • ConfigAConfigB 都导入 BaseConfig,因为它们的进程使用相同的 CommonProcess
  • ConfigAConfigB 都定义了其特定的、专用的 Service 实现,以提供来自不同来源的通用值
    (例如,一项来自 XML,一项来自 JSON)。
  • 这里的 App 类是我将部署在 Tomcat 服务器上的 Servlet 的替代品。显然,应用程序必须知道(提供)服务器应提供的所有接口(interface),因此应用程序必须@ImportConfigAConfigB。< br/>据我了解,应用程序抽象层的“叶节点”的这种“收集点”需要存在,以便将它们全部暴露给世界
    (在此示例中,只需在 Tomcat 服务器中注册其 Spring Controller 来运行它们)。

现在,可以观察到以下行为:

  1. 按原样启动应用程序将打印 false falsetrue true,但绝不会打印预期的 false truetrue false;
  2. App 中删除 @Import 可以预见会导致应用无法运行任何内容。

而预期的行为是:

  1. ConfigA 调用 CommonProcess 时,它使用 ConfigAservice
  2. ConfigB 调用 CommonProcess 时,它使用 ConfigBservice

问题:产生预期行为的规范方法是什么?
(首选基于注释的解决方案)

<小时/>

作为引用,纯 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();
}
}

这里的输出确实符合预期false true

最佳答案

你对 Spring IoC 的思考过多并且令人困惑 @ConfigurationApplicationContext (实际的 IoC 容器)。

@Configuration在现有容器的范围内进行处理。还有docs一旦声明:

@Import represents JavaConfig's equivalent of XML configuration's <import/> element. One configuration class can import any number of other configuration classes, and their bean definitions will be processed as if locally defined.

也就是说,所有导入和发现的@Configurations都会加载到同一个容器中。

之后所有单例 bean 都被创建。然后将它们连接在一起。

在一个容器内,您可以拥有多个相同类型的 bean,但不能具有相同的名称。在 JavaConfig 中,bean 名称源自工厂方法名称或类名称。如果是Service只有一个名字,service ,因此只有一个 Service 类型的 bean 。如果仔细观察,您会看到一条启动消息,内容为“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]

唯一的service然后连接到需要的任何地方(在 commonProcessconfigAconfigB 中)。

根据您的具体情况,您可以通过传递 Service 来解决它至CommonProcess.test()就像在普通的 Java 版本中一样,并为每个 Service 指定一个唯一的名称实例(例如 serviceAserviceB ):

@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);
}
}

我还建议查看 bean scopes ,尤其是工厂范围。

最后,Spring Boot 支持 hierarchy of ApplicationContext's ,它本质上允许您在一个可执行文件中创建子应用程序。这边ConfigAConfigB每个人都可以有自己的Service名为 service 的实例。此功能很少使用。

@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);
}
}

关于java - 在一个最小的例子中,Spring IoC 实际上是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52523103/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com