從spring 2.5版本開始,spring提供了基于注解方式的依賴注入。在容器的xml配置檔案中,添加如下的配置
<context:annotation-config />
<context:component-scan base-package="com.example" />
即可掃描com.example包及其子包下所有使用特定注解注明的類,建立他們的執行個體并完成他們之間的依賴注入。非常的友善,大大友善了系統開發的配置。
昨天一個同僚在使用@Autowired自動注入依賴的集合bean時碰到了問題。定義了Manager,并聲明自動注入一個Set<String>
package com.example;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class Manager implements ApplicationListener<ApplicationContextEvent> {
@Autowired
private Set<String> locations;
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
for (String loc : locations) {
System.out.println("location -> " + loc);
}
}
}
}
容器配置檔案如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<context:annotation-config />
<context:component-scan base-package="com.example" />
<bean id="resourcePackage" class="java.lang.String">
<constructor-arg value="com.example.resource" />
</bean>
<bean id="resourceLocation" class="java.lang.String">
<constructor-arg value="classpath:config" />
</bean>
<util:set id="locations" value-type="java.lang.String">
<value>com.example.module</value>
<value>com.example.common.entity</value>
</util:set>
</beans>
期望的是注入配置檔案中id為locations的set,而事實卻是容器啟動後,輸出的卻是
location -> com.example.resource
location -> classpath:config
很奇怪,debug跟蹤源碼,在org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) 方法中發現了原因
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor);
}
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (this.dependencyComparator != null && result instanceof Object[]) {
Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
}
return result;
}
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getCollectionType();
if (elementType == null) {
if (descriptor.isRequired()) {
throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");
}
return null;
}
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor);
}
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (this.dependencyComparator != null && result instanceof List) {
Collections.sort((List<?>) result, adaptDependencyComparator(matchingBeans));
}
return result;
}
else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> keyType = descriptor.getMapKeyType();
if (keyType == null || !String.class.isAssignableFrom(keyType)) {
if (descriptor.isRequired()) {
throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +
"] must be assignable to [java.lang.String]");
}
return null;
}
Class<?> valueType = descriptor.getMapValueType();
if (valueType == null) {
if (descriptor.isRequired()) {
throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");
}
return null;
}
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
targetDesc.increaseNestingLevel();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor);
}
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return matchingBeans;
}
else {
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) {
raiseNoSuchBeanDefinitionException(type, "", descriptor);
}
return null;
}
if (matchingBeans.size() > 1) {
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (primaryBeanName == null) {
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(primaryBeanName);
}
return matchingBeans.get(primaryBeanName);
}
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
if (autowiredBeanNames != null) {
autowiredBeanNames.add(entry.getKey());
}
return entry.getValue();
}
對于@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的。