Search code examples
javaspringautowiredspring-java-config

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!


Solution

  • 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.