Reputation: 634
First, let me introduce a few simple classes and interface to be able to describe my problem.
interface Basic { void foo(); }
interface Extended extends Basic { void bar(); }
class BasicService {
@Inject
List<Basic> basics;
void execute() {
basics.forEach(Basic::foo);
}
}
class ExtendedService {
@Inject
List<Extended> extendeds;
void execute() {
extendeds.forEach(Extended::bar);
}
}
@Configuration
class MyConfiguration {
// Assume, that Basic1 and Basic2 are implementations of Basic and
// Extended1 is an implementation of Extended
@Bean
public Basic basic1() {
return new Basic1();
}
@Bean
public Basic basic2() {
return new Basic2();
}
@Bean
public List<Extended> extendeds() {
return Arrays.asList(new Extended1("0"), new Extended1("1"), new Extended1("2"));
}
@Bean
public BasicService basicService() {
return new BasicService();
}
@Bean
public ExtendedService extendedService() {
return new ExtendedService();
}
}
I have two services that act on different level of abstraction. My problem is that I'm failing to find a way how I can inject all beans that implement the Basic
interface in my BasicService
. With the current implementation it only injects all Extended
implementations, because I have a factory bean method that has a return type of List
in its method signature.
I can not change the bean configuration in a way that all Extended
beans have their own factory methods, because in my real code the number of Extended
implementations is dynamically computed on runtime...
Is there a way I can configure Spring, so that it merges all beans with Basic
and List<Basic>
together in 1 big list that I can use in my BasicService
?
Upvotes: 0
Views: 81
Reputation: 3753
I'm not sure whether there is a way to solve this implicitly, since Spring tries to find the best match, which the List bean seems to be (have you tried generics, e.g., List<? extends Basic>
?).
But you could do it programmatically, by getting the bean definitions from the ApplicationContext:
public BasicService(@Inject ApplicationContext ctx) {
Map<String, ? extends Basic> basicMap = ctx.getBeansOfType(Basic.class);
Map<String, Collection<? extends Basic>> basicCollectionMap = ctx.getBeansofType(ResolvableType.forClassWithGenerics(Collection.class, Basic.class));
// Now merge
Collection<? extends Basic> basics = basicMap.values();
basicCollectionMap.values().stream().map(l -> basics.addAll(l));
}
I just typed this here, so I hope there aren't any obvious errors.
Upvotes: 1
Reputation: 634
I solved the problem by adding generic bean definitions for the Extended
implementations. This can be done by adding a BeanDefinitionRegistryPostProcessor
implementation to the @Configuration
class as follows:
@Configuration
class MyConfiguration {
@Bean
public static ExtendedBeanFactory extendedBeanFactory() {
return new ExtendedBeanFactory();
}
}
class ExtendedBeanFactory implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
for (int i = 0; i < 3; i++) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(Extended1.class);
ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addGenericArgumentValue(Integer.toString(i));
beanDefinition.setConstructorArgumentValues(args);
registry.registerBeanDefinition("extended_" + i, beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// noop
}
}
This has the advantage, that consumer classes can simply inject a list of Basic
or a list of Extended
as the code from the question does.
Upvotes: 0