Problem
You define a group of beans of certain type in Spring and at the same time you want to define a custom list of these beans. However when using
@Autowired to inject this list you end up with the default implementation of the list provided by
@Autowired .
Imagine those are the beans that we define in Spring Configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Bean public Environment createPentahoDev() { return EnvironmentImpl.getBuilder() .setName("DEV") .addNode("https://devaisbi01.cern.ch/pentaho-tools") .addNode("https://devaisbi02.cern.ch/pentaho-tools") .addProxy("https://devaisbi.cern.ch").build(); } @Bean public Environment createPentahoPreprod() { return EnvironmentImpl.getBuilder() .setName("PREPROD") .addNode("https://preprodaisbi01.cern.ch/pentaho-tools") .addNode("https://preprodaisbi02.cern.ch/pentaho-tools") .addProxy("https://preprodaisbi.cern.ch").build(); } @Bean public Environment createPentahoProd() { return EnvironmentImpl.getBuilder() .setName("PROD") .addNode("https://aisbi01.cern.ch/pentaho-tools") .addNode("https://aisbi02.cern.ch/pentaho-tools") .addProxy("https://aisbi.cern.ch").build(); } |
Now let’s say you want to create a bean that is a list of Environments, a list of specific implementation. You might be tempted to do something like this:
1 2 3 4 5 6 7 |
@Bean @Autowired public List<Environment> createListOfEnvironments(List<Environment> environments) { return new MagicList<>(environments); } |
Surprise, surprise, everything works! Until in some other place in the code you decide to autowire this list:
1 2 3 4 |
@Autowired private List<Environment> environments; |
Soon enough you will find out that the list you received is not the MagicList you have been expecting.
Not working solution
My first idea was to add @Order/ @Priority when defining the list.
1 2 3 4 5 6 7 8 |
@Bean @Autowired @Order(Ordered.HIGHEST_PRECEDENCE) public List createListOfEnvironments(List<Environment> environments) { return new MagicList<>(environments); } |
Well that did not work very well.
Some workarounds
An obvious workaround would be add a @Qualifier annotation so that when injecting the list be able to specify what implementation should be provided.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Bean("magicList") @Autowired public List<Environment> createListOfEnvironments(List<Environment> environments) { return new MagicList<>(environments); } /* [...] */ @Autowired @Qualifier("magicList") private List<Environment> environments; |
At the same time instead of autowiring the List interface, we could autowire particular implementation.
1 2 3 4 5 6 7 8 9 10 11 12 |
@Bean @Autowired public List<Environment> createListOfEnvironments(List<Environment> environments) { return new MagicList<>(environments); } /* [...] */ @Autowired private MagicList<Environment> environments; |
Both solutions would work but they somehow do not feel right – they explicitly expose specific implementation of the list. Also they seem error-prone as I can easily imagine a situation when someone forgets to put the @Qualifier annotation and ends up with wrong implementation without even realizing this fact.
Solution
To fix this problem we will do two things. First of all we will remove Environment class beans as candidates for autowiring (thanks to Marcin Grzejszczak for the tip how do to it). Thanks to that there will not be any default Listobject.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Configuration public class EnvironmentBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { for (String beanName : beanFactory.getBeanNamesForType(Environment.class)) { beanFactory.getBeanDefinition(beanName).setAutowireCandidate(false); } } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } } |
Now, let’s create our own list of Environments.
1 2 3 4 5 6 7 8 9 10 11 |
@Bean @Autowired public List<Environment> createPentahoEnvironmentsList(ApplicationContext applicationContext) { MagicList environments = new MagicList<>(); applicationContext.getBeansOfType(Environment.class).forEach( (name, environment) -> environments.add(environment) ); return environments; } |
Et voilà! Now a simple @Autowired annotation will inject our custom implementation of List interface.
1 2 3 4 |
@Autowired private List<Environment> environments; |