Spring 注解原理AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup
Posted binarylei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 注解原理AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup相关的知识,希望对你有一定的参考价值。
Spring 注解原理(二)AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup
Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)
AutowiredAnnotationBeanPostProcessor 是 Spring 注解驱动的核心组件之一,都是处理的 Bean 的依赖注入,相关的注解有 @Autowired @Value @Inject @Lookup 四个,也可以自定义注解后添到 autowiredAnnotationTypes 集合中。
- @Autowired @Value @Inject:这三个注解的逻辑完全一样,都是处理依赖注入,其优先级 @Autowired > @Value > @Inject。因此本文在此做如下约定,@Autowired 一般指的是这三个注解。
- @Lookup:本质也是解决依赖注入的问题,但和上面三个注解不同的是,@Lookup 注入的对象是动态的( 尤其是 prototype 实例),而 @Autowired 注入的对象是静态的,一旦注入就不可发生改变。@Lookup 只能标注在抽象方法上,实例化时使用 CglibSubclassingInstantiationStrategy 进行字节码提升,每次调用该抽象方法时,都调用 beanFactory#getBean 重新获取对象。
1. 工作原理
现在我们先大致看一下 AutowiredAnnotationBeanPostProcessor 是如何工作的呢?beanFactory#doCreateBean 在创建 bean 过程中依次调用如下方法:
- determineCandidateConstructors:解析类的构造器,用于处理构造器注入。如果构造器上标注有 @Autowired 注解,或只有一个有参构造器,则采用构造器自动注入。否则完全按照默认的配置参数 bd. constructorArgumentValues 实例化对象,或无参构造器实例化对象。
- postProcessMergedBeanDefinition:配合 postProcessPropertyValues 方法一起处理字段或方法注入。解析标注有 @Autowired 的注入点元信息 InjectionMetadata,底层调用 findAutowiringMetadata 方法解析注入点元信息。
- postProcessPropertyValues:将 postProcessMergedBeanDefinition 阶段解析的 InjectionMetadata 依次进行属性注入。
AbstractAutowireCapableBeanFactory#doCreateBean
-> createBeanInstance
-> determineConstructorsFromBeanPostProcessors
-> AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
-> autowireConstructor
-> ConstructorResolver#autowireConstructor
-> applyMergedBeanDefinitionPostProcessors
-> AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
-> populateBean
-> AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
2. determineCandidateConstructors
determineCandidateConstructors 处理构造器注入的场景。该方法解析类的构造器,如果构造器上标注有 @Autowired 注解,或只有一个有参构造器,则采用构造器自动注入。否则完全按照默认的配置参数 bd. constructorArgumentValues 实例化对象,或无参构造器实例化对象。
-
@Lookup:实例化时使用 CglibSubclassingInstantiationStrategy 进行字节码提升,生成代理对象。
-
@Autowired:如果构造器上标注有 @Autowired(required=false) 注解,则添加到候选构造器 candidates 中,最后再将默认的无参构造器(如果存在)添加到 candidates 中返回即可。
但如果有标注 @Autowired 的候选构造器,则标注有 @Autowired 注解的候选构造器只能有一个,并最终返回这个候选构造器。
-
无 @Autowired:如果只有一个有参构造器,则直接返回这个构造器即可。如果有多个构造器或只有一个默认构造器,则返回 null。
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException {
// 1. 校验@Lookup 注解 省略...
// 2. 解析@Autowire标注的构造器
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
synchronized (this.candidateConstructorsCache) {
candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
Constructor<?>[] rawCandidates = beanClass.getDeclaredConstructors();
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
Constructor<?> requiredConstructor = null;
Constructor<?> defaultConstructor = null;
int nonSyntheticConstructors = 0;
// 3.1 遍历所有的构造器
for (Constructor<?> candidate : rawCandidates) {
// 3.2 构造器上是否标注有@Autowire,注意CGLIB代理
MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
if (ann == null) {
Class<?> userClass = ClassUtils.getUserClass(beanClass);
if (userClass != beanClass) {
Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann = findAutowiredAnnotation(superCtor);
}
}
// 3.3 如果标注@Autowire,进一步判断是否require=true,如果为true,只能有一个
if (ann != null) {
if (requiredConstructor != null) {
throw new BeanCreationException();
}
boolean required = determineRequiredStatus(ann);
if (required) {
if (!candidates.isEmpty()) {
throw new BeanCreationException();
}
requiredConstructor = candidate;
}
candidates.add(candidate);
// 3.4 缓存默认构造器
} else if (candidate.getParameterCount() == 0) {
defaultConstructor = candidate;
}
}
// 3.5 结果处理
// 3.5.1 标注@Autowire,如果require=true直接返回。否则添加无参构造器返回
if (!candidates.isEmpty()) {
if (requiredConstructor == null) {
if (defaultConstructor != null) {
candidates.add(defaultConstructor);
}
}
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
// 3.5.2 无@Autowire。如果只有一个有参构造器,返回这个构造器,自动注入
} else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
// 3.5.3 无@Autowire。如果有多个构造器或只有一个无参构造器,返回null
} else {
candidateConstructors = new Constructor<?>[0];
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
说明: determineCandidateConstructors 解析类的构造器,我们主要看一下结果是如何处理的。
- 有构造器上标注 @Autowire。根据属性值 require 又可以分为两种情况,指定是否是必须的构造器,默认为 true。
- require=true:只能有一个构造器设置为必须构造器,直接使用这个构造器实例化对象。
- require=false:可以标注多个候选构造器,需要根据参数进一步匹配具体的构造器。此时,会添加默认的无参构造器。
- 没有构造器标注 @Autowire。也可以分为两种情况:
- 只有一个有参构造器:直接返回这个有参构造器。
- 有多个构造器或只有一个无参构造器:返回 null。此时需要根据 bd 配置来实例化对象。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
...
// 1. 解析是否有构造器可用,当 ctors!=null 时采用即构造器自动注入
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 2. 有参构造器实例化(大部分情况),采用即构造器自动注入
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 3. 无参构造器实例化
return instantiateBean(beanName, mbd);
}
3. postProcessMergedBeanDefinition
postProcessMergedBeanDefinition 和 postProcessMergedBeanDefinition 处理字段或方法注入的场景。postProcessMergedBeanDefinition 方法将标注 @Autowired 注入点(字段或方法)解析成元信息 InjectionMetadata,postProcessMergedBeanDefinition 则根据元信息 InjectionMetadata 注入到 bean 中。
字段或方法注入相关方法说明:
- postProcessMergedBeanDefinition:将 @Autowired 注入点(字段或方法)解析成元信息 InjectionMetadata。
- findAutowiringMetadata:缓存解析后的注入点元信息到 injectionMetadataCache。
- buildAutowiringMetadata:递归遍历所有的字段和方法,将标注 @Autowired 的注入点解析成 InjectionMetadata。该方法不会解析静态字段,所以 @Autowired 无法注入静态字段。
- 字段:AutowiredFieldElement
- 方法:AutowiredMethodElement
- InjectionMetadata#checkConfigMembers:将已经处理过的注入点缓存到 bd.externallyManagedConfigMembers 中,下次再处理时不会处理已经缓存的注入点。
- InjectionMetadata#inject:依次遍历所有的注入点元信息 InjectedElement,进行属性注入。
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
-> findAutowiringMetadata
-> buildAutowiringMetadata
-> InjectionMetadata#checkConfigMembers
AutowiredAnnotationBeanPostProcessor#postProcessProperties
-> InjectionMetadata#inject
3.1 buildAutowiringMetadata
buildAutowiringMetadata 方法递归遍历所有的字段和方法,将标注 @Autowired 的注入点解析成 InjectionMetadata。该方法不会解析静态字段,所以 @Autowired 无法注入静态字段。方法本身并不难理解,最重要的关心解析后的对象 AutowiredFieldElement 和 AutowiredMethodElement。
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
// 1. 校验,如果clazz是JDK中的类,直接忽略,因为不可能标注有这些标注
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
// 递归循环所有的父类,所有@Autowired父类的字段也会自动注入
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 2. 解析所有字段上的注解,封装成AutowiredFieldElement,不包括static字段
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 3. 解析所有方法上的注解,封装成AutowiredMethodElement,不包括static方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
// 3.1 处理桥接方法,先忽略这部分
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
// 3.2 忽略static方法
if (Modifier.isStatic(method.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
// 3.3 如果是JavaBean字段,则返回pd,否则返回null
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
说明: buildAutowiringMetadata 字段和方法注入点的元信息解析大同小异:
- 递归解析所有的父类的字段和方法,所以父类可以通过 @Autowired 注入。
- 不会解析静态字段或方法,所以静态字段无法通过 @Autowired 注入。
- 字段和方法分别解析为 AutowiredFieldElement 和 AutowiredMethodElement。其中有两个重要的属性:一个是注入点 Member,二是注入点是否必须。
4. postProcessPropertyValues
postProcessMergedBeanDefinition 方法根据元信息 InjectionMetadata,在 Spring IoC 容器中查找依赖注入到 bean 中。该方法完全委托给了 InjectionMetadata#inject 方法:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}
}
说明: inject 方法就是循环所有的注入点,依次调用其 inject 进行属性注入。问题的关键是 checkedElements 是什么,也就是说会注入那些字段?在之前分析 postProcessMergedBeanDefinition 时,提到调用 findAutowiringMetadata 解析完注入点元信息后,会调用 InjectionMetadata#checkConfigMembers 校验。校验到底是做什么的呢?
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
for (InjectedElement element : this.injectedElements) {
Member member = element.getMember();
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
}
}
this.checkedElements = checkedElements;
}
说明: bd.externallyManagedConfigMembers 缓存已经校验过的注入点,这些缓存的注入点不会再次进行注入,目的就是为了避免重复注入的问题。那问题就来了,字段怎么会进行重复注入呢?比如 CommonAnnotationBeanPostProcessor 同样会解析注入点的元信息,如果 @Autowired 和 @Resource 出现在同一个字段上,此时会出现重复注入的情况。
下面对 InjectionMetadata 中两个注入点属性进行一下说明:
- injectedElements:所有解析的注入点元信息 InjectedElement。
- checkedElements:需要进行属性注入的注入元信息,剔除 bd.externallyManagedConfigMembers 已经处理的注入点。
下面会对字段注入和方法注入,分别进行分析。关键是如何进行依赖查找,底层最终都是调用 beanFactory#resolveDependency 方法。
5. AutowiredFieldElement
字段注入时,通常根据字段类型和字段名称查找依赖。当然,如果你使用 @Value("#{beanName}") 时,会读取注解中的值进行解析。核心还是 beanFactory#resolveDependency 方法。方法本身很简单,都不多说了。
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
// 从缓存中提取value值,可能为desc、beanName、value值
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
} else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
synchronized (this) {
// 只要是解析过,不会缓存下来
if (!this.cached) {
if (value != null || this.required) {
// 缓存DependencyDescriptor
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
// 缓存名称beanName,直接根据名称查找
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
} else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
// java反射
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
6. AutowiredMethodElement
基本上和 AutowiredFieldElement 雷同,唯一不同的是方法注入时,需要对方法的所以参数依次调用 beanFactory#resolveDependency 进行依赖查找。根据 require 值,如果为 true 时无法查找到依赖时会继续查找,false 则不再进行查找。一般默认为 true。
每天用心记录一点点。内容也许不重要,但习惯很重要!
以上是关于Spring 注解原理AutowiredAnnotationBeanPostProcessor:@Autowired @Value @Inject @Lookup的主要内容,如果未能解决你的问题,请参考以下文章