【问题标题】:How does Spring IoC actually work in a minimal example?Spring IoC 如何在一个最小的示例中实际工作?
【发布时间】:2018-09-26 17:20:24
【问题描述】:

绝大多数教程都处理退化的情况,即只有一个实现用于注入接口。但是,我不知所措,到目前为止,我还没有找到任何关于如何构建一个应用程序的线索,该应用程序的几个专门部分提供了几种不同的通用接口实现,以注入到通用部分(又名策略模式,又名控制反转)。

在我的现实生活中,我有一个 Tomcat 服务器,上面部署了一个应用程序,其中几个部分为外部世界提供了不同的接口。在这个应用程序中,在一个专用的@Configuration 中为一个通用的interface 定义一个@Bean 总是会导致其他专用部分接收相同的@Bean,即使它们(只是看似?)独立的@Configurations 定义了一个不同的@ 987654327@.

举个简单的例子,我尝试编写一个 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 的替身。显然,App 必须知道(提供)Server 应该提供的所有接口,所以 App 必须 @Import ConfigAConfigB
    我的理解是,需要存在这样一个应用程序抽象层的“叶节点”的“收集点”,以便将它们全部公开
    (在这个例子中,通过简单地运行它们,通过注册它们的 Spring 控制器在 Tomcat 服务器中)。

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

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

而预期的行为是:

  1. 其中CommonProcess 是从ConfigA 调用的,它使用serviceConfigA
  2. 其中CommonProcess 是从ConfigB 调用的,它使用serviceConfigB

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


参考纯 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

【问题讨论】:

标签: java spring-annotations spring-bean


【解决方案1】:

您过度考虑 Spring IoC 并将 @ConfigurationApplicationContext(实际的 IoC 容器)混淆了。

@Configuration 在现有容器的范围内处理。而docs曾经说过:

@Import 表示 JavaConfig 等效于 XML 配置的 &lt;import/&gt; 元素。一个配置类可以导入任意数量的其他配置类,它们的 bean 定义将像本地定义一样被处理。

也就是说,所有导入和发现的@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)。

在您的特定情况下,您可以像在纯 Java 版本中一样将Service 传递给CommonProcess.test(),并为每个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);
  }
}

【讨论】:

    猜你喜欢
    • 2017-07-22
    • 2018-09-10
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多