Reputation: 3273
I have a configuration class as below:
@Configuration
public class ListConfiguration {
@Bean
public List<Integer> list() {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
return ints;
}
@Bean
public int number() {
return 4;
}
}
I also have a test class as below
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ListConfiguration.class)
public class ListTest {
@Autowired
List<Integer> ints;
@Test
public void print() {
System.out.println(ints.size());
System.out.println(ints);
}
}
But the output of the print
method is 1
and [4]
, why not 3
and [1,2,3]
? Thank you very much for any help!
Upvotes: 5
Views: 5471
Reputation: 6540
You've got a bean of type Integer
and a bean of type List<Integer>
in your application context.
Now obviously the bean you want to autowire is of type List<Integer>
, which does qualify as a candidate for autowiring. To discover how Spring actually autowires fields I had to dive deep into the AutowiredAnnotationBeanPostProcessor
class.
TL;DR of my investigation is that Spring will prefer to autowire objects in the following order:
@Value
That means that if you're autowiring a List<Integer>
Spring will attempt to autowire multiple Integer
beans into the list before it will attempt to autowire a single List<Integer>
bean.
You can see this behaviour in the DefaultListableBeanFactory
class.
Relevant snippet below:
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
Class<?> type = descriptor.getDependencyType();
//Searches for an @Value annotation and
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
//Handle finding, building and returning default value
}
/*
* Check for multiple beans of given type. Because a bean is returned here,
* Spring autowires the Integer bean instance.
*/
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
// Do more stuff here to try and narrow down to a single instance to autowire.
}
}
Hopefully this explains why you do need to use an @Qualifer
annotation when trying to autowire a list of a type when you've got individual beans of that type in your application context.
EDIT:
It's worth noting that this is not good practice. Creating a collection of primitives or primitive wrappers and registering it as a bean is going to cause issues. The best way to do this is with @Value
and define your list of primitives in a properties file, that Spring picks up.
Example:
application.properties file
list=1,2,3,4
In your config class declare the following bean:
@Bean
public ConversionService conversionService() {
return new DefaultConversionService();
}
The default conversion service is used to convert comma separated values declared in a properties file into a collection of objects with type safety.
Class to use it:
@Value("${list}")
private List<Integer> anotherList;
anotherList
will contain 1,2,3 & 4 as elements in the list.
Upvotes: 14
Reputation: 12385
May be Spring is injecting all the Integer
type beans into a List
instead of Autowiring List<Integer>
bean that you declared.
Probably if you add @Qualifier("list")
at your injection point in your Test then it will provide the behavior you are expecting.
Upvotes: 1