首页 > 解决方案 > 如何在 @Bean-annotated 方法或类似方法中创建多个 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 MVCHttpInvokerServiceExporter在将导出器注册到自己的配置时使用 Spring bean 名称作为端点 URI。因此,我必须将每个导出器注册为具有自己的 bean 名称的“一等公民”bean 到应用程序上下文中。

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

我尝试了使用@PostContruct-annotated 方法的解决方案,使用BeanPostProcessor, 使用@Autowired方法(如上所示),没有按要求工作。

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

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

提前感谢您提供的任何帮助...

标签: javaspringspring-mvc

解决方案


我不是 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 自动拾取所有实现我的接口的组件,而工厂没有大量的导入。而且我可以通过模拟实现列表来轻松测试工厂。


推荐阅读