如何在 @Bean 注释方法或类似方法中创建多个 Spring bean?

问题描述

在使用 HTTP 远程处理的 Spring 应用程序中,我有一个如下配置的服务外观模块(我使代码通用以提高清晰度):

@Configuration
public class MyFacadeConfig {

  private httpinvokerServiceExporter facade(Class<?> cls) {
    httpinvokerServiceExporter bean = new httpinvokerServiceExporter();
    // The service referred to by this exporter is already instantiated as another Spring bean with all its dependencies.
    bean.setService(appContext.getBean(cls));
    bean.setServiceInterface(cls);
    return bean;
  }

  @Bean("/first.service")
  public httpinvokerServiceExporter firstServiceFacade() {
    return facade(FirstService.class);
  }

  @Bean("/second.service")
  public httpinvokerServiceExporter secondServiceFacade() {
    return facade(SecondService.class);
  }

  // ... and so on for the 37 other services

}

其中 FirstServiceSecondService 是与现有实现的接口,此处不需要其详细信息。

我有一个模块,它定义了 39 个代理(httpinvokerProxyfactorybean 的实例),这些代理对应于通过我的外观公开的每个服务。

到目前为止,一切正常。

但我想让代码更加通用、优雅和健壮,同时降低出错的风险(例如,将来服务与其代理之间的错误映射)。我想这样做的方式如下:

首先,我将外观/代理元数据移动到枚举中:

public enum ConfigBeansFacade {

  FirsT("/first",FirstService.class),SECOND("/second",SecondService.class)
  // ... and so on for the 37 other services
  ;

  private String beanName;
  private Class<?> serviceInterface;

  // Constructor and getters

  public String getCompleteBeanName() {
    return beanName + ".service";
  }

}

那么外观的配置将被简化为类似于以下的样式:

@Configuration
public class MyFacadeConfig {

  @Autowired
  private Configurablebeanfactory beanfactory;

  @Autowired
  public void configExporters() {
    for (ConfigBeansFacade bean : ConfigBeansFacade.values()) {
      httpinvokerServiceExporter exp = new httpinvokerServiceExporter();
      exp.setService(beanfactory.getBean(bean.getServiceInterface()));
      exp.setServiceInterface(bean.getServiceInterface());
      beanfactory.registerSingleton(bean.getCompleteBeanName(),exp);
    }
  }

}

我尝试了我在在线论坛上找到的每一个食谱,包括 StackOverflow,但有两个其他地方没有遇到的限制:

  1. 在定义导出器时,底层服务是其他 Spring bean,它们通过标准 Spring 机制使用自己的配置和依赖项进行实例化、初始化和注册。除了导出器本身之外,没有直接的类实例化。
  2. 我考虑过按照某些人的建议将出口商分组到一个集合中。唯一的问题是 Spring MVC 在将导出器注册到自己的配置中时使用 httpinvokerServiceExporter Spring bean 名称作为端点 URI。因此,我必须将每个导出器注册为“一等公民”bean,并将其自己的 bean 名称注册到应用程序上下文中。

鉴于这些限制,当我尝试检索要封装到导出器中的底层服务时,我在 (1) 中遇到的问题:它们不一定准备好,这导致 UnsatisfiedDependencyException s。

我尝试使用带 @PostContruct 注释的方法、使用 BeanPostProcessor 和使用 @Autowired 方法(如上所示)的解决方案,但没有按要求工作。

有谁知道在上述约束下在单个方法中初始化和注册多个 bean 的方法或技术?这样的方法不需要用 @Bean@Autowired 或任何其他特定的注释进行注释,这只是我尝试过的一个示例。

幸运的是,在客户端模块中,httpinvokerProxyfactorybean 实例只需要接口和 bean 名称,因此上面的约束 (1) 不适用。

预先感谢您提供的任何帮助...

解决方法

我不是 100% 理解您想要做什么,但我想知道您是否可以尝试自动装配实现接口的 List bean?

例如

public interface MyService {
  String getKey();
  void doStuff();
}

然后根据需要实现尽可能多的这些

例如

@Component
public class FirstService implements MyService {
  public String getKey() {
    return "/first";
  }
  public void doStuff() {
    ...
  }
}

然后有一个带有自动装配列表的工厂 bean

@Component
public class MyServiceFactory {

  private final List<MyService> services;

  @Autowired
  public MyServiceFactory(List<MyService> services) {
      this.services = services;
  }
}

要添加更多 MyService 的实现,只需将它们添加为 @Component,Spring 就会神奇地将它们添加到列表中。

有时我发现通过 Map 访问我的实现很有用

@Component
public class MyServiceFactory {

  private final Map<String,MyService> services;

  @Autowired
  public MyServiceFactory(List<MyService> services) {
      this.services = services
        .stream()
        .collect(toMap(MyService::getKey,Function.identity()));
  }

  public MyService getServiceByKey(String key) {
    return services.get(key);
  }
}

我发现这使每个实现都保持良好且自包含(并且易于测试)。 Spring 会自动选取实现我的接口的所有组件,而无需工厂大量导入。我可以通过模拟实现列表轻松测试工厂。