Spring注解之@Autowired:按类型自动装配Bean到数组、集合和Map
在Spring Boot项目中,如何把某些接口的多个实现类的Bean注入到Arrays, java.util.Collection 和 java.util.Map类型的变量中,方便应用的时候直接读取?其实,Spring是支持这种基于接口实现类的直接注入的——使用注解@Autowired即可。
软件环境
-
java version 13.0.1
- IntelliJ IDEA 2019.3.2 (Ultimate Edition)
- Spring Boot 2.3.0.RELEASE
举例说明
新增一个没有方法的接口BeanInterface:
/** * 定义bean接口
*/
public interface BeanInterface {
void doSth(String sth);
}
创建两个实现类:
package com.east7.service.impl; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * order:把实现类排序输出 只适合List * * @author Wiener * @date 2020/7/8 21:27 */ @Order(2) @Component public class BeanImplOne implements BeanInterface { @Override public void doSth(String sth) { System.out.println(String.format("BeanImplOne:%s", sth)); } } ===========我是分割线============= package com.east7.service.impl; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * * @author Wiener * @date 2020/7/8 21:29 */ @Order(1) @Component("beanImplTwoAlias") //指定bean的名称 public class BeanImplTwo implements BeanInterface { @Override public void doSth(String sth) { System.out.println(String.format("BeanImplTwo:%s", sth)); } }
下面新增一个类BeanInvoker,主要测试@Autowired注解能否把我们预期的Bean注入Map和List类型的变量中。
package com.east7.service.impl;
import com.east7.service.BeanInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* @author Wiener
* @date 2020/7/7 21:32
*/
@Component
public class BeanInvoker {
@Autowired(required = false)
private List<BeanInterface> list;
@Autowired
private Map<String, BeanInterface> map;
/**
* @Autowired默认为byType的 所以有两个相同类型的bean;
* 如果不使用 @Resource 指定具体的bean就会抛出异常
* private BeanInterface beaninterface;
*/
@Resource(name = "beanImplOne")
private BeanInterface beaninterface;
public void say() {
System.out.println("list...");
list.forEach(bean -> {
System.out.println(bean.getClass().getName());
});
System.out.println("map...");
map.forEach((key, value) -> {
System.out.println(key + ":" + value.getClass().getName());
});
System.out.println("-------------------------");
System.out.println(beaninterface.getClass().getName());
beaninterface.doSth("在打印此信息!");
beaninterface = map.get("beanImplTwoAlias");
beaninterface.doSth("当前方法的实现类,表明切换实现类成功!");
}
}
修改Spring Boot 启动类的main函数:
/**
* @author Wiener 1610776933
*/
@SpringBootApplication
public class East7Application {
public static void main(String[] args) {
ApplicationContext act = SpringApplication.run(East7Application.class, args);
BeanInvoker invoker = (BeanInvoker) act.getBean("beanInvoker");
invoker.say();
}
}
以断点模式运行main函数,在断点视图中可以看到接口的两个实现类已经全部注入到属性list和map中,而且map里面的key默认设置为两个实现类的类名,如下图所示:
放开断点后,控制台打印结果如下:
list...
com.east7.service.impl.BeanImplTwo
com.east7.service.impl.BeanImplOne
map...
beanImplOne:com.east7.service.impl.BeanImplOne
beanImplTwoAlias:com.east7.service.impl.BeanImplTwo
-------------------------
com.east7.service.impl.BeanImplOne
BeanImplOne:在打印此信息!
BeanImplTwo:当前方法的实现类,表明切换实现类成功!
关于list中的Bean,Spring是怎么排序的?在实现类中加入@Order(value) 注解即可指定Bean的序列,值越小优先级越高,就尽早被初始化和放入list。
原理机制
关于两个变量中的Bean,为什么只有BeanInterface类型的?Spring把容器中所有与变量中泛型相同类型的Bean提取出来,构造成对应对象,注入到目标变量(如变量map)中。 换种说法就是Spring会查找应用上下文里BeanInterface类型的bean并放进List<BeanInterface> list或者Map<String, BeanInterface> map,例如查找BeanInterface类型的bean 以value的形式put进map,key为bean的name。
1610776933
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class<?> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
@Nullable
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
final Class<?> type = descriptor.getDependencyType();
if (descriptor instanceof StreamDependencyDescriptor) {
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
Stream<Object> stream = matchingBeans.keySet().stream()
.map(name -> descriptor.resolveCandidate(name, type, this))
.filter(bean -> !(bean instanceof NullBean));
if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
stream = stream.sorted(adaptOrderComparator(matchingBeans));
}
return stream;
}
else if (type.isArray()) {
Class<?> componentType = type.getComponentType();
ResolvableType resolvableType = descriptor.getResolvableType();
Class<?> resolvedArrayType = resolvableType.resolve(type);
if (resolvedArrayType != type) {
componentType = resolvableType.getComponentType().resolve();
}
if (componentType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
if (result instanceof Object[]) {
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
Arrays.sort((Object[]) result, comparator);
}
}
return result;
}
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
if (elementType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (result instanceof List) {
if (((List<?>) result).size() > 1) {
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
((List<?>) result).sort(comparator);
}
}
}
return result;
}
else if (Map.class == type) {
ResolvableType mapType = descriptor.getResolvableType().asMap();
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
return null;
}
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return matchingBeans;
}
else {
return null;
}
}
我们可以看到在resolveMultipleBeans方法的实现包括三个方面:①如果是数组,则查找组件类型为数组的所有bean(Returns the Class representing the component type of an array.),返回一个此类bean的数组;②如果该类可赋给Collection,并且是一个接口,则匹配相关集合类型的所有bean,返回一个这些bean的集合;③如果该类型是Map(注意是type == Map.class),且key是String类型,则查找该类型的所有bean的Map集合,这是一个key为bean name、value为bean实例的一个Map。其中用到的一个关键函数是findAutowireCandidates,它的功能是在自动装配bean的时候,根据类型查找所有bean,英文注释如下:
/**
* Find bean instances that match the required type.
* Called during autowiring for the specified bean.
* @param beanName the name of the bean that is about to be wired
* @param requiredType the actual type of bean to look for
* (may be an array component type or collection element type)
* @param descriptor the descriptor of the dependency to resolve
* @return a Map of candidate names and candidate instances that match
* the required type (never {@code null})
* @throws BeansException in case of errors
* @see #autowireByType
* @see #autowireConstructor
*/
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
// omit function body
}
Reference
https://cloud.tencent.com/developer/article/1420334