Java 在一个简单的例子中,SpringIOC实际上是如何工作的?

Java 在一个简单的例子中,SpringIOC实际上是如何工作的?,java,spring-annotations,spring-bean,Java,Spring Annotations,Spring Bean,绝大多数教程都处理退化的情况,即只有一个实现用于注入接口。然而,我不知所措,到目前为止还没有找到任何关于如何构建一个应用程序的线索,在这个应用程序中,几个专门的部分提供了几个不同的公共接口实现,以注入到公共部分(又名策略模式,又名控制反转) 在我的现实生活中,我有一个Tomcat服务器,服务器上部署了一个应用程序,其中几个部分为外部世界提供了不同的接口。在此应用程序中,在一个专门的@Configuration中为公共接口定义@Bean,总是会导致其他专门部分接收相同的@Bean,即使它们(仅表面

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

在我的现实生活中,我有一个Tomcat服务器,服务器上部署了一个应用程序,其中几个部分为外部世界提供了不同的接口。在此应用程序中,在一个专门的
@Configuration
中为公共
接口定义
@Bean
,总是会导致其他专门部分接收相同的
@Bean
,即使它们(仅表面上)独立的
@Configuration
定义了不同的
@Bean

举一个简单的例子,我尝试编写一个Spring引导应用程序,它表现出相同的行为,并且具有相同的通用架构:

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)));
  }
}
本规范的目的如下:

  • ConfigA
    ConfigB
    都导入BaseConfig,因为它们的进程使用相同的
    CommonProcess
  • ConfigA
    ConfigB
    都定义了它们对
    Service
    的特定、专门的实现,以提供来自不同来源的通用值
    (例如,一个来自XML,一个来自JSON)
  • App
    这里是我将在Tomcat服务器上部署的Servlet的替代。显然,应用程序必须知道(提供)服务器应该提供的所有接口,因此应用程序必须
    @Import
    同时
    ConfigA
    ConfigB

    我的理解是,应用程序抽象层的“叶节点”需要存在这样一个“收集点”,以便将它们全部公开给世界
    (在本例中,只需在Tomcat服务器中通过注册Spring控制器来运行它们)
现在,可以观察到以下行为:

  • 按原样启动应用程序将打印
    假假
    ,但不会打印预期的
    真假
  • App
    中删除
    @Import
    将导致应用程序无法运行任何东西
  • 鉴于预期行为为:

  • 当从
    ConfigA
    调用
    CommonProcess
    时,它使用
    ConfigA的
    服务
  • 当从
    ConfigB
    调用
    CommonProcess
    时,它使用
    ConfigB
  • 问题:产生预期行为的标准方法是什么?
    (首选基于注释的解决方案)


    请参考纯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();
      }
    }
    
    导入java.util.array;
    导入java.util.List;
    @功能接口
    接口服务{boolean test();}
    类公共进程{
    公共静态最终CommonProcess实例=新建CommonProcess();
    公共布尔测试(服务服务){return Service.test();}
    }
    类ProcessA实现可运行{
    //特定项目知道通用项目->无需注入
    私有静态最终CommonProcess CommonProcess=CommonProcess.INSTANCE;
    私有静态最终服务=()->false;
    公开募捐{
    //通用项目不知道特定项目->注入了具体内容
    System.out.println(this.commonProcess.test(this.service));
    }
    }
    类ProcessB实现可运行{
    //特定项目知道通用项目->无需注入
    私有静态最终CommonProcess CommonProcess=CommonProcess.INSTANCE;
    私有静态最终服务=()->true;
    公开募捐{
    //通用项目不知道特定项目->注入了具体内容
    System.out.println(this.commonProcess.test(this.service));
    }
    }
    类PlainApp{
    私有静态最终列表进程=Arrays.asList(新进程A(),新进程B());
    公共静态void main(字符串[]args){
    for(可运行进程:进程)
    process.run();
    }
    }
    

    这里的输出确实如预期的那样
    false-true

    您过度考虑了Spring IoC,并将
    @配置
    ApplicationContext
    (实际的IoC容器)混淆了

    @Configuration
    在已存在容器的范围内处理。而该委员会曾表示:

    @Import
    表示JavaConfig与XML配置的
    元素的等价物。一个配置类可以导入任意数量的其他配置类,它们的bean定义将被处理为本地定义

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

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

    在一个容器中,可以有多个相同类型但名称不同的bean。在JavaConfig中,bean名称派生自工厂方法名或类名。在
    服务
    的情况下,只有一个名称,
    服务
    ,因此只有一个类型为
    服务
    的bean。如果仔细观察,您将看到一条启动消息,内容大致如下“
    用不同的定义覆盖bean‘服务’的bean定义:将[factoryBeanName=ConfigA;factoryMethodName=service;在ConfigA中定义]替换为[factoryBeanName=ConfigB;factoryMethodName=service;在ConfigB中定义]

    然后将唯一的
    服务
    连接到所需的任何位置(在
    commonProcess
    configA
    configB

    在您的特定情况下,您可以通过将
    Service
    传递给
    CommonProcess.test()
    来解决这个问题,就像在普通Java版本中一样,并给出一个唯一的n
    @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);
      }
    }
    
    @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);
      }
    }