walsh
walsh

Reputation: 3273

How to @Autowired a List<Integer> in spring framework

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

Answers (2)

JamesENL
JamesENL

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:

  1. Default Value using @Value
  2. Multiple beans using a type parameter.
  3. Individual beans that match the field type.

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

K. Siva Prasad Reddy
K. Siva Prasad Reddy

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

Related Questions