[英]Constructor injection and arrays with Spring
I would like to understand whether there is a clean way to use constructor injection with arrays in spring-boot (1.3.5.RELEASE). 我想了解一下是否有一种在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: 我知道@Autowired按类型工作,因此上一个应用程序不起作用的原因是,当必须注入Car []时,Spring首先尝试查找所有Car Bean,但是由于没有Car Bean,因此以下内容抛出异常:
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. 我也明白,如果我用字段注入+ @Resource代替构造函数注入,那么一切都会正常,因为数组是按名称而不是类型注入的。
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)? 那么,我是否缺少某些东西,或者它是真正的spring限制(即,当前无法在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: Bean是使用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 ). 事实证明,从Spring 4.3开始,可以对数组,集合和映射进行构造函数注入(请参阅issue )。
As stated in the docs: 如文档中所述:
6.9.4 Fine-tuning annotation-based autowiring with qualifiers
6.9.4使用限定符微调基于注释的自动装配
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.如果您打算通过名称表示注释驱动的注入,则即使技术上可以通过
@Qualifier
值引用bean名称,也不要主要使用@Autowired
。 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.而是使用JSR-250
@Resource
批注,该批注的语义定义是通过其唯一名称标识特定的目标组件,而声明的类型与匹配过程无关。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.由于这种语义差异的特定结果, 本身定义为集合或映射类型的bean无法通过
@Autowired
注入 ,因为类型匹配不适用于它们。 Use@Resource
for such beans, referring to the specific collection or map bean by unique name.对此类bean使用
@Resource
,通过唯一名称引用特定的collection或map bean。
@Autowired
applies to fields, constructors, and multi-argument methods, allowing for narrowing through qualifier annotations at the parameter level.@Autowired
适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符注释的范围。 By contrast,@Resource
is supported only for fields and bean property setter methods with a single argument.相比之下,只有具有单个参数的字段和bean属性设置器方法才支持
@Resource
。 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
6.4.5自动装配合作者
With byType or constructor autowiring mode, you can wire arrays and typed-collections.使用byType或构造函数自动装配模式,您可以连接数组和类型集合。 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 is
String
.如果期望的键类型为
String
则可以自动连接强类型的Maps。 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.自动装配的Maps值将由与期望类型匹配的所有bean实例组成,并且Maps键将包含相应的bean名称。
6.9.2 @Autowired
6.9.2 @自动接线
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: [...]通过将注释添加到需要该类型数组的字段或方法中,也可以从
ApplicationContext
提供所有特定类型的bean:[...]
The same applies for typed collections: [...]同样适用于类型化的集合:[...]
Even typed Maps can be autowired as long as the expected key type isString
.只要预期的键类型是
String
即使是键入的Map都可以自动装配。 The Map values will contain all beans of the expected type, and the keys will contain the corresponding bean names: [...]Map值将包含所有预期类型的bean,并且键将包含相应的bean名称:[...]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.