4

I am trying to initialize a Spring component with a set of all beans of a certain type (well really, anything I can iterate).

The Spring core documentation talks about collection merging, but only in the context of annotation-based configuration.

Suppose I have the following configuration

@Configuration
public class MyConfig {
    @Bean
    public SomeInterface single() {
        return new SomeInterface() {};
    }

    @Bean
    public Set<SomeInterface> multi() {
        return Collections.singleton(
            new SomeInterface() {}
        );
    }
}

Where the interface is defined as

public interface SomeInterface {}

I would like this component to get an aggregate of both beans - some collection containing both anonymous classes.

@Component
public class MyComponent {
    public MyComponent(Set<SomeInterface> allInterfaces) {
        System.out.println(allInterfaces.size()); // expecting 2, prints 1
    }
}

I see why Spring has come to the result it has; it sees this method is expecting a Set<SomeInterface> and MyConfig::multi is a bean of type Set<SomeInterface>, so it autowires with that.

If I change the signature to Collection<SomeInterface>, it autowires with MyConfig::single. Again, I see why: there's nothing matching exactly, but there's beans of type SomeInterface (in this case, just one) so it constructs a temporary collection of them and autowires with that. Fine, but not what I'm after.

I would like the solution to be extensible so that if another bean is added, the dependent component does not need to change. I've tried using two parameters, each with a @Qualifier, and that works but is not extensible.

How can I get this to work?

Michael
  • 41,989
  • 11
  • 82
  • 128

1 Answers1

4

As you already mentioned, MyConfig::multi is a bean of type Set<SomeInterface>, so autowiring Collection<Set<SomeInterface>> would give you all of those sets. The following should work

public MyComponent(Collection<SomeInterface> beans,
                   Collection<Set<SomeInterface>> beanSets) {
    // merge both params here
}

If you need all implementations in multiple places it might make sense to define another bean containing the merged collection and autowire that bean:

static class SomeInterfaceCollection {
    final Set<SomeInterface> implementations;

    SomeInterfaceCollection(Set<SomeInterface> implementations) {
        this.implementations = implementations;
    }
}

@Bean
public SomeInterfaceCollection collect(Collection<SomeInterface> beans,
        Collection<Collection<SomeInterface>> beanCollections) {
    final HashSet<SomeInterface> merged = ...
    return new SomeInterfaceCollection(merged);
}
Jörn Horstmann
  • 33,639
  • 11
  • 75
  • 118