Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析

2023-11-01

从《Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》?》可以看出,解析待注入字段或参数主要由 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set<java.lang.String>, org.springframework.beans.TypeConverter)org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName 两种方法完成注入值解析, 本文将从源码角度分析 Spring 是如何解析?

一、Type-driven

在使用 @Autowired 注解或者 @Resource 注解在某些情况下,Spring 会将待解析字段或参数封装成 DependencyDescriptor 调用第一种解析方法,看下该方法源码,

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	
	// ...

	@Override
	@Nullable
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	
		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
		// 待注入变量类型为Optional
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		// 待注入变量类型为ObjectFactory|ObjectProvider, 延迟注入
		else if (ObjectFactory.class == descriptor.getDependencyType() ||
				ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		// 待注入变量类型为javax.inject.Provider类型, 延迟注入
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
		}
		else {
			// @Lazy延迟注入
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			if (result == null) {
				// 实际依赖解析
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

	/** Create an {@link Optional} wrapper for the specified dependency. */
	private Optional<?> createOptionalDependency(
			DependencyDescriptor descriptor, @Nullable String beanName, final Object... args) {

		DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) {
			@Override
			public boolean isRequired() {
				return false;
			}
			@Override
			public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
				return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
						super.resolveCandidate(beanName, requiredType, beanFactory));
			}
		};
		// 实际解析
		Object result = doResolveDependency(descriptorToUse, beanName, null, null);
		return (result instanceof Optional ? (Optional<?>) result : Optional.ofNullable(result));
	}

	// ...

}

1. 延迟注入

这里主要根据待注入变量类型,处理依赖延迟注入情况,

  • Optional, 立即解析,并将解析结果包装为 Optional
  • ObjectFactory | ObjectProvider,返回实现类 DependencyObjectProvider 对象,并将依赖解析逻辑封装在其获取值相关 API 中,如org.springframework.beans.factory.support.DefaultListableBeanFactory.DependencyObjectProvider#getObject()org.springframework.beans.factory.support.DefaultListableBeanFactory.DependencyObjectProvider#stream 等,这样,在调用这些 API 获取实际值时,才发生依赖解析;
  • javax.inject.Provider,原理同上,实现方式不同,该类为 Jsr330 类,非 Spring 原生类;
  • 待注入变量被 @Lazy 注解,为该变量创建代理,在调用该变量任意方法时才调用org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 解析依赖值,然后调用目标对象相关方法,参考org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy

2. 依赖解析

如果非以上情形,则需要调用 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 方法立即解析待注入变量值,

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
	try {
		// 处理缓存
		// AutowiredAnnotationBeanPostProcessor在满足条件情况下将待注入变量解析bean名称缓存到ShortcutDependencyDescriptor
		// prototype bean再次依赖注入时, 便可根据bean name解析bean实例
		// 参考 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue
		Object shortcut = descriptor.resolveShortcut(this);
		if (shortcut != null) {
			return shortcut;
		}

		Class<?> type = descriptor.getDependencyType(); // 待注入变量类型
		// 计算@Value注解value属性,
		// 例如, 如果待注入变量有@Value("${server.port}")注解, 则value值为"${server.port}"
		Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
		if (value != null) {
			if (value instanceof String) {
				// 解析"${}"占位符
				// 最终由org.springframework.core.env.PropertyResolver#resolvePlaceholders处理
				// 参考https://blog.csdn.net/u010141779/article/details/120747337
				String strVal = resolveEmbeddedValue((String) value);
				BeanDefinition bd = (beanName != null && containsBean(beanName) ?
						getMergedBeanDefinition(beanName) : null);
				value = evaluateBeanDefinitionString(strVal, bd); // 计算SpEL表达式
			}
			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()));
			}
		}

		// 当待注入变量类行为 Array | Collection | Map 或者ObjectProvider调用流相关方法或者依赖值时, 此方法生效
		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
		if (multipleBeans != null) {
			return multipleBeans;
		}

		// 找出所有和type即待注入变量类型相同候选bean, bean name => bean
		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) {
			if (isRequired(descriptor)) { 
			    // 如果此变量必须(@Autowired注解required属性为tru), 但不存在候选bean, 则抛异常
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			return null;
		}

		String autowiredBeanName;
		Object instanceCandidate;

		if (matchingBeans.size() > 1) {
			// bean @Primary 注解,优先级,bean名称是否与待注入变量名称相同等条件筛选出唯一一个
			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) {
			// 根据bean名称最终调用org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法获取bean实例
			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);
	}
}

解析待注入变量逻辑大致分如下几步,

  • 调用 org.springframework.beans.factory.config.DependencyDescriptor#resolveShortcut 以快捷方式解析,对该方法提供实现只有 ShortcutDependencyDescriptor。可以看出 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 根据类型解析比较耗时,需要找出所有类型匹配 bean,再根据一些条件筛选,所以 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue 等函数中,如果满足条件,则其将解析完成的 bean 名称以及表示待注入变量的 DependencyDescriptor 封装为 ShortcutDependencyDescriptor,当 prototype bean再次依赖注入时,便可直接通过 bean 名称解析;
  • 处理 @Value 占位符,其中又包括解析 ${} 占位符以及 #{} SpEL 表达式,类型转换等;
  • 调用 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveMultipleBeans 处理待注入变量为复合类型,包括 Array | Collection | Map 或者 ObjectProvider,复合类型候选 bean 有多个时直接组装返回;
  • 通过 org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates,首先找到所有和待注入变量类型匹配 bean,然后再根据 bean autowireCandidate 属性,@Qualifier 注解等条件筛选出候选 bean;
  • 如果候选 bean 存在多个,则通过 org.springframework.beans.factory.support.DefaultListableBeanFactory#determineAutowireCandidate 函数根据 bean @Primary 注解,优先级,是否与待注入变量名称相同等条件筛选出唯一一个;

3. 查找候选 bean

其中比较核心代码在函数 org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates 查找所有候选 bean,看下源码,

protected Map<String, Object> findAutowireCandidates(
			@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

	// 从当前容器及其祖先容器中查找type为requiredType即待注入变量实际类型的bean名称
	String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
			this, requiredType, true, descriptor.isEager());
	Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
	// resolvableDependencies一些特殊bean无法从IOC容器中获取, 需要直接判断类型是否匹配
	// 比如, IOC容器本身, org.springframework.beans.factory.BeanFactory
	// 应用上下文,org.springframework.context.ApplicationContext 等
	for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
		Class<?> autowiringType = classObjectEntry.getKey();
		if (autowiringType.isAssignableFrom(requiredType)) {
			Object autowiringValue = classObjectEntry.getValue();
			autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
			if (requiredType.isInstance(autowiringValue)) {
				result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
				break;
			}
		}
	}
	for (String candidate : candidateNames) {
		// isSelfReference 判断是否将bean注入到自己字段或者方法参数中
		// isAutowireCandidate 判断bean是否符合条件
		if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
			// 将candidate Calss或其实例添加到结果result中
			addCandidateEntry(result, candidate, descriptor, requiredType);
		}
	}
	if (result.isEmpty()) {
		boolean multiple = indicatesMultipleBeans(requiredType);
		// Consider fallback matches if the first pass failed to find anything...
		DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
		for (String candidate : candidateNames) {
			// 后备
			if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
					(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
				addCandidateEntry(result, candidate, descriptor, requiredType);
			}
		}
		if (result.isEmpty() && !multiple) {
			// Consider self references as a final pass...
			// but in the case of a dependency collection, not the very same bean itself.
			for (String candidate : candidateNames) {
				// 处理注入待注入变量为自身所属bean
				if (isSelfReference(beanName, candidate) &&
						(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
						isAutowireCandidate(candidate, fallbackDescriptor)) {
					addCandidateEntry(result, candidate, descriptor, requiredType);
				}
			}
		}
	}
	return result;
}
α. 候选 bean 筛选

从以上源码可以看出为什么这节标题为 “Type-driven” 类型驱动,在查找候选 bean 时,Spring 首先将容器中所有和待注入变量类型相符所有 bean 找到, 然后再调用 org.springframework.beans.factory.support.DefaultListableBeanFactory#isAutowireCandidate(java.lang.String, org.springframework.beans.factory.config.DependencyDescriptor) 筛选符合条件 bean,该方法最终由 org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#isAutowireCandidate 完成,

public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwareAutowireCandidateResolver {
	// ...
	
	// 判断bdHolder是否能注入到待注入变量descriptor
	@Override
	public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
		// 使用@Bean在Configuration中注入bean时, 可指定autowireCandidate为false
		// 表示该bean不会被注入到其他bean属性或方法参数中,在筛选候选bean时, 会忽略掉这些bean
		// 例如, @Bean(autowireCandidate = false)
		// 另外, 这个方法中还会检测泛型是否匹配
		boolean match = super.isAutowireCandidate(bdHolder, descriptor);
		if (match) {
			// descriptor.getAnnotations()取值如下
			// 如果待注入变量为字段, 则取该字段所有注解
			// 如果待注入变量为方法参数, 则取方法参数所有注解
			// checkQualifiers 主要根据@Qualifier参数筛选
			match = checkQualifiers(bdHolder, descriptor.getAnnotations());
			if (match) {
				MethodParameter methodParam = descriptor.getMethodParameter();
				if (methodParam != null) {
					Method method = methodParam.getMethod();
					// Set方法注入, 还要校验方法参数
					if (method == null || void.class == method.getReturnType()) {
						match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
					}
				}
			}
		}
		return match;
	}
	
	// ...

}
β. 根据限定符注解筛选

以上方法重中之重在 org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#checkQualifiers 检查 @Qualifier 注解, @Qualifier 注解可以限定注入 bean, 详细参考《Spring Boot v2.4.4源码解析(九)依赖注入上 —— @Autowired》。
看下该方法源码,

// annotationsToSearch =》 需要注入变量标注的所有注解
// bdHolder =》 待注入BeanDefinition
protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
	if (ObjectUtils.isEmpty(annotationsToSearch)) {
		return true;
	}
	SimpleTypeConverter typeConverter = new SimpleTypeConverter();
	for (Annotation annotation : annotationsToSearch) {
		Class<? extends Annotation> type = annotation.annotationType();
		boolean checkMeta = true;
		boolean fallbackToMeta = false;
		// 判断是否是限定符注解, 一下两种情况满足条件
		// 1. @Qualifier本身
		// 2. 被@Qualifier注解的注解, 例如, @LoadBalanced
		// @Qualifier
		// public @interface LoadBalanced {
		// }
		if (isQualifier(type)) {
			if (!checkQualifier(bdHolder, annotation, typeConverter)) {
				// fallbackToMeta 默认false, 只有不匹配才会被赋值为true
				// 所以, fallbackToMeta=true 表示有限定符注解, 但是没有匹配成功
				fallbackToMeta = true; 
			}
			else {
				checkMeta = false;  // 匹配成功, 不需要匹配元数据
			}
		}
		// checkMeta 默认为true, 没有限定符注解(isQualifier(type)返回false)或限定符注解没有匹配成功才会走到该分支
		if (checkMeta) { 
			boolean foundMeta = false;
			for (Annotation metaAnn : type.getAnnotations()) {
				Class<? extends Annotation> metaType = metaAnn.annotationType();
				if (isQualifier(metaType)) {
					foundMeta = true;
					// Only accept fallback match if @Qualifier annotation has a value...
					// Otherwise it is just a marker for a custom qualifier annotation.
					// 详细解释看后文
					if ((fallbackToMeta && ObjectUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) ||
							!checkQualifier(bdHolder, metaAnn, typeConverter)) {
						return false;
					}
				}
			}
			// 有限定符注解,没有匹配成功, 并且在元数据中没有找到限定符, 匹配失败
			if (fallbackToMeta && !foundMeta) {
				return false;
			}
		}
	}
	return true;  // 所有限定符注解匹配成功才算成功匹配
}

源码大致意思已经注释在源码中,首先解释下参数, annotationsToSearch,表示被注入变量所有注解,比如如下代码,

@Component
public class B {

    @Autowired
    @Qualifier
    private A a;

}

那么在注入字段 A a 时,annotationsToSearch 数组中包含 @Qualifier@Autowired 两个元素。
bdHolder 表示 Spring IOC 容器中类型为 A 的 BeanDefinition
这里检查 bdHolder @Qualifier 限定符注解能否和 A a 能否匹配。
方法 org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#isQualifier 用于判断注解是否为“限定符注解”,注意一定要记住这个名词 ——“限定符注解”,

protected boolean isQualifier(Class<? extends Annotation> annotationType) {
	for (Class<? extends Annotation> qualifierType : this.qualifierTypes) {
		if (annotationType.equals(qualifierType) || annotationType.isAnnotationPresent(qualifierType)) {
			return true;
		}
	}
	return false;
}

从源码可以看出如下两种情况视为限定符注解

  • @Qualifier 注解本身,称为第一种形式;
  • 注解被 @Qualifier 注解,例如 @LoadBalanced,称为第二种形式;

以上源码执行大致步骤如下,

  • 先判断限定符注解能否直接匹配;
  • 如果限定符注解能直接匹配(checkMeta = false),则不需要检查其元数据。如果注解非限定符注解(默认 checkMeta = true)或者限定符注解注解匹配失败(fallbackToMeta = true,默认 checkMeta = true)则需要进入匹配元数据中限定符注解能否匹配;
  • 循环检查注解元数据中限定符注解能否匹配,这里分两种情况 —— 如果该注解本身为限定符注解例如@Qualifier | @LoadBalanced …,只是没有匹配成功,那么只有@LoadBalanced 这种被 @Qualifier 注解的限定符注解第二种形式元数据中才包含限定符注解(必定为 @Qualifier ),此时fallbackToMeta = true(本身没有匹配成功);如果注解本身非限定符注解,如果其元数据中出现限定符注解,那么该限定符注解只能是第二种形式,否则本身就是限定符注解;
  • 所有限定符注解匹配成功才算最终匹配成功;

这里主要看下匹配不成功情况,即返回 false 情况,

  • fallbackToMeta && !foundMeta ,注解为限定符注解且未匹配成功,且注解中未找到限定符注解,说明该注解为直接@Qualifier 注解且未匹配成功;
  • fallbackToMeta && ObjectUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) || !checkQualifier(bdHolder, metaAnn, typeConverter),这里又分为两种情况,
    !checkQualifier(bdHolder, metaAnn, typeConverter) 元数据中的限定符注解匹配失败;另外一种情况比较难理解,但源码给了注释,解释如下,
    !fallbackToMeta && ObjectUtils.isEmpty(AnnotationUtils.getValue(metaAnn)))!fallbackToMeta 说明注解本身为限定符注解,那只能是第二种形式,其元数据 metaAnn 必定为 @Qualifier ,如果该注解被 @Qualifier 注解时没有指定 value 属性,那么 @Qualifier 注解只是用来标识该注解为限定符注解,不需要匹配,原来匹配不成功那就是不成功,直接短路不需要匹配元数据。如果该注解被 @Qualifier 注解时指定 value 属性,那么再匹配元数据 @Qualifier

例如,定义注解几个注解如下,

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier(value = "foo")
public @interface Genre {
    String mark() default "";
}

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier()
public @interface Level2 {
    String q();
}

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Level2(q="foo")
public @interface Level3 {}

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Level2(q="foo")
public @interface Level3Other {}

通过 Configuration 向 Spring IOC 容器中注入如下几个 bean,

@Configuration
public class Config {

    @Bean("b1")
    @Genre(mark = "bar")
    public B b() {

        return new B();
    }

    @Bean("b2")
    @Level3Other
    public B b2() {

        return new B();
    }

    @Bean("b3")
    @Qualifier(value = "foo")
    public B b3() {

        return new B();
    }

    @Bean("b4")
    @Genre
    public B b4() {

        return new B();
    }
}

bean A 中包含需要依赖注入字段 bb1,



@Component
public class A implements InitializingBean {

    // b2
    // @Level3Other和@Level3虽然非限定符注解, 但其都被限定符注解@Level2(q="foo")注解, 且属性q相同能匹配
    @Autowired
    @Level3
    private Map<String, B> b;

	// b1, b3, b4
	// b1, 虽然直接限定符注解@Genre不能匹配(属性mark不同,分别为"foo"和"bar"),
	// 但@Genre被@Qualifier注解时指定了Value @Qualifier(value = "foo"),所以后备匹配元数据@Qualifier也能匹配成功
	// b3, 同b1, b3未被@Genre注解, 所以@Genre注解必定匹配失败, 元数据@Qualifier能匹配成功
	// b4, 直接限定符注解@Genre能匹配成功
    @Autowired
    @Genre(mark = "foo")
    private Map<String, B> b1;


    @Override
    public void afterPropertiesSet() throws Exception {
    	// b2
        b.keySet().forEach(System.err::println);
        System.err.println("=================");
        // b1
		// b3
		// b4
        b1.keySet().forEach(System.err::println);
    }
}
γ. 限定符注解筛选原理

再来看下最后一个重要方法 —— org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#checkQualifier,该方法判断 BeanDefinition 能否匹配限定符注解 annotation

protected boolean checkQualifier(
		BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {

	Class<? extends Annotation> type = annotation.annotationType();
	RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
	// 可以在BeanDefinition指定该属性
	// 详细参见解释一
	AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
	if (qualifier == null) {
		qualifier = bd.getQualifier(ClassUtils.getShortName(type));
	}
	// qualifier不存在情况下, 匹配候选bean限定符注解
	if (qualifier == null) {
		// targetAnnotation表示候选bean中类型为type的限定符注解
		// 词方法是从BeanDefinition标签里拿这个类型的注解声明,非XML配置此处targetAnnotation为null
		// First, check annotation on qualified element, if any
		Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);
		// Then, check annotation on factory method, if applicable
		if (targetAnnotation == null) {
			// 通过Configuration @Bean方法注入, 则从方法注解中获取, 如上注入bean b
			targetAnnotation = getFactoryMethodAnnotation(bd, type);
		}
		if (targetAnnotation == null) {
			RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
			if (dbd != null) {
				// 从DecoratedDefinition的FactoryMethodAnnotation中获取
				targetAnnotation = getFactoryMethodAnnotation(dbd, type);
			}
		}
		if (targetAnnotation == null) {
			// Look for matching annotation on the target class
			if (getBeanFactory() != null) {
				try {
					Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
					if (beanType != null) {
						// 从bean Class中获取
						targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
					}
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Not the usual case - simply forget about the type check...
				}
			}
			if (targetAnnotation == null && bd.hasBeanClass()) {
				targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
			}
		}
		// 这里equals方法匹配两个注解中所有属性相同
		// 详细参见解释二 java.lang.annotation.Annotation#equals
		if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
			return true;
		}
	}

	Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
	if (attributes.isEmpty() && qualifier == null) {
		// If no attributes, the qualifier must be present
		return false;
	}

	// 匹配限定符注解中所有属性
	for (Map.Entry<String, Object> entry : attributes.entrySet()) {
		String attributeName = entry.getKey(); // 属性名称
		Object expectedValue = entry.getValue(); // 属性期望值, 即待注入变量该属性值
		Object actualValue = null; // 属性期望值
		// Check qualifier first
		if (qualifier != null) {
			actualValue = qualifier.getAttribute(attributeName);
		}
		if (actualValue == null) {
			// Fall back on bean definition attribute
			actualValue = bd.getAttribute(attributeName);
		}
		// 限定符注解value属性可以匹配bean名称
		// 这也是最常见使用方式 —— 直接在@Qualifier直接value属性中指定候选bean名称
		// 例如 @Qualifier(value = "foo"), 表示需要注入名称为foo的bean
		if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
				expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
			// Fall back on bean name (or alias) match
			continue;
		}
		if (actualValue == null && qualifier != null) {
			// Fall back on default, but only if the qualifier is present
			actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
		}
		if (actualValue != null) {
			actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
		}
		if (!expectedValue.equals(actualValue)) {
			return false;
		}
	}
	return true;
}

解释一,AutowireCandidateQualifier
该类可以使用非注解方式指定可以匹配的限定符注解,例如,存在如下限定符注解,

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier()
public @interface CustomizedQualifier {
    String property() default "";
}

向Spring IOC 容器注册如下 bean b,

@Configuration
public class Config {
    @Bean("b")
    public B b(){ return new B();}
}

bean a 中字段 b 使用限定符注解 CustomizedQualifier 依赖 bean b 时将依赖不到,因为 bean b 没有使用该限定符注解,故不能匹配成功,

@Component
public class A implements InitializingBean {

	// n=null, 容器中没有能匹配 @CustomizedQualifier(property = "foo") 且类型为 B 的 bean
    @Autowired(required = false)
    @CustomizedQualifier(property = "foo")
    private Map<String, B> b;

    @Override
    public void afterPropertiesSet() throws Exception {
    	// java.lang.NullPointerException
        b.keySet().forEach(System.err::println);
    }
}

这时,通过 org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory 修改 bean b BeanDefinition —— 向其添加一个表示 @CustomizedQualifierAutowireCandidateQualifier ,如下

@Component
public class QualifierBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        BeanDefinition b = beanFactory.getBeanDefinition("b");
        if (b instanceof AbstractBeanDefinition) {
            AbstractBeanDefinition abd = (AbstractBeanDefinition) b;
            // 新建表示CustomizedQualifier的AutowireCandidateQualifier
            AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(CustomizedQualifier.class);
            // 限定符注解属性也要匹配成功
            qualifier.setAttribute("property", "foo");
            abd.addQualifier(qualifier);
        }
    }
}

此时bean a 中字段 b 就能成功依赖到 bean b。

解释二 java.lang.annotation.Annotation#equals
对于注解, Java 会为其生成代理 AnnotationInvocationHandler,在 sun.reflect.annotation.AnnotationInvocationHandler#invoke 中会拦截 equals() 方法,

public Object invoke(Object var1, Method var2, Object[] var3) {
	String var4 = var2.getName();
		Class[] var5 = var2.getParameterTypes();
	if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { // 拦截equals()方法
		return this.equalsImpl(var3[0]);
	} else if (var5.length != 0) {
		throw new AssertionError("Too many parameters for an annotation method");
	} 
	// ...
}

看下 this.equalsImpl() 对拦截的 equals() 方法做了什么,

private Boolean equalsImpl(Object var1) {
    if(var1 == this) { 	//本身和本身equals必定为true
        return true;
    } else if (!this.type.isInstance(var1)) {  // 类型不匹配
        return false;
    } else {
	    // 注解中属性通过定义方法表示, 例如, String value() default ""; 表示value属性
	    // 获取所有成员方法, 相当于获取所有属性
        Method[] var2 = this.getMemberMethods();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Method var5 = var2[var4];
            String var6 = var5.getName(); // 属性名称
            Object var7 = this.memberValues.get(var6); // 本省该属性值 
            Object var8 = null;
            AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
            if (var9 != null) {
                var8 = var9.memberValues.get(var6);
            } else {
                try {
                    var8 = var5.invoke(var1); // 比较对象该属性值
                } catch (InvocationTargetException var11) {
                    return false;
                } catch (IllegalAccessException var12) {
                    throw new AssertionError(var12);
                }
            }
            // 如果var7不为数组, 则直接调用该对象equals()方法比较
            // 否则, 调用Arrays相关数组比较方法比较两个数组是否相等
            if (!memberValueEquals(var7, var8)) { // 判断该属性值是否相等
                return false;
            }
        }

        return true; // 所有属性值相等才算两个注解equals()为true
    }
}

从上面源码可以看出 —— java.lang.annotation.Annotation#equals 方法处理逻辑是,该注解所有属性 equals() 方法返回 true,结果才为 true
例如定义注解,

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Eq {
    String name() default "";
    String key() default "";
    String value() default "";
}

ABC 分别有该注解,

@Eq(name = "a", key = "keyA", value = "q")
public class B { }

@Eq(name = "a", key = "keyA", value = "1q")
public class C { }

@Eq(name = "a", key = "keyA", value = "1q")
public class A {

    public static void main(String[] args) {

        Eq annotationA = A.class.getAnnotation(Eq.class);
        Eq annotationB = B.class.getAnnotation(Eq.class);
        Eq annotationC = C.class.getAnnotation(Eq.class);

		// false, 属性value不匹配
        System.err.println(annotationA.equals(annotationB));
        // true, 所有属性均匹配
        System.err.println(annotationA.equals(annotationC));
    }

}

回到 checkQualifier函数,看下该函数如何认定一个 BeanDefinition 能匹配待注入变量限定符注解,

  • 首先根据限定符注解类型全限定名和短名称从 BeanDefinition 中获取 AutowireCandidateQualifier
  • 如果步骤一未获取到 AutowireCandidateQualifier,则尝试获取该类型限定符注解并和 annotation匹配一波(需要两个注解所有属性equals() 方法返回 true ,才算匹配成功,详细参考以上解释二),匹配成功,提前结束。获取同类型限定符注解方式为,如果该 bean 通过 Configuration 配置类 @Bean 方法注入,则从该方法注解中获取,获取失败,则从该 bean Class 文件中中获取。这里有一个小问题 —— 如果 @Bean 方法和 Class 文件中都有该限定符注解呢?从源码可以看出最终以 @Bean 方法为准,Class 文件中并不会生效;
  • 匹配限定符注解中所有属性,这些属性值可能来源于, AutowireCandidateQualifier | BeanDefinition | bean 名称(属性 value 在前两种来源获取不到时才会取 bean 名称),默认值(AutowireCandidateQualifier ) 需存在;
  • 所有属性值匹配成功,返回 true,有一个匹配失败则返回 false

二、Name-driven

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName 根据名称解析依赖 bean 实现比较简单,因为每个 bean 对应唯一名称,不存在“筛选”过程;

@Override
public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {
	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
	try {
		return getBean(name, descriptor.getDependencyType());
	}
	finally {
		ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
	}
}

该方法最终会调用到的 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,该方法比较经典,这里不打算继续书写。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析 的相关文章

随机推荐

  • ToList()所带来的性能影响

    原文 ToList 所带来的性能影响 前几天优化师弟写的代码 有一个地方给我留下很深刻的印象 就是我发现他总是将PLINQ的结果ToList lt gt 然后再返回给主程序 对于这一点我十分不解 于是去问他是什么原因 得到的答案很幽默 因为
  • sequence_item、sequence、sequencer、driver的关系

    框图 简单描述 driver sequencer sequence sequence item 细节理解 最初的验证平台只需要driver即可为什么还需要sequence机制 sequence机制的内部协议 sequence还有很多细节需要
  • 虚拟远程桌面

    微型服务器 太难理解了 我会为你简化它 考虑一个供应商部署的服务器机器来托管其众多客户的网站 为了在这台机器上设计 VPS 提供商会将其划分为多个分区并在虚拟级别上隔离它们 虚拟服务器 如果考虑性能 VPS 显然不如其父服务器 但是 就功能
  • LCD段码显示屏常见故障问题总结

    1 液晶屏有内污 一般现象为黑点 污点 纤维 指LCD内有纤维 2 液晶屏有内刮 一般现象为黑线 白线 PI被刮伤表现为线条刮伤 3 液晶显示颜色不均 一般现象为色彩不一致 彩虹 即LCD的色彩不均匀 在中间彩虹或杠边彩虹以及彩色条纹不均
  • C++之继承

    1 类与类之间的关系有哪些 与类之间的关系分为纵向和横向两种 纵向就是继承 横向包括 依赖 关联 聚合和组合 这里不进行解释 详解链接 https blog csdn net u014694510 article details 88316
  • 关于卷积和其偏置的详细动态图

    动态图 每走一步 得到的图片的值为a b c bias 其中a为卷积核在第一个信道上卷积的值 b为卷积和在第二个信道上卷积的值 c为卷积核在第三个信道上卷积的值 将他们加起来再加上偏置 而在TensorFlow中为什么用conv1 bias
  • Vue基础

    前期回顾 字符串 vue可能会用到的内容 indexOf lastIndexOf 查询字符串下标 找不到返回 1 split 分割为数组 slice start end 切割字符串 subString start end 截取字符串 按下标
  • 360移动安全岗位实习生笔试和面试之旅

    之前抱着试一试的心态投了360的安全岗位 个人觉得移动安全在未来会有很大的需求量 并且人才比较少 安全圈子本来就很小 安全技术本来价值就很高 所以很多大公司以及真正的黑客很少分享一些安全方面的技术 这些感受是我作为一个脚本小子半年来的感触
  • 为什么http请求会缓存?显示from disk cache?

    请求一个接口 发现status code 200 但是居然是否 from disk cache 接口也会缓存吗 请问是什么原因 问题描述 请求接口 发现拿的还是旧数据 排查了一天 后面和前端发现请求接口只花了1ms 然后发现接口状态为 20
  • Dynamics CRM 2016 常用基础操作

    来源 https blog csdn net jxian2009 article details 22179447 http www cnblogs com allenhua archive 2012 12 25 2832473 html
  • Unity 通过代码为一个物体添加多个材质球materials

    Unity 通过代码为一个物体添加多个材质球materials Unity的MeshRenderer提供了Materials数组 支持同时挂多种材质 这样做的目的是 为含有Mesh对象的多个SubMesh使用不同的材质 渲染不同的效果 需要
  • 【acadres.dll文件丢失怎么办】acadres.dll文件丢失的解决办法

    acadres dll文件丢失怎么办 acadres dll是一个windows系统中必备的dll文件 该类型文件的全称为Dynamic Link Library 意思就是动态链接库 不过各位小伙伴不必在意 我们只需要知道它是一个电脑中非常
  • python三维曲面图投影_matplotlib:在2dp上投影三维曲面

    有与 Axes3 相当的吗DSubplot plot 表面 在2D里 我试图在matplotlib中绘制网格在XY平面上的投影 因此不是在 3d 模式下 在import numpy as np import matplotlib pyplo
  • 计算机考研专业课考c语言的大学,【择校必看】十三所计算机专业课只考数据结构的985院校!...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 敲黑板 本文涉及到的学校计算机专业考研只考数据结构 其中部分院校同时也会考算法 C语言等相关内容 但是 相对其他几门 无疑在专业课的复习上大大降低了难度 如果各位同学目前的专业课复习并不理想 也
  • 微信小程序-解决scroll-view抖动

    微信小程序scroll view抖动 原因 产品需要点击换一换 列表置顶并刷新 所以需要动态绑定scroll view里面的scrollTop属性 scrollTop属性用法需要保存 scroll时的值 如果在 scroll时直接复制给sc
  • 01内存对齐之结构体偏移量

    01内存对齐之结构体偏移量 前提概念 结构体偏移量 所谓偏移量 就是我们每个结构体成员的首地址而已 1 求结构体成员偏移量的两中办法 1 简单求结构体成员偏移量 注意 求偏移量时 必须将地址转成int整数才能求偏移量 不能直接地址相减 否则
  • Easyexcel导出带下拉框选项excel模板(解决下拉框超50个的问题)

    1 为了避免excel下拉框选项过多会导致内容不显示 或者生成的时候报错 String literals in formulas can t be bigger than 255 characters ASCII easyexcel 将下拉
  • MobaXterm下载提示输入Master密码,如何使用ResetMasterPassword工具恢复MobaXterm和设置MobaXterm主密码

    MobaXterm忘记主密码 如何恢复和设置主密码 点此下载ResetMasterPassword MobaXterm 20 0汉化版下载 MobaXterm是一款非常好用的远程管理软件 支持SSH FTP 串口 VNC X server等
  • 基础算法题——异或和之和(位运算、组合数)

    异或和之和 题目链接 解题思路 解题方案 暴力枚举 时间复杂度 O n3 超时 位操作 组合数 解铃还须系铃人 对于这种与 或 异或的位操作 一般也是通过位操作来解答 总结规律 题目要求在 n 个正整数中枚举 3 个数进行位操作 若要确定
  • Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析

    从 Spring Boot v2 4 4源码解析 八 依赖注入原理上 由一道面试题引起的思考 Autowired和 Resource的区别 可以看出 解析待注入字段或参数主要由 org springframework beans facto