首页 > 技术文章 > Spring注解之@Autowired:按类型自动装配Bean到数组、集合和Map

east7 2020-07-08 21:28 原文

   在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

 

推荐阅读