在一个最小的示例中,Spring IoC实际如何工作?

时间:2018-09-26 17:20:25

标签: java spring-annotations spring-bean

绝大多数教程处理的是退化的情况,即仅存在一个要实现的接口实现。但是,我茫然不知所措,到目前为止,如何构建一个应用程序并没有找到任何线索,该应用程序的几个专用部分提供了将注入到公共部分中的公共接口的几种不同实现(又称“策略模式”,又称“控制反转”)。 / p>

在我的现实生活中,我有一台Tomcat服务器,上面部署了一个应用程序,其中几个部分提供了与外界的不同接口。在此应用程序中,在一个专门的@Bean中为公用interface定义一个@Configuration总是导致其他专门的部分接收相同的@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的替代品。显然,App必须知道(提供)服务器应提供的所有接口,因此App必须同时@ImportConfigA ConfigB
    据我了解,应用程序抽象层的“叶节点”必须存在这样的“收集点”,以便将它们全部暴露给世界
    (在此示例中,只需注册它们的Spring Controller,即可在Tomcat服务器中运行它们)。

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

  1. 直接启动应用程序将打印false falsetrue true,但不会打印预期的false truetrue false
  2. @Import中删除App会导致该应用程序无法运行。

预期的行为将是:

  1. CommonProcess调用ConfigA的地方使用service的{​​{1}}
  2. ConfigA调用CommonProcess的地方使用ConfigB的{​​{1}}

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


作为参考,使用纯Java的工作示例:

service

这里的输出确实符合预期ConfigB

1 个答案:

答案 0 :(得分:1)

您对Spring IoC的想法过高,并将@ConfigurationApplicationContext(实际的IoC容器)相混淆。

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

  

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

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

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

在一个容器中,您可以有多个相同类型的豆,但没有相同的 name 。在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实例(例如{{1} }和serviceA):

serviceB

我还建议您研究bean scopes,尤其是工厂范围。

最后,Spring Boot支持hierarchy of ApplicationContext's,它实际上使您可以在一个可执行文件中创建子应用程序。这样@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); } } ConfigA可以各自拥有自己的名为ConfigB的{​​{1}}实例。很少使用此功能。

Service