0

I am trying to apply Strategy pattern (which I think I get it wrong) using Spring as following

My main class looks like

@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));
    }
}

where DirectoryUsersFetcher is an interface as

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

with 2 implementations as

@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();
    }
}

and

@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();
    }
}

I want one of them to be used at Runtime based on what the DataSourceType is

public enum DataSourceType {
    DirectoryServer,
    Csv
}

The DataSource itself is an interface which looks like

public interface DirectoryUserDataSource {
    DataSourceType getType();
}

with 2 implementations as

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

and

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

My test looks like

@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();
    }
}

What I see is

        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

How can I solve this problem?

What I need?

Based on what DataSource is ActiveDirectory or Csv, the specific fetcher should invoke ActiveDirectoryUsersFetcher or CsvUsersFetcher

Where am I missing the understanding?

Thanks in advance

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
daydreamer
  • 87,243
  • 191
  • 450
  • 722

1 Answers1

4

So, you've got two beans, with the same interface but two different names, but you know that already. Now you have multiple choices...

You could, as @crm86 suggested, simple add the @Qualifier to the @Autowired, giving the name of the bean. This, of course, will couple your implementation tightly to the place where you use it and you will have no way to use another - except changing the code. You could also autowire not the interface but the implementing class, but of course, this is also a bad idea for the same reason - why trying to use dependency injection in the first place when then removing any choice for the container?

Another way would be to only create one instance of your DirectoryUsersFetcher in the first place, using a @Configuration and a method like...

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

Of course, this will limit your application to only one fetcher per Runtime (if you don't declare them prototype - but this will require you to keep the type around somewhere, which i guess is troublesome). You would need to have the type defined somewhere anyway.

Another way would be to not create your beans directly, but use a factory pattern, for example...

@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);
    } 

}

This way, you could wire the factory instead of the beans directly. The factory could also cache the object, etc. Personally, i would suggest that solution.

@Autowired
private DirectoryUsersFetcherFactory factory;

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

Of course, your factory could also simply autowire the fetchers in the factory and return the fetchers as beans...

@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);
    } 

}

So many choices ;-) You could even also associate the fetchers with a datasource, autowire your ApplicationContext and look through all the existing fetchers to find a suitable one, this way, your factory would not even need to know the actual implementation but could discover them at runtime... But I guess you get the basic idea...

Florian Schaetz
  • 10,454
  • 5
  • 32
  • 58
  • still confused. I need to use both the beans, just need to select based on what the `DataSourceType` is – daydreamer Aug 26 '16 at 19:36
  • 1
    Use the factory. Wire it instead of the fetcher itself, then give the DataSourceType into the factory method and let the method create/return your apropriate fetcher. – Florian Schaetz Aug 26 '16 at 19:37
  • hmm, can you direct me to an example or reference I could look up and understand? – daydreamer Aug 26 '16 at 19:39