【问题标题】:How to inject a bean based on the property of another bean?如何根据另一个bean的属性注入一个bean?
【发布时间】:2017-01-03 12:54:24
【问题描述】:

我正在尝试使用 Spring 应用 Strategy 模式(我认为我弄错了),如下所示

我的主课看起来像

@Component
public class DirectoryUserImportWorkflow {
    private List<DirectoryUserDataSource> dataSources = Arrays.asList(new ActiveDirectoryDataSource(), new CsvDataSource());

    @Autowired
    private DirectoryUsersFetcher directoryUsersFetcher;

    public void run() {
        dataSources.forEach(dataSource -> directoryUsersFetcher.importUsers(dataSource));
    }
}

其中DirectoryUsersFetcher 是一个接口

public interface DirectoryUsersFetcher {
    Iterator<String> importUsers(DirectoryUserDataSource dataSource);
}

有2个实现

@Component
public class ActiveDirectoryUsersFetcher implements DirectoryUsersFetcher {
    public Iterator<String> importUsers(DirectoryUserDataSource dataSource) {
        System.out.println("Returning data from Active Directory");
        return Arrays.asList("ActiveDirectoryUser1", "ActiveDirectoryUser2", "ActiveDirectoryUser3").iterator();
    }
}

@Component
public class CsvUsersFetcher implements DirectoryUsersFetcher {
    public Iterator<String> importUsers(DirectoryUserDataSource dataSource) {
        System.out.println("Returning data from CSV");
        return Arrays.asList("CsvUser1", "CsvUser2", "CsvUser3").iterator();
    }
}

我希望根据DataSourceType 的含义在运行时使用其中一个

public enum DataSourceType {
    DirectoryServer,
    Csv
}

DataSource 本身就是一个界面,看起来像

public interface DirectoryUserDataSource {
    DataSourceType getType();
}

有2个实现

public class ActiveDirectoryDataSource implements DirectoryUserDataSource {
    public DataSourceType getType() {
        return DataSourceType.DirectoryServer;
    }
}

public class CsvDataSource implements DirectoryUserDataSource {
    public DataSourceType getType() {
        return DataSourceType.Csv;
    }
}

我的test 看起来像

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DirectoryUserImportWorkflow.class, ActiveDirectoryUsersFetcher.class, CsvUsersFetcher.class})
public class DirectoryUserImportWorkflowTest {

    @Autowired
    private DirectoryUserImportWorkflow workflow;

    @Test
    public void runStrategy() throws Exception {
        workflow.run();
    }
}

我看到的是

        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'directoryUserImportWorkflow': Unsatisfied dependency expressed through field 'directoryUsersFetcher': No qualifying bean of type [com.learner.datafetcher.DirectoryUsersFetcher] is defined: expected single matching bean but found 2: activeDirectoryUsersFetcher,csvUsersFetcher; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.learner.datafetcher.DirectoryUsersFetcher] is defined: expected single matching bean but found 2: activeDirectoryUsersFetcher,csvUsersFetcher

我该如何解决这个问题?

我需要什么?

基于DataSourceActiveDirectoryCsv,特定的提取器应调用ActiveDirectoryUsersFetcherCsvUsersFetcher

我在哪里缺少理解?

提前致谢

【问题讨论】:

  • 为什么fetcher一开始就不包含数据源?

标签: java spring java-8 strategy-pattern


【解决方案1】:

所以,您有两个 bean,具有相同的接口但有两个不同的名称,但您已经知道了。现在您有多种选择...

您可以按照@crm86 的建议,简单地将@Qualifier 添加到@Autowired,给出bean 的名称。当然,这会将您的实现紧密地耦合到您使用它的地方,并且您将无法使用另一个 - 除了更改代码。您也可以自动装配接口而不是实现类,但当然,出于同样的原因,这也是一个坏主意 - 为什么在删除容器的任何选择时首先尝试使用依赖注入?

另一种方法是首先使用@Configuration 和类似...的方法创建DirectoryUsersFetcher 的一个实例。

@Bean
public DirectoryUsersFetcher directoryUsersFetcher () {
// decide, create, return
}

当然,这会将您的应用程序限制为每个运行时只有一个提取器(如果您不声明它们的原型 - 但这将要求您将类型保留在某个地方,我想这很麻烦)。无论如何,您都需要在某处定义类型。

另一种方法是不直接创建 bean,而是使用工厂模式,例如...

@Component
public class DirectoryUsersFetcherFactory {

    public DirectoryUsersFetcher createDirectoryUsersFetcher (ActiveDirectoryDataSource dataSource) {
          DataSourceType type = dataSource.getType();
          if(type == DataSourceType.DirectoryServer) 
              return new ActiveDirectoryUsersFetcher ();
          if(type == DataSourceType.Csv) 
              return new CsvUsersFetcher ();
          throw new IllegalArgumentException("Unknown type" + type);
    } 

}

这样,您可以直接连接工厂而不是 bean。工厂还可以缓存对象等。就我个人而言,我会建议该解决方案。

@Autowired
private DirectoryUsersFetcherFactory factory;

public void run() {
    dataSources.forEach(dataSource -> directoryUsersFetcher.importUsers(factory.createDirectoryUsersFetcher(dataSource)));
}

当然,您的工厂也可以简单地自动装配工厂中的 fetcher 并将 fetcher 作为 bean 返回......

@Component
public class DirectoryUsersFetcherFactory {

    @Autowired
    private ActiveDirectoryUsersFetcher activeDirectoryUsersFetcher ;

    @Autowired
    private CsvUsersFetcher csvUsersFetcher ;

    public DirectoryUsersFetcher createDirectoryUsersFetcher (ActiveDirectoryDataSource dataSource) {
          DataSourceType type = dataSource.getType();
          if(type == DataSourceType.DirectoryServer) 
              return activeDirectoryUsersFetcher ;
          if(type == DataSourceType.Csv) 
              return csvUsersFetcher;
          throw new IllegalArgumentException("Unknown type" + type);
    } 

}

这么多选择 ;-) 您甚至可以将 fetcher 与数据源关联,自动装配 ApplicationContext 并查看所有现有的 fetcher 以找到合适的,这样,您的工厂甚至不需要知道实际的实现但可以在运行时发现它们......但我想你明白了基本的想法......

【讨论】:

  • 还是一头雾水。我需要使用两个bean,只需要根据DataSourceType 是什么来选择
  • 使用工厂。连接它而不是 fetcher 本身,然后将 DataSourceType 提供给工厂方法并让该方法创建/返回您适当的 fetcher。
  • 嗯,你能指导我看一个我可以查找并理解的示例或参考吗?
猜你喜欢
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 2017-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-28
  • 1970-01-01
相关资源
最近更新 更多