Spring @Autowired 注入集合类型的问题

从spring 2.5版本开始,spring提供了基于注解方式的依赖注入。在容器的xml配置文件中,添加如下的配置

[html]  view plain  copy
  1. <context:annotation-config />  
  2. <context:component-scan base-package="com.example" />  

即可扫描com.example包及其子包下所有使用特定注解注明的类,创建他们的实例并完成他们之间的依赖注入。非常的方便,大大方便了系统开发的配置。

昨天一个同事在使用@Autowired自动注入依赖的集合bean时碰到了问题。定义了Manager,并声明自动注入一个Set<String>

[java]  view plain  copy
  1. package com.example;  
  2.   
  3. import java.util.Set;  
  4.   
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.context.ApplicationListener;  
  7. import org.springframework.context.event.ApplicationContextEvent;  
  8. import org.springframework.context.event.ContextRefreshedEvent;  
  9. import org.springframework.stereotype.Component;  
  10.   
  11. @Component  
  12. public class Manager implements ApplicationListener<ApplicationContextEvent> {  
  13.   
  14.     @Autowired  
  15.     private Set<String> locations;  
  16.   
  17.     @Override  
  18.     public void onApplicationEvent(ApplicationContextEvent event) {  
  19.         if (event instanceof ContextRefreshedEvent) {  
  20.             for (String loc : locations) {  
  21.                 System.out.println("location -> " + loc);  
  22.             }  
  23.         }  
  24.     }  
  25. }  
容器配置文件如下

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xsi:schemaLocation="  
  6.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd  
  7.             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd  
  8.             http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">  
  9.   
  10.     <context:annotation-config />  
  11.   
  12.     <context:component-scan base-package="com.example" />  
  13.   
  14.     <bean id="resourcePackage" class="java.lang.String">  
  15.         <constructor-arg value="com.example.resource" />  
  16.     </bean>  
  17.   
  18.     <bean id="resourceLocation" class="java.lang.String">  
  19.         <constructor-arg value="classpath:config" />  
  20.     </bean>  
  21.   
  22.     <util:set id="locations" value-type="java.lang.String">  
  23.         <value>com.example.module</value>  
  24.         <value>com.example.common.entity</value>  
  25.     </util:set>  
  26. </beans>  
期望的是注入配置文件中id为locations的set,而事实却是容器启动后,输出的却是
location -> com.example.resource
location -> classpath:config

很奇怪,debug跟踪源码,在org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) 方法中发现了原因

[java]  view plain  copy
  1. if (type.isArray()) {  
  2.     Class<?> componentType = type.getComponentType();  
  3.     DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);  
  4.     targetDesc.increaseNestingLevel();  
  5.     Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc);  
  6.     if (matchingBeans.isEmpty()) {  
  7.         if (descriptor.isRequired()) {  
  8.             raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor);  
  9.         }  
  10.         return null;  
  11.     }  
  12.     if (autowiredBeanNames != null) {  
  13.         autowiredBeanNames.addAll(matchingBeans.keySet());  
  14.     }  
  15.     TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());  
  16.     Object result = converter.convertIfNecessary(matchingBeans.values(), type);  
  17.     if (this.dependencyComparator != null && result instanceof Object[]) {  
  18.         Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));  
  19.     }  
  20.     return result;  
  21. }  
  22. else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {  
  23.     Class<?> elementType = descriptor.getCollectionType();  
  24.     if (elementType == null) {  
  25.         if (descriptor.isRequired()) {  
  26.             throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");  
  27.         }  
  28.         return null;  
  29.     }  
  30.     DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);  
  31.     targetDesc.increaseNestingLevel();  
  32.     Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc);  
  33.     if (matchingBeans.isEmpty()) {  
  34.         if (descriptor.isRequired()) {  
  35.             raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor);  
  36.         }  
  37.         return null;  
  38.     }  
  39.     if (autowiredBeanNames != null) {  
  40.         autowiredBeanNames.addAll(matchingBeans.keySet());  
  41.     }  
  42.     TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());  
  43.     Object result = converter.convertIfNecessary(matchingBeans.values(), type);  
  44.     if (this.dependencyComparator != null && result instanceof List) {  
  45.         Collections.sort((List<?>) result, adaptDependencyComparator(matchingBeans));  
  46.     }  
  47.     return result;  
  48. }  
  49. else if (Map.class.isAssignableFrom(type) && type.isInterface()) {  
  50.     Class<?> keyType = descriptor.getMapKeyType();  
  51.     if (keyType == null || !String.class.isAssignableFrom(keyType)) {  
  52.         if (descriptor.isRequired()) {  
  53.             throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +  
  54.                     "] must be assignable to [java.lang.String]");  
  55.         }  
  56.         return null;  
  57.     }  
  58.     Class<?> valueType = descriptor.getMapValueType();  
  59.     if (valueType == null) {  
  60.         if (descriptor.isRequired()) {  
  61.             throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");  
  62.         }  
  63.         return null;  
  64.     }  
  65.     DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);  
  66.     targetDesc.increaseNestingLevel();  
  67.     Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc);  
  68.     if (matchingBeans.isEmpty()) {  
  69.         if (descriptor.isRequired()) {  
  70.             raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor);  
  71.         }  
  72.         return null;  
  73.     }  
  74.     if (autowiredBeanNames != null) {  
  75.         autowiredBeanNames.addAll(matchingBeans.keySet());  
  76.     }  
  77.     return matchingBeans;  
  78. }  
  79. else {  
  80.     Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);  
  81.     if (matchingBeans.isEmpty()) {  
  82.         if (descriptor.isRequired()) {  
  83.             raiseNoSuchBeanDefinitionException(type, "", descriptor);  
  84.         }  
  85.         return null;  
  86.     }  
  87.     if (matchingBeans.size() > 1) {  
  88.         String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);  
  89.         if (primaryBeanName == null) {  
  90.             throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());  
  91.         }  
  92.         if (autowiredBeanNames != null) {  
  93.             autowiredBeanNames.add(primaryBeanName);  
  94.         }  
  95.         return matchingBeans.get(primaryBeanName);  
  96.     }  
  97.     // We have exactly one match.  
  98.     Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();  
  99.     if (autowiredBeanNames != null) {  
  100.         autowiredBeanNames.add(entry.getKey());  
  101.     }  
  102.     return entry.getValue();  
  103. }  

对于@Autowired声明的数组、集合类型,spring并不是根据beanName去找容器中对应的bean,而是把容器中所有类型与集合(数组)中元素类型相同的bean构造出一个对应集合,注入到目标bean中。对应到上问配置文件中,就是把容器中所有类型为java.lang.String的bean放到新建的Set中,然后注入到Manager bean中。也就是把resourcePackage和resourceLoaction这两个String注入了,导致上面的输出结果。

在spring reference中也发现相关说明。

@Autowired

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.

从上面的说明中找到解决办法就是注入集合类型不要使用@Autowired,而使用@Resource注解。同时Spring官方也是不推荐使用@Autowired的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: @Resouce和@Autowired都是用于依赖注入的注解,但是它们有一些区别。@Resouce是JavaEE提供的注解,而@AutowiredSpring框架提供的注解。@Resouce默认按照名称进行装配,如果找不到对应名称的bean,则按照类进行装配。而@Autowired默认按照类进行装配,如果找不到对应类的bean,则会报错。此外,@Resouce还可以通过name属性指定名称进行装配,而@Autowired可以通过required属性指定是否必须装配成功。 ### 回答2: @Resouce和@Autowired都是用于依赖注入的注解,用来自动装配Bean对象,但它们有以下不同: 1. 来源:@Resource是JavaEE提供的注解,而@Autowired是由Spring框架提供的注解。 2. 注入方式:@Resource按照名称进行匹配注入,@Autowired默认按照类进行匹配注入。 3. 属性:@Resource注解没有任何属性,而@Autowired注解有一些属性,如required、qualifier和primary等。 4. JSR-250规范:@Resource注解是JSR-250规范中定义的注解,而@AutowiredSpring框架特有的注解。 5. 兼容性:@Resource注解是兼容JavaEE规范的注解,可以被其他JavaEE容器解析,而@Autowired注解只能被Spring框架解析。 总体来说,两者的作用相似,都可以实现依赖注入的功能,但使用的规范和源头不同。在实际开发中,可以根据具体情况选择适合自己的注解。 ### 回答3: @Resource和@Autowired都是Spring框架中用于实现依赖注入的注解。它们的作用是注入bean对象,减少手动的对象创建和依赖查询。 首先讲一下@Resource注解。@Resource注解是由JSR250规范提供的,它有两个常用的属性name和type。name属性用于指定需要注入的bean的名称,如果不指定,则默认按照属性名进行查找。type属性用于指定需要注入的bean的类。@Resource注解默认按照name进行查找,如果找不到对应的bean,则会按照type进行查找。如果即没有指定name也没有指定type,则会按照属性的类进行查找。 而@Autowired注解是由Spring提供的,它的作用和@Resource类似,也用于注入bean对象。@Autowired注解默认按照属性的类进行查找,并且需要配合@Autowired注解的required属性,来指定该属性是否必须进行注入。@Autowired注解也可以在构造方法、方法参数以及集合类注入中使用。 总结一下两者的区别: 1. 来源不同:@Resource注解由JSR250规范提供,而@Autowired注解由Spring框架提供。 2. 属性选择不同:@Resource注解可以根据name或type进行查找,默认是按照name进行查找,而@Autowired注解默认是按照属性的类进行查找。 3. 结合注解不同:@Autowired注解可以和required属性进行结合使用,来指定属性是否必须进行注入,而@Resource注解没有类似的属性选择。 综上所述,@Resource和@Autowired注解在实现依赖注入功能上有一些区别,开发者在使用时要根据具体情况选择适合的注解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值