Spring IoC 依赖查找之类型查找

Spring IoC 依赖查找之类型查找

Spring 核心编程思想目录:https://www.cnblogs.com/binarylei/p/12290153.html

推荐文章:

Spring 的类型查找相当复杂,主要原因倒不是因为理解起来有多复杂,主要是因为 Spring Bean 创建有多种方式,Spring 需要兼容如此多的类型查找方式,代码看起来就有点复杂了。

1. Spring Bean 五种实例方式

关于 Spring Bean 常见的五种创建方式,每一各创建方式获取其类型的方式都不一样。

  • 1. 无参构造器
  • 2. 有参构造器:无论是有参还是无参构造器,BeanDefinition 都定义了 className 属性,可以直接获取其类型,不需要实例化 Bean.。
  • 3. FactoryBean:一般来说是根据 FactoryBean#getObjectType 获取 Bean 类型。所以必须实例化 FactoryBean 对象,但 Spring 为了减少提前初始化 Bean 的影响,只部分实例化 FactoryBean 对象,而不进行属性注入。但还有一种特殊的情况,FactoryBean 正在初始化时,就不能通过 getObjectType 获取对象类型,此时可以获取 FactoryBean 上的泛型类型,参考 循环依赖+类型推断引发的 Bug。也就是泛型是 FactoryBean 的一种兜底方案。
  • 4. 静态工厂:通过方法的返回值类型,获取 Bean 类型,不需要实例化 Bean
  • 5. 实例工厂:通过方法的返回值类型,获取 Bean 类型,不需要实例化 Bean。正因为不会实例化该实例工厂,如果工厂方法的返回值类型有泛型,则无法获取 Bean 类型。

我们之所以强调这五种 Bean 配置方式,分别获取 Bean 类型时是否会实例化 Bean,是因为在 Spring 中提前初始化 Bean 会导致很多问题。

2. Spring API

Spring 根据类型查找提供了如下 API:

  • 获取单个 Bean 类型实例
    • getBean(Class) 以及重载方法
  • 获取集合 Bean 类型实例
    • getBeansOfType(Class) 以及重载方法
  • 获取集合 Bean 类型名称
    • getBeanNamesForType(Class)
    • Spring 4.2 getBeanNamesForType(ResolvableType)

2.1 获取单个 Bean 类型实例

getBean(Class) 最终调用 resolveNamedBean 方法,resolveNamedBean 是 Spring 内部根据类型查找 bean 的方法。

private <T> NamedBeanHolder<T> resolveNamedBean(ResolvableType requiredType, 
        Object[] args, boolean nonUniqueAsNull) throws BeansException {
    // 1. 根据类型查找,不会实例化 bean
    String[] candidateNames = getBeanNamesForType(requiredType);

    // 2. 多个类型,如何过滤?
    if (candidateNames.length > 1) {
        List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
        for (String beanName : candidateNames) {
            // 如果容器中定义的beanDefinition.autowireCandidate=false(默认为true)则剔除
            // ①没有定义该beanDefinition或②beanDefinition.autowireCandidate=true时合法
            // 什么场景下会出现:没有定义该BeanDefinition,但根据类型可以查找到该beanName?
            if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
                autowireCandidates.add(beanName);
            }
        }
        if (!autowireCandidates.isEmpty()) {
            candidateNames = StringUtils.toStringArray(autowireCandidates);
        }
    }

    // 3. 单个candidateNames,则调用getBean(beanName)实例化该bean
    if (candidateNames.length == 1) {
        String beanName = candidateNames[0];
        return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
    // 4. 多个candidateNames,先尝试是否标注Primary属性,再尝试类上@Priority注解
    } else if (candidateNames.length > 1) {
        Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
        for (String beanName : candidateNames) {
            if (containsSingleton(beanName) && args == null) {
                Object beanInstance = getBean(beanName);
                candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
            } else {
                candidates.put(beanName, getType(beanName));
            }
        }
        // 查找 primary Bean,即 beanDefinition.primary=true
        String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass());
        // 比较 Bean 的优先级。@javax.annotation.Priority
        if (candidateName == null) {
            candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
        }
        // 4. 过滤后只有一个符合条件,getBean(candidateName)实例化
        if (candidateName != null) {
            Object beanInstance = candidates.get(candidateName);
            if (beanInstance == null || beanInstance instanceof Class) {
                beanInstance = getBean(candidateName, requiredType.toClass(), args);
            }
            return new NamedBeanHolder<>(candidateName, (T) beanInstance);
        }
        // 5. 多个bean,抛出NoUniqueBeanDefinitionException异常
        if (!nonUniqueAsNull) {
            throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
        }
    }

    return null;
}

说明: 代码很长,但逻辑却很简单。

  1. 首先,根据 Bean 类型查找 Spring IoC 容器中所有符合条件的 Bean 名称,可能有多个。要注意的是,getBeanNamesForType 只会读取 BeanDefinition 信息或部分实例化 FactoryBean 来获取 Bean 的类型,但还没有实例化 Bean。

  2. 如果容器中只注册了一个这种类型的 Bean,没什么可说的,直接实例化该 Bean 后返回即可。

  3. 问题是,Spring IoC 容器中可能注册有多个 Bean,此时该怎么办呢?Spring 做了好几重过滤:

    • 第一重过滤:没有定义该 BeanDefinition 或该 beanDefinition.autowireCandidate=true。默认情况下 autowireCandidate=true,也就是一般情况下都会通过。

      但令人疑惑的是,什么场景下会出现:没有定义该 BeanDefinition,但根据类型查找 getBeanNamesForType 可以查找到该 beanName 呢?多线程情况下?

    • 第二重过滤:查找 primary Bean,即 beanDefinition.primary=true。如果有多个,则抛出 NoUniqueBeanDefinitionException。

    • 第三重过滤:比较 Bean 的优先级。Spring 默认的比较器是 AnnotationAwareOrderComparator,比较 Bean 上 @javax.annotation.Priority 的优先级,值越小优先级越高。同样的,如果最高级别的多个,则抛出 NoUniqueBeanDefinitionException。

      话说,好像不支持 Spring 原生的 @Order 注解,难道我看错了?

    • 最后,还有多个,则抛出 NoUniqueBeanDefinitionException。

  4. 根据类型查找,最复杂的两步:

    • 根据类型查找所有符合条件的 beanNames - getBeanNamesForType。
    • 实例化 candidateName - getBean(candidateName)。Bean 的实例化不是本章重点。

2.2 获取集合 Bean 类型实例

和获取单个 Bean 类型实例的过程如出一辙,只不过如果有多个 candidateNames 时不用过滤,全部返回即可。

@Override
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException {
    return getBeansOfType(type, true, true);
}
@Override
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, 
       boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
    String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
    Map<String, T> result = new LinkedHashMap<>(beanNames.length);
    for (String beanName : beanNames) {
        Object beanInstance = getBean(beanName);
        if (!(beanInstance instanceof NullBean)) {
            result.put(beanName, (T) beanInstance);
        }
    }
    return result;
}

说明: getBeansOfType 同样调用 getBeanNamesForType 获取所有类型匹配的 beanNames,然后调用 getBean(beanName) 实例化所有的 Bean。

2.3 获取集合 Bean 类型名称

getBeanNamesForType 方法的功能,Spring 内部根据类型匹配所有的 beanNames。getBeanNamesForType 不会初始化 Bean,根据其 BeanDefinition 或 FactoryBean#getObjectType 获取其类型,具体获取 Bean 类型的方法参考本文第一部分。

getBeanNamesForType 有三个重载的方法:

  • Spring 4.2 getBeanNamesForType(ResolvableType):用于处理泛型
  • getBeanNamesForType(Class)
  • getBeanNamesForType( Class<?> type, boolean includeNonSingletons, boolean allowEagerInit):type 表示要查找的类型,includeNonSingletons 表示是否包含非单例 Bean,allowEagerInit 表示是否提前部分初始化 FactoryBean。
// 泛型处理。ResolvableType 是Spring内部专门用于处理泛型的工具类
@Override
public String[] getBeanNamesForType(ResolvableType type) {
    Class<?> resolved = type.resolve();
    if (resolved != null && !type.hasGenerics()) {
        return getBeanNamesForType(resolved, true, true);
    } else {
        return doGetBeanNamesForType(type, true, true);
    }
}

// 非泛型处理
@Override
public String[] getBeanNamesForType(@Nullable Class<?> type) {
    return getBeanNamesForType(type, true, true);
}

// 通用API
@Override
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
    // 1. 查询的结果不使用缓存,是因为此时查询的结果可能不正确吗?
    // 1.1 configurationFrozen表示是否冻结BeanDefinition,不允许修改,因此查询的结果可能有误
    //     一旦调用refresh方法,则configurationFrozen=true,也就是容器启动过程中会走if语句
    // 1.2 type=null,查找所有Bean?
    // 1.3 !allowEagerInit 表示不允许提前初始化FactoryBean,因此可能获取不到Bean的类型
    if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
        return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
    }
    // 2. 允许使用缓存,此时容器已经启动完成,bean已经加载,BeanDefinition不允许修改
    Map<Class<?>, String[]> cache =
        (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
    String[] resolvedBeanNames = cache.get(type);
    if (resolvedBeanNames != null) {
        return resolvedBeanNames;
    }
    resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
    if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
        cache.put(type, resolvedBeanNames);
    }
    return resolvedBeanNames;
}

说明: 三种类型推断底层都调用 doGetBeanNamesForType 方法,前两种重载方法都很简单就不多说了,这里重点说一下第三种查找方式。

  1. 参数 type 表示要查找的类型,includeNonSingletons 表示是否包含非单例 Bean,allowEagerInit 表示是否提前部分初始化 FactoryBean(为什么说是部分初始化?是因为只实例化 FactoryBean,而不进行属性注入)。

  2. 是否将查询的结果缓存起来,个人认为原因是 getBeanNamesForType 方法根据类型查找匹配的 beanNames 结果并不十分准确。原因很简单,因为该方法不会通过提前实例化 Bean 的方式获取其类型,只会根据 BeanDefinition 或 FactoryBean#getObjectType 获取其类型。

    不使用缓存的场景如下:

    • configurationFrozen=false,表示 Spring 容器在初始化阶段,可以对 BeanDefinition 进行调整,当然缓存结果毫无意义。在 refresh 后会设置为 true,此时可以对结果进行缓存。
    • type=null ?
    • allowEagerInit=false,即不允许通过实例化 FactoryBean 来获取 Bean 类型,那就只能通过 FactoryBean 上的泛型来获取其类型了,也可能导致查询的结果不准确。

猜你喜欢

转载自www.cnblogs.com/binarylei/p/12302235.html