I would like to understand whether there is a clean way to use constructor injection with arrays in spring-boot (1.3.5.RELEASE).
I've created this simple app that better explains my question:
package com.stackoverflow;
import java.util.Arrays;
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@SpringBootApplication
public class Application {
private static class Car { }
@Bean
public Car[] cars() {
return IntStream.range(0, 10).mapToObj(i -> new Car()).toArray(Car[]::new);
}
@Component
private static class Road implements CommandLineRunner {
private final Car[] cars;
@Autowired
public Road(Car[] cars) {
this.cars = cars;
}
// @Resource
// private Car[] cars;
@Override
public void run(String... args) throws Exception {
System.out.println(Arrays.toString(cars));
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I understand that @Autowired works by type, so the reason why the previous application does not work is because when the Car[] has to be injected, Spring first tries to find all Car beans but, since there is no Car bean, the following exception is thrown:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application.Road' defined in file [/spring-array-injection/target/classes/com/stackoverflow/Application$Road.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.stackoverflow.Application$Car[]]: No qualifying bean of type [com.stackoverflow.Application$Car] found for dependency [array of com.stackoverflow.Application$Car]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.stackoverflow.Application$Car] found for dependency [array of com.stackoverflow.Application$Car]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:185) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1143) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1046) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at com.stackoverflow.Application.main(Application.java:44) [classes/:na]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.stackoverflow.Application$Car] found for dependency [array of com.stackoverflow.Application$Car]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 19 common frames omitted
I also understand that if I replace the constructor injection with field injection + @Resource everything works because the array is injected by name instead of type.
So, am I missing something or is it a real spring limitation (ie it is currently not possible to use constructor injection with arrays/lists of object in spring)?
Wow, I thought this question had a shorter life but the case is not solved yet. I will post the two (ugly) workarounds I've tested so far:
A wrapper around the array is injected instead of the plain array:
package com.stackoverflow.workaround.arrayholder; import java.util.Arrays; import java.util.stream.IntStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @SpringBootApplication public class Application { private static class ArrayHolder<T> { private final T array; public ArrayHolder(T array) { this.array = array; } public T getArray() { return array; } } private static class Car { } @Bean public ArrayHolder<Car[]> cars() { return new ArrayHolder<>(IntStream.range(0, 10).mapToObj(i -> new Car()).toArray(Car[]::new)); } @Component private static class Road implements CommandLineRunner { private final Car[] cars; @Autowired public Road(ArrayHolder<Car[]> cars) { this.cars = cars.getArray(); } @Override public void run(String... args) throws Exception { System.out.println(Arrays.toString(cars)); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
The beans are dynamically created and registered using the BeanFactoryPostProcessor:
package com.stackoverflow.workaround.dynamicregistration; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Component; @SpringBootApplication public class Application implements BeanFactoryPostProcessor { private static class Car { } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { AtomicInteger atomicInteger = new AtomicInteger(); IntStream.range(0, 10) .mapToObj(i -> new Car()) .forEach(car -> beanFactory.registerSingleton(String.valueOf(atomicInteger.getAndIncrement()), car)); } @Component private static class Road implements CommandLineRunner { private final Car[] cars; @Autowired public Road(Car[] cars) { this.cars = cars; } @Override public void run(String... args) throws Exception { System.out.println(Arrays.toString(cars)); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
It turns out that constructor injection of arrays, collections and maps will be possible starting from Spring 4.3 (see issue ).
As stated in the docs:
6.9.4 Fine-tuning annotation-based autowiring with qualifiers
If you intend to express annotation-driven injection by name, do not primarily use@Autowired
, even if is technically capable of referring to a bean name through@Qualifier
values. Instead, use the JSR-250@Resource
annotation, which is semantically defined to identify a specific target component by its unique name, with the declared type being irrelevant for the matching process.As a specific consequence of this semantic difference, beans that are themselves defined as a collection or map type cannot be injected through
@Autowired
, because type matching is not properly applicable to them. Use@Resource
for such beans, referring to the specific collection or map bean by unique name.
@Autowired
applies to fields, constructors, and multi-argument methods, allowing for narrowing through qualifier annotations at the parameter level. By contrast,@Resource
is supported only for fields and bean property setter methods with a single argument. As a consequence, stick with qualifiers if your injection target is a constructor or a multi-argument method.
And arrays are treated the same way as collections:
6.4.5 Autowiring collaborators
With byType or constructor autowiring mode, you can wire arrays and typed-collections. In such cases all autowire candidates within the container that match the expected type are provided to satisfy the dependency. You can autowire strongly-typed Maps if the expected key type isString
. An autowired Maps values will consist of all bean instances that match the expected type, and the Maps keys will contain the corresponding bean names.
6.9.2 @Autowired
It is also possible to provide all beans of a particular type from theApplicationContext
by adding the annotation to a field or method that expects an array of that type: [...]
The same applies for typed collections: [...]
Even typed Maps can be autowired as long as the expected key type isString
. The Map values will contain all beans of the expected type, and the keys will contain the corresponding bean names: [...]
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.