在Spring Boot项目中,如何把某些接口的多个实现类的Bean注入到Arrays, java.util.Collection 和 java.util.Map类型的变量中,方便应用的时候直接读取?其实,Spring是支持这种基于接口实现类的直接注入的——使用注解@Autowired即可。

软件环境

  1. java version 13.0.1

  2. IntelliJ IDEA 2019.3.2 (Ultimate Edition)
  3. 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

    这个实现的源码在DefaultListableBeanFactory的doResolveDependency方法中,具体如下(参考的Spring版本是spring-beans-5.2.7.RELEASE.jar): 
@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

Spring注解之@Autowired:按类型自动装配Bean到数组、集合和Map的更多相关文章

  1. IDEA02 利用Maven创建Web项目、为Web应用添加Spring框架支持、bean的创建于获取、利用注解配置Bean、自动装配Bean、MVC配置

    1 环境版本说明 Jdk : 1.8 Maven : 3.5 IDEA : 专业版 2017.2 2 环境准备 2.1 Maven安装及其配置 2.2 Tomcat安装及其配置 3 详细步骤 3.1 ...

  2. 【Spring】Spring中的Bean - 5、Bean的装配方式(XML、注解(Annotation)、自动装配)

    Bean的装配方式 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 文章目录 Bean的装配方式 基于XML的装配 基于注解 ...

  3. Spring中类型自动装配--byType

    在Spring中,“类型自动装配”的意思是如果一个bean的数据类型与其它bean属性的数据类型相同,将自动兼容装配它. 例如,一个“persion” bean 公开以“ability”类数据类型作为 ...

  4. spring的自动装配Bean与自动检测Bean

    spring可以通过编写XML来配置Bean,也可以通过使用spring的注解来装配Bean. 1.自动装配与自动检测: 自动装配:让spring自动识别如何装配bean的依赖关系,减少对<pr ...

  5. Spring -- 入门,装备集合,自动装配,分散装配,自定义编辑器

    1. 概要 struts2:web hibernate:持久化 spring:业务层.管理bean的,容器.List Map Set. 体验spring: 1.创建java项目. 2.引入spring ...

  6. spring中自动装配bean

    首先用@Component注解类: package soundsystem: import org.springframework.stereotype.Component; @Component p ...

  7. spring框架学习(四)自动装配

    set注入和构造注入有时在做配置时比较麻烦.所以框架为了提高开发效率,提供自动装配功能,简化配置.spring框架式默认不支持自动装配的,要想使用自动装配需要修改spring配置文件中<bean ...

  8. Spring 自动装配 Bean

    Spring3系列8- Spring 自动装配 Bean 1.      Auto-Wiring ‘no’ 2.      Auto-Wiring ‘byName’ 3.      Auto-Wiri ...

  9. Spring入门(5)-自动装配Bean属性

    Spring入门(5)-自动装配Bean属性 本文介绍如何装配Bean属性. 0. 目录 ByName ByType constructor 默认自动装配 混合使用自动装配和显示装配 1. ByNam ...

  10. Spring自动装配Bean详解

    1.      Auto-Wiring ‘no’ 2.      Auto-Wiring ‘byName’ 3.      Auto-Wiring ‘byType 4.      Auto-Wirin ...

随机推荐

  1. FormCreate设计器v5.6发布—AI加持的低代码表单设计器正式上线!

    近期DeepSeek可谓是刷遍全网,当然,在DeepSeek等AI技术的推动下,人工智能正以惊人的速度改变着各行各业.AI不仅是一种技术趋势,更是未来生产力的核心驱动力. 如今,FormCreate设 ...

  2. mysql : 第5章 数据库的安全性

    -- 创建用户CREATE USER utest@localhost IDENTIFIED BY 'temp';-- 查看所有用户SELECT * FROM mysql.user;-- 查看表级权限S ...

  3. VitePress全局组件封装

    前言 VuePress 主题默认有 <CodeGroup> 组件用于切换代码很方便. 如图所示: 痛点 使用 VitePress 后,官方没有提供 <CodeGroup> 组件 ...

  4. JS中数组的操作方法大全

    常见的一些数组操作push . pop.unshift. shift push 语法: array.push(item1, item2, -, itemX) push( )方法:可以将一个或者更多的参 ...

  5. 解决macOS无法验证“xxx”的开发者。你确定要打开它吗?

    前言 当 macOS 无法验证开发者时,有两种方式解决,可以通过以下步骤来打开 xxx 系统偏好设置: 打开"系统偏好设置". 选择"安全性与隐私". 在&qu ...

  6. NumPy学习4

    今天学习NumPy相关数组操作 NumPy 中包含了一些处理数组的常用方法,大致可分为以下几类:(1)数组变维操作(2)数组转置操作(3)修改数组维度操作(4)连接与分割数组操作 numpy_test ...

  7. 用Docker Swarm实现容器服务高可用

    背景与技术选择 根据我之前的几篇「Django 系列」文章,后端架构中我使用了 Django + Celery + RabbitMQ 三个框架/服务.现在有几个问题: 如何用容器快速部署这三个应用? ...

  8. .net 跨域 config中配置

    <system.webServer> <validation validateIntegratedModeConfiguration="false" /> ...

  9. 在Unity中实现(纯C#)热更新--使用ILRunTime{学习日志}

    热更新的逻辑:热更新的那部分内容其实就是一个dll的库文件,到时候修改也是改这个库文件: 我们只需要在主工程(我们的Unity项目)中引入并调用这个dll库里的代码就行了. 首先我们需要在Unity中 ...

  10. 阿里云ECS安装 CoreOS

    没事重装了下阿里云的ECS,无意发现竟然有了 CoreOS 的选项,有点小激动,于是乎,果断选择安装尝试了下. 阿里云ECS安装 CoreOS 其他阿里云注册啥的就不多说了,来个主要的图说明下: 题外 ...