Spring IoC 依赖查找之类型自省
Spring IoC 依赖查找之类型自省
Spring 核心编程思想目录:https://www.cnblogs.com/binarylei/p/12290153.html
推荐文章:
- Spring Bean 实例化方法总结
- Spring Bean 类型推断:主要是分析 AbstractBeanFactory#isTypeMatch 方法。
Spring 类型自省的原则:尽可能的不要通过创建 bean 来获取其类型,除了 FactoryBean 只有通过创建对象调用其 getObjectType 方法,但也只是部分创建该 FactoryBean(所谓的部分创建是指只实例化对象,而不进行属性注入和初始化过程)。
1. Spring Bean 五种实例方式
Spring 类型自省和 bean 的创建方式密切相关,而 Spring 要兼容如此多的创建方式,也导致了代码看起来就有点复杂。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;
}
说明: 代码很长,但逻辑却很简单。
-
首先,根据 Bean 类型查找 Spring IoC 容器中所有符合条件的 Bean 名称,可能有多个。要注意的是,getBeanNamesForType 只会读取 BeanDefinition 信息或部分实例化 FactoryBean 来获取 Bean 的类型,但还没有实例化 Bean。
-
如果容器中只注册了一个这种类型的 Bean,没什么可说的,直接实例化该 Bean 后返回即可。
-
问题是,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。
-
-
根据类型查找,最复杂的两步:
- 根据类型查找所有符合条件的 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。
@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 方法。
-
参数 type 表示要查找的类型,includeNonSingletons 表示是否包含非单例 Bean,allowEagerInit 表示是否提前部分初始化 FactoryBean(为什么说是部分初始化?是因为只实例化 FactoryBean,而不进行属性注入)。
-
是否将查询的结果缓存起来,个人认为原因是 getBeanNamesForType 方法根据类型查找匹配的 beanNames 结果并不十分准确。原因很简单,因为该方法不会通过提前实例化 Bean 的方式获取其类型,只会根据 BeanDefinition 或 FactoryBean#getObjectType 获取其类型。如果不实例化对象,有些场景可能并不能获取对象的类型。
不使用缓存的场景如下:
- configurationFrozen=false,表示 Spring 容器在初始化阶段,可以对 BeanDefinition 进行调整,当然缓存结果毫无意义。在 refresh 后会设置为 true,此时可以对结果进行缓存。
- type=null ?
- allowEagerInit=false,即不允许通过实例化 FactoryBean 来获取 Bean 类型,那就只能通过 FactoryBean 上的泛型来获取其类型了,也可能导致查询的结果不准确。
3. Spring 类型自省源码分析
getBeanNamesForType
:查找容器中所以类型匹配的 beanNames,包括托管 Bean。该方法不会实例 Bean,通过 BeanDefinition 或 FactoryBean 获取其类型。predictBeanType
:从 BeanDefinition 获取原始类型,不处理 FacoryBean。getType
:查询 beanName 的类型,包括托管 Bean。先从缓存的单例中获取对象类型,再通过 predictBeanType 获取 Bean 类型后,处理 FacoryBean。同时也会处理父容器的情况。isTypeMatch
:处理流程基本同 getType 方法。getTypeForFactoryMethod
:提取工厂方法的返回值类型,包括泛型处理,静态工厂和实例工厂等。getTypeForFactoryBean
:从 FactoryBean 中提取对象类型。getTypeForFactoryBeanFromMethod
:提取工厂方法返回值 FactoryBean 上的泛型类型。这个方法功能类似于 getTypeForFactoryMethod,只不过 getTypeForFactoryBeanFromMethod 创建的对象只可能为 FactoryBean。同样也会出现匹配多个工厂方法的情况,提取公共类型即可。
之前已经分析了,Spring 三种类型查找 API 底层都是先调用 getBeanNamesForType 匹配符合类型的 beanNames,下面我们就从 doGetBeanNamesForType 开始一探究竟。
3.1 getBeanNamesForType
Spring 类型匹配规则有以下几个:
- 注册的 BeanDefinition:通过 registerBeanDefinition 方法将 BeanDefinition 注册到容器中,大多数情况
- isTypeMatch(beanName, type):匹配 Bean,如果是 FactoryBean 会部分初始化来获取其类型。
- isTypeMatch(&beanName, type):匹配 FactoryBean。
- 托管的 Bean:通过 registerSingleton 方法直接注册到 manualSingletonNames 集合中,极少数
- isTypeMatch(beanName, type)
- isTypeMatch(&beanName, type)
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
List<String> result = new ArrayList<>();
// 1. Check all bean definitions.
for (String beanName : this.beanDefinitionNames) {
if (!isAlias(beanName)) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 1.1 校验 BeanDefinition
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
// 1.2 匹配beanName
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
isTypeMatch(beanName, type);
// 1.3 没有匹配成功,继续匹配&beanName
if (!matchFound && isFactoryBean) {
beanName = FACTORY_BEAN_PREFIX + beanName;
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}
if (matchFound) {
result.add(beanName);
}
}
}
}
// 2. Check manually registered singletons too.
for (String beanName : this.manualSingletonNames) {
if (isFactoryBean(beanName)) {
// 2.1 匹配beanName
if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) {
result.add(beanName);
continue;
}
// 2.2 没有匹配成功,继续匹配&beanName
beanName = FACTORY_BEAN_PREFIX + beanName;
}
if (isTypeMatch(beanName, type)) {
result.add(beanName);
}
}
return StringUtils.toStringArray(result);
}
说明: doGetBeanNamesForType 方法整体逻辑十分清晰,先查找 Spring 中注册的 BeanDefinition,再查找 Spring 托管的 Bean(即通过 registerSingleton 方法注册)。每种情况都先匹配 Bean,再匹配 FactoryBean。
doGetBeanNamesForType 方法难点有以下几个,下面会一一进行讲解:
- 如何校验 BeanDefinition?
- 如何匹配 beanName?
- 如何判断 beanName 是否是 FactoryBean?
- isTypeMatch 方法是如何里获取类型的?
(1)如何校验 BeanDefinition
首先,Spring 如何判断能否从 BeanDefinition 获取其类型?
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
...
}
下面我们对上述的表达式进行分析。通常情况下该表达式都会返回 true,一般只有 bean 的工厂为 FactoryBean 且未初始化时,返回 false。
-
条件1:不能是抽象类,这点显而易见。
-
条件2:allowEagerInit=true 表示可以提前初始化 FactoryBean。因为 FactoryBean 只有初始化才能通过 getObjectType 获取其类型。当然还有一种兜底的方案是 FactoryBean 上的泛型。
-
条件3:判断 JVM 是否允许提前加载 mdb.beanClass,因为获取 Bean 类型时会将该类加载到 JVM 中。默认情况下永远为 true。
mdb.beanClass
已经解析完成,这毫无疑问可以获取 bean 类型。mbd.lazyInit=false
非懒加载 bean 时解析 Bean 类型,因为此时需要在 JVM 上加载 mdb.className 为 mdb.beanClass。默认为 false,也就是可以加载 Bean 类型。allowEagerClassLoading=true
表示强制加载 mdb.className,即使 mbd.lazyInit=true。Spring 容器 allowEagerClassLoading 默认为 true,而且目前为至,没有发现 Spring 的上下文将其修改为 false 的。也就是这一项条件判断永远为 true。
-
条件4:requiresEagerInitForType 方法通常情况下返回 false,只有当工厂类为 FactoryBean 时且未初始化时才返回 true 时。如果为 true 则表示 FactoryBean 需要提前初始化,即必须允许提前初始化,也就是说 allowEagerInit=true。
// ① mbd 存在 factoryBeanName 工厂类 // ② factoryBeanName 为 FactoryBean // ③ FactoryBean 未初始化。 private boolean requiresEagerInitForType(@Nullable String factoryBeanName) { return (factoryBeanName != null && isFactoryBean(factoryBeanName) && !containsSingleton(factoryBeanName)); }
(2)如何匹配 beanName
Spring 调用 isTypeMatch 判断 beanName 的类型,但在正式调用 isTypeMatch 还做了很多判断。
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
isTypeMatch(beanName, type);
首先要对 isTypeMatch 方法有一定的了解,isTypeMatch 获取 Bean 类型时,如果发现是 FactoryBean,会提前部分初始化 FactoryBean,所以判断前需要判断能不能调用该方法。
- 条件1:判断能不能提前初始化 FactoryBean。
allowEagerInit=true
允许提前部分初始化,直接过。不管 FactoryBean 是否已经初始化,也不管是否懒加载,是否允许提前初始化,都可以初始化 FactoryBean。isFactoryBean=false
不是 FactoryBean 也直接过。- 此时一定是 FactoryBean,则需要进行一步判断:要么是非懒加载,要么已经初始化完成。
- 条件2:是否查找非单例 Bean,这个很简单就不多说了。
- 条件3:isTypeMatch 是 Spring 中真正获取 Bean 类型的方法。
(3)如何判断 beanName 是否是 FactoryBean
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
return (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
}
说明: predictBeanType 方法是 Spring 中从 BeanDefinition 中获取 Bean 类型的方法。下面会重点讲解一下这个方法。
3.2 predictBeanType
predictBeanType 方法功能是 Spring 中从 BeanDefinition 中提取 Bean 类型的底层 API。predictBeanType 有两个重写方法:
-
AbstractBeanFactory 类中:调用 resolveBeanClass 方法
- resolveBeanClass:直接加载 mbd.beanClassName,并缓存为 mbd.beanClass。
-
AbstractAutowireCapableBeanFactory 类中:在 AbstractBeanFactory 类的基础进行了扩展
-
处理工厂方法 factoryMethodName 属性:包括静态工厂和实例工厂方法,通过方法的返回值类型获取 Bean 类型。
-
resolveBeanClass:直接加载 mbd.beanClassName。
-
SmartInstantiationAwareBeanPostProcessor#predictBeanType:后置处理器,用于扩展。
-
@Override
protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
Class<?> targetType = determineTargetType(beanName, mbd, typesToMatch);
// ... SmartInstantiationAwareBeanPostProcessor扩展
return targetType;
}
protected Class<?> determineTargetType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
Class<?> targetType = mbd.getTargetType();
if (targetType == null) {
targetType = (mbd.getFactoryMethodName() != null ?
getTypeForFactoryMethod(beanName, mbd, typesToMatch) :
resolveBeanClass(mbd, beanName, typesToMatch));
if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) {
mbd.resolvedTargetType = targetType;
}
}
return targetType;
}
说明: resolveBeanClass 很简单。getTypeForFactoryMethod 提取工厂方法的返回值类型,包括泛型处理。代码看上去非常复杂,但无论怎么复杂都是首先获取工厂类 factoryClass,再匹配工厂类中的工厂方法 factoryMethodName,获取工厂方法 Method,最后解析工厂方法的返回值类型。在后面会专门分析其源码。
3.3 getType
上文中已经分析了 predictBeanType 方法,predictBeanType 已经可以从 BeanDefinition 中提取 Bean 类型。但返回的该类型可能不是我们想要的,比如说返回的是 FactoryBean,我们需要的却是 Bean 的类型,还比如说从父容器中获取 Bean 类型,总之 getType 帮助我们解决了这些问题。
getType 在 predictBeanType 的基础上进行了扩展:
- 如果该 Bean 已经实例化,则直接通过实例对象获取对象类型。这样不仅可以对通过 BeanDefinition 创建的单例 Bean 进行类型查询,也可以对托管的 Bean 进行类型查询。对于 FactoryBean 需要特殊处理一下。
- 如果本地容器不包含 beanName 的定义,则尝试从父容器中获取对象类型。
- 调用 predictBeanType 获取对象类型。
- 如果返回的 FactoryBean 同样也需要特殊处理一下。
@Override
public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
// 1. 直接根据实例对象提取 Bean 类型
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
return getTypeForFactoryBean((FactoryBean<?>) beanInstance);
} else {
return beanInstance.getClass();
}
}
// 2. 子容器没有定义该beanName,从容器中提取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
return parentBeanFactory.getType(originalBeanName(name));
}
// 3. 现在开始从BeanDefinition中提取
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 4. 暂时未知
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd);
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
return targetClass;
}
}
// 5. predictBeanType方法从BeanDefinition中提取beanClass
Class<?> beanClass = predictBeanType(beanName, mbd);
// 6. 对结果进行处理,FacotoryBean
if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) {
// 6.1 实际查询的就是 Bean 类型,但实例类型是FactoryBean
if (!BeanFactoryUtils.isFactoryDereference(name)) {
return getTypeForFactoryBean(beanName, mbd);
// 6.2 实际查询的就是 FactoryBean 类型
} else {
return beanClass;
}
} else {
// 6.3 实际查询的就是 Bean 类型
return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null);
}
}
说明: 这一段代码在 predictBeanType 基础上,主要增强了三点功能:一是从实例中提取类型,二是从父类中提取类型,三是对结果类型 FactoryBean 进行处理。
思考:实例的自省类型和 BeanDefinition 的自省类型总是一致的吗?
答案是否定的,Spring 中大量使用的 CGLIB 或 JDK 动态代理,实例类很可能是代理类。看下面的这个小案例:
@EnableAsync
public class GetTypeTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(GetTypeTest.class);
Assert.assertEquals(BeanA.class, context.getType("beanA"));
}
@Bean
@Lazy
public BeanA beanA() {
return new BeanA();
}
private class BeanA {
@Async
public String hello() {
return "hello, binarylei";
}
}
}
结果说明:如果在 beanA 上添加 @Lazy 注解,则正常;如果删除 beanA 上的注解,则抛出异常。也就是前后两次调用 context.getType("beanA") 查询是类型居然会不一致。为什么呢?
说明: 其实原因很简单,标注 @Lazy 注解是从 BeanDefinition 进行自省类型,而不标注 @Lazy 注解是从实例上进行自省类型,两者类型自省的方式不同,导致结果不同。这种情况不仅仅是 getType 方法独有,其实在所有类似的处理(实例 -> BeanDefinition) 的方法都会有这类问题,如下面将要介绍的 isTypeMatch 方法。
补充说明: @Async 注解标注的类会 AsyncAnnotationBeanPostProcessor 处理后进行代理,也就是容器中的实例其实是 CGLIB 代理后的类。
3.4 isTypeMatch
最后,我们分析一下 isTypeMatch 这个方法,逻辑基本上和 getType 个致。
- 先直接通过实例对象获取对象类型,这样不仅可以对通过 BeanDefinition 创建的单例 Bean 进行类型查询,也可以对托管的 Bean 进行类型查询。同样对 FactoryBean 需要特殊处理一下。
- 如果本地容器不包含 beanName 的定义,则尝试从父容器中获取对象类型。
- 调用 predictBeanType 获取对象类型。
- 如果返回的 FactoryBean 同样也需要特殊处理,还有泛型和非泛型的匹配。
@Override
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
// 1. 根据实例化后的对象获取 bean 的类型,注意 FactoryBean 的处理即可
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
// 1.1 如果是 FactoryBean 类型,此时需要判断是否要查找的就是这个工厂对象,判断 beanName 是否是以 & 开头
// 如果是其创建的类型,则需要调用 getTypeForFactoryBean 从这个 FactoryBean 实例中获取真实类型
if (beanInstance instanceof FactoryBean) {
if (!BeanFactoryUtils.isFactoryDereference(name)) {
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
return (type != null && typeToMatch.isAssignableFrom(type));
} else {
return typeToMatch.isInstance(beanInstance);
}
// 1.2 如果是普通 bean 则可直接判断类型,当然 Spring 还考虑的泛型的情况
} else if (!BeanFactoryUtils.isFactoryDereference(name)) {
if (typeToMatch.isInstance(beanInstance)) {
return true;
} else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
// AOP 有关 ????
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class<?> targetType = mbd.getTargetType();
if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance) &&
typeToMatch.isAssignableFrom(targetType)) {
Class<?> classToMatch = typeToMatch.resolve();
return (classToMatch == null || classToMatch.isInstance(beanInstance));
}
}
}
return false;
} else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
// null instance registered
return false;
}
// 2. 父工厂,没什么好说的
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch);
}
// 下面就要从 bean 的定义中获取该 bean 的类型了
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class<?> classToMatch = typeToMatch.resolve();
if (classToMatch == null) {
classToMatch = FactoryBean.class;
}
Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});
// AOP 代理时会将原始的 BeanDefinition 存放到 decoratedDefinition 属性中,可以行忽略这部分
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
return typeToMatch.isAssignableFrom(targetClass);
}
}
// 3. predictBeanType 推断 beanName 的类型,主要的逻辑
Class<?> beanType = predictBeanType(beanName, mbd, typesToMatch);
if (beanType == null) {
return false;
}
// 4. 结果处理:FactoryBean
// 4.1 查询Bean类型,需要调用getTypeForFactoryBean方法从FactoryBean中获取Bean类型
if (FactoryBean.class.isAssignableFrom(beanType)) {
if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
// 此时需要从 FactoryBean 中推断出真实类型
beanType = getTypeForFactoryBean(beanName, mbd);
if (beanType == null) {
return false;
}
}
// 4.2 查询FactoryBean类型
} else if (BeanFactoryUtils.isFactoryDereference(name)) {
beanType = predictBeanType(beanName, mbd, FactoryBean.class);
if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) {
return false;
}
}
// 4.3 普通类型处理,两种情况分别处理:有泛型和无泛型
// mbd.resolvedTargetType 在方法determineTargetType中解析后缓存
// mbd.targetType 什么时候解析的?好像基本都为空
ResolvableType resolvableType = mbd.targetType;
// mbd.factoryMethodReturnType 在方法getTypeForFactoryMethod解析后缓存的
if (resolvableType == null) {
resolvableType = mbd.factoryMethodReturnType;
}
// 4.3.2 泛型匹配,使用resolvableType匹配
if (resolvableType != null && resolvableType.resolve() == beanType) {
return typeToMatch.isAssignableFrom(resolvableType);
}
// 4.3.3 无泛型匹配,直接使用beanType匹配
return typeToMatch.isAssignableFrom(beanType);
}
说明: isTypeMatch 和 getType 的逻辑基本上完全一致,可以将这两个方法对比起来看。
- Spring 先直接从缓存中获取这个 bean 实例,再根据对象推断其类型。如果 bean 还没有创建或者干脆就不是单例,则只能根据定义这个 bean 的 BeanDefinition 获取了。
- 先根据 BeanDefinition 获取其当前 bean 的类型,Spring 全部委托给了 predictBeanType(beanName, mbd, typesToMatch) 方法。
- 如果当前 bean 是 FactoryBean,则还需要进一步获取其真实类型。这个过程由 getTypeForFactoryBean 方法完成,这个方法有两个重载的方法,即可以直接通过实例对象获取,也可以通过定义的 BeanDefinition 获取对象类型。
最后关于 Spring 内部几个 getType* 功能总结如下:
- getTypeForFactoryMethod:提取工厂方法的返回值类型,包括泛型处理,静态工厂和实例工厂等。在 predictBeanType 中已经进行过源码分析。
- getTypeForFactoryBean:从 FactoryBean 中提取对象类型。
- getTypeForFactoryBeanFromMethod:提取工厂方法返回值 FactoryBean 上的泛型类型。这个方法功能类似于 getTypeForFactoryMethod,只不过 getTypeForFactoryBeanFromMethod 创建的对象只可能为 FactoryBean。同样也会出现匹配多个工厂方法的情况,提取公共类型即可。
3.5 getTypeForFactoryMethod
protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
// 1. 查找缓存
ResolvableType cachedReturnType = mbd.factoryMethodReturnType;
// 2. 查找工厂类 factoryClass
Class<?> factoryClass;
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
factoryClass = getType(factoryBeanName);
isStatic = false;
} else {
factoryClass = resolveBeanClass(mbd, beanName, typesToMatch);
}
// 3. 匹配 Method,包括父类方法
Class<?> commonType = null;
Method uniqueCandidate = null;
Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(
factoryClass, ReflectionUtils::getUniqueDeclaredMethods);
for (Method candidate : candidates) {
// 匹配成功 static、factoryMethodName、parameterCount
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
candidate.getParameterCount() >= minNrOfArgs) {
// 根据方法返回值类型,注意泛型解析
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
candidate, args, getBeanClassLoader());
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
candidate : null);
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
if (commonType == null) {
return null;
}
}
}
// 4. 返回Method工厂方法返回值类型
cachedReturnType = (uniqueCandidate != null ?
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
mbd.factoryMethodReturnType = cachedReturnType;
return cachedReturnType.resolve();
}
说明: 我们来理一下从工厂方法获取 Bean 类型的思路。
-
获取工厂类 factoryClass:分两种情况,静态工厂和实例工厂。
- 静态工厂:没有设置 factoryBeanName 属性,将当前类的 beanClassName 作为 factoryClass。
- 实例工厂:设置了 factoryBeanName 属性,通过 factoryBeanName 获取工厂类 factoryClass。
-
方法匹配 beanFactoryMethod:包括父类,方法匹配分三步,一是 static 匹配,二是方法名称匹配,三是参数个数匹配(不匹配具体的参数类型)。
-
为什么 Spring 只匹配参数个数,只要 "工厂方法实际参数个数" >= "beanDefinition 中参数个数" 即可?
如果参数个数不够,可以从 Spring 容器中获取对象进行依赖注入。如此匹配,就可能会匹配多个候选工厂。
-
如果匹配后有多个候选的工厂方法怎么办?
Spring 的解决方案是,如果只有一个候选工厂方法,直接返回该方法的返回值类型。如果有多个候选工厂方法,返回所有候选工厂方法的返回值类型的公共类型。如果没有公共类型,则返回 null。
但大家想一下,类型查找可以取公共类型作为 Bean 的类型,实例化时到底取那一个候选工厂呢?个人猜测可能是取方法参数最多的那个工厂方法?
3.6 getTypeForFactoryBean
getTypeForFactoryBean 方法功能:从 FactoryBean 中提取对象类型,包括 getObjectType 和泛型提取。
- FactoryBeanRegistrySupport:直接调用 factoryBean#getObjectType() 提取对象类型。
- AbstractBeanFactory:先实例化 FactoryBean,再调用 FactoryBeanRegistrySupport 的重载方法获取对象类型。
- AbstractAutowireCapableBeanFactory:在 AbstractBeanFactory 的基础上,主要是增加了对 FactoryBean 上泛型的处理。
前两个重载的 getTypeForFactoryBean 方法都比较简单,我们主要看一下 AbstractAutowireCapableBeanFactory 的这个重载的方法。
@Override
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
// 1. 处理 mbd.getInstanceSupplier(),另外一种创建 bean 的方式,@Sping 5.0 提供,我们先忽略...
// if (mbd.getInstanceSupplier() != null) ...
// 2. 这个 FactoryBean 也可能是其他的工厂的工厂方法创建的
String factoryBeanName = mbd.getFactoryBeanName();
String factoryMethodName = mbd.getFactoryMethodName();
// 2. 实例工厂创建FactoryBean<User> `public FactoryBean<User> createUser() { ... }`
// 此时我们不想调用实例工厂来创建这个FactoryBean,因为这需要实例化factoryBeanName对象
// 如果factoryBeanName对象已经实例了,当然我们也可以部分实例化FactoryBean
// 如果没有实例化,大家都后退一步,何不直接解析FactoryBean上的泛型呢?当然也可能没有定义泛型,则返回null
if (factoryBeanName != null) {
if (factoryMethodName != null) {
BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
if (fbDef instanceof AbstractBeanDefinition) {
AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef;
if (afbDef.hasBeanClass()) {
// 2.1 直接解析factoryMethodName方法返回值上 FactoryBean 的泛型
Class<?> result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName);
if (result != null) {
return result;
}
}
}
}
// 2.2 当然,如果factoryBeanName已经实例化,那当然就可以直接调用工厂方法创建这个FactoryBean
// 如果 factoryBeanName 没有创建则直接返回null,因为我们不想嵌套创建bean
if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
return null;
}
}
// 3. 现在我们要先部分实例化这个 FactoryBean,调用其 getObjectType() 来获取对象类型
FactoryBean<?> fb = (mbd.isSingleton() ?
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
if (fb != null) {
// 4.1 调用 FactoryBean#getObjectType() 获取类型
Class<?> result = getTypeForFactoryBean(fb);
if (result != null) {
return result;
// 4.2 如果部分实例化还是获取不到,没办法了只有将这个 FactoryBean 全部实例化出来了
} else {
return super.getTypeForFactoryBean(beanName, mbd);
}
}
// 4. 出现FactoryBean循环创建,此时FactoryBean正在创建,也不能获取其实例对象
if (factoryBeanName == null && mbd.hasBeanClass()) {
// 4.1 解析方法的返回值的泛型类型,FactoryBean<User>
if (factoryMethodName != null) {
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
// 4.2 该类就是一个 FactoryBean,则直接解析其泛型就好 FactoryBean<User>
} else {
return GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
}
}
return null;
}
说明: getTypeForFactoryBean 是从 FactoryBean 中提取对象类型,但 FactoryBean 的来源比较多,基本上常见的一种创建 Bean 的方式都可以创建 FactoryBean。我们一起整理一下:
-
mbd.instanceSupplier:这是 Spring 5.0+ 后提供的一种 Bean 的创建方式,我们先忽略...
-
实例工厂创建:Spring 首先解析其工厂方法返回值的 FactoryBean 上的泛型,如果有则直接返回。
如果无法解析到对象类型,那问题就来了,为了获取 FactoryBean,Spring 会先创建 factoryBeanName 吗?答案是否定的,Spring 不会为了获取对象类型而循环创建另外一个对象实例,因为这会提前初始化对象,造成更严重的 Bug。当然,如果这个 factoryBeanName 在 Spring 容器中已经实例化了,那就另说了。
public FactoryBean<User> createUser() { ... }
-
现在,我们可以部分实例化 FactoryBean,调用 FactoryBean#getObjectType 获取对象类型。如果还是无法获取对象类型,Spring 会完全实例化 FactoryBean,这部分代码是在 AbstractBeanFactory 中完成的。
-
如果不能获取提前初始化的 FactoryBean,此时只能通过解析 FactoryBean 的泛型。也分种情况:一是工厂方法创建的 FactoryBean,二是正常注册的 FactoryBean。如果是工厂方法创建的 FactoryBean,同样也会出现匹配多个方法的可能,也是提取公共类型。
3.7 getTypeForFactoryBeanFromMethod
提取工厂方法返回值 FactoryBean 上的泛型类型。这个方法功能类似于 getTypeForFactoryMethod,只不过 getTypeForFactoryBeanFromMethod 创建的对象只可能为 FactoryBean。同样也会出现匹配多个工厂方法的情况,提取公共类型即可。
每天用心记录一点点。内容也许不重要,但习惯很重要!
posted on 2020-02-13 09:17 binarylei 阅读(1987) 评论(1) 编辑 收藏 举报