繁体   English   中英

Spring构造函数注入和数组

[英]Constructor injection and arrays with Spring

我想了解一下是否有一种在spring-boot(1.3.5.RELEASE)中对数组使用构造函数注入的干净方法。

我创建了这个简单的应用程序,可以更好地解释我的问题:

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);
    }

}

我知道@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

我也明白,如果我用字段注入+ @Resource代替构造函数注入,那么一切都会正常,因为数组是按名称而不是类型注入的。

那么,我是否缺少某些东西,或者它是真正的spring限制(即,当前无法在spring中将构造函数注入与对象的数组/列表一起使用)?

更新1

哇,我以为这个问题的寿命较短,但案件尚未解决。 我将发布到目前为止已经测试过的两个(丑陋)解决方法:

  1. 围绕数组的包装器被注入,而不是普通数组:

     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); } } 
  2. 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); } } 

更新2

事实证明,从Spring 4.3开始,可以对数组,集合和映射进行构造函数注入(请参阅issue )。

如文档中所述:

6.9.4使用限定符微调基于注释的自动装配
如果您打算通过名称表示注释驱动的注入,则即使技术上可以通过@Qualifier值引用bean名称,也不要主要使用@Autowired 而是使用JSR-250 @Resource批注,该批注的语义定义是通过其唯一名称标识特定的目标组件,而声明的类型与匹配过程无关。

由于这种语义差异的特定结果, 本身定义为集合或映射类型的bean无法通过@Autowired注入 ,因为类型匹配不适用于它们。 对此类bean使用@Resource ,通过唯一名称引用特定的collection或map bean。

@Autowired适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符注释的范围。 相比之下,只有具有单个参数的字段和bean属性设置器方法才支持@Resource 因此,如果注入目标是构造函数或多参数方法,请坚持使用限定符。

数组与集合的处理方式相同:

6.4.5自动装配合作者
使用byType构造函数自动装配模式,您可以连接数组和类型集合。 在这种情况下,将提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。 如果期望的键类型为String则可以自动连接强类型的Maps。 自动装配的Maps值将由与期望类型匹配的所有bean实例组成,并且Maps键将包含相应的bean名称。

6.9.2 @自动接线
通过将注释添加到需要该类型数组的字段或方法中,也可以从ApplicationContext提供所有特定类型的bean:[...]
同样适用于类型化的集合:[...]
只要预期的键类型是String即使是键入的Map都可以自动装配。 Map值将包含所有预期类型的​​bean,并且键将包含相应的bean名称:[...]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM