本文基于 Spring Boot 3.0.0 (Spring 6.0.2)。
@Autowired
来自于 spring-beans 模块;而@Resource
则来自于 jakarta.annotation-api 模块,它是 Jakarta EE 规范中的内容。虽然 @Autowired 与 @Resource 均用于实现依赖注入,但 Spring 对二者的处理逻辑是不一样的。
面向 @Autowired 与 @Resource 注解的依赖注入发生于 Bean 加载流程中 属性填充 populateBean 阶段,具体逻辑位于InstantiationAwareBeanPostProcessor
实现类的postProcessProperties() 方法内。InstantiationAwareBeanPostProcessor 主要有两个实现类,分别是:AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
。
CommonAnnotationBeanPostProcessor 优先于 AutowiredAnnotationBeanPostProcessor 执行,因此咱们先来分析 @Resource 注解,然后是 @Autowired 注解。
1 @Resource
在 CommonAnnotationBeanPostProcessor 中,postProcessProperties() 方法负责为 Bean 实例内由 @Resource 注解标识的 成员变量、setter 方法 注入依赖。
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
}
}
从上述内容来看,postProcessProperties() 方法做了两件事。
-
首先,构建一个 InjectionMetadata
实例,该实例内封装了若干ResourceElement
,而每个 ResourceElement 实例代表了一个由 @Resource 注解标识的 字段 或 setter 方法。 -
然后,调用 InjectionMetadata 的 inject()
方法完成依赖注入。
接着跟进到 InjectionMetadata 中的 inject() 方法,主要就是遍历 ResourceElement 并调用其 inject() 方法。
public class InjectionMetadata {
public void inject(Object target, String beanName, 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);
}
}
}
}
ResourceElement 继承自LookupElement
,LookupElement 又继承自InjectedElement
;从 InjectionMetadata 中 inject() 方法可以看出一些端倪:InjectedElement 肯定抽象了整体的注入逻辑,留一些拓展给子类覆盖,这是老套路了。
继续跟进到 InjectedElement 中的 inject() 方法。
public abstract static class InjectedElement {
protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
if (this.isField) {
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
} else {
try {
Method method = (Method) this.member;
ReflectionUtils.makeAccessible(method);
method.invoke(target, getResourceToInject(target, requestingBeanName));
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return null;
}
}
没错,InjectedElement 最终是通过反射机制来实现依赖注入的。getResourceToInject()
方法用于获取所需要的依赖,InjectedElement 并没有为该方法提供具体的实现,需要由其子类来覆盖。关于 InjectedElement 的继承关系如下图所示。
现在!是时候到 ResourceElement 中一探究竟了。
private class ResourceElement extends LookupElement {
private final boolean lazyLookup;
public ResourceElement(Member member, AnnotatedElement ae, PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
} else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != resourceType) {
checkResourceType(resourceType);
} else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
}
ResourceElement 构造方法内有两处逻辑值得大家关注:
-
如果 @Resource 注解的 name 属性值为 EMPTY STRING (默认值),那么 ResourceElement 实例中
isDefaultName
属性值为 true,进一步表明 ResourceElement 实例中name
属性值要么是成员变量名称,亦或是转换后的 setter 方法名称,具体是通过 StringUtils.uncapitalizeAsProperty(resourceName.substring(3)) 转换的;另外,我们还看到了EmbeddedValueResolver
的身影,这意味着 @Resource 注解的 name 属性值是可以使用占位符的。 -
如果 @Resource 注解的 type 属性值为 Object.class (默认值),则 ResourceElement 实例中
lookupType
属性值要么是成员变量所对应的 Class,亦或是 setter 方法参数所对应的 Class;如果 @Resource 注解的 type 属性值非默认值,则 ResourceElement 实例中lookupType
属性值直接就是 Resource 注解中 type 属性所指定的了。
此外,ResourceElement 中的 getResourceToInject() 方法具体会委派 CommonAnnotationBeanPostProcessor 的 getResource() 方法来获取具体的依赖。
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
protected Object getResource(LookupElement element, String requestingBeanName) throws NoSuchBeanDefinitionException {
return autowireResource(this.resourceFactory, element, requestingBeanName);
}
protected Object autowireResource(BeanFactory factory, LookupElement element, String requestingBeanName) throws NoSuchBeanDefinitionException {
Object resource;
Set<String> autowiredBeanNames;
String name = element.name;
if (factory instanceof AutowireCapableBeanFactory autowireCapableBeanFactory) {
// new LookupDependencyDescriptor((Field) this.member, this.lookupType)
// or
// new LookupDependencyDescriptor((Method) this.member, this.lookupType)
DependencyDescriptor descriptor = element.getDependencyDescriptor();
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
// 核心逻辑
resource = autowireCapableBeanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
} else {
// 核心逻辑
resource = autowireCapableBeanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
} else {
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}
if (factory instanceof ConfigurableBeanFactory configurableBeanFactory) {
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && configurableBeanFactory.containsBean(autowiredBeanName)) {
configurableBeanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}
return resource;
}
}
上述 autowireResource() 方法交代了两个极为重要的知识点:
-
如果当前 ResourceElement 实例的
isDefaultName
属性值为 true 且 ResourceElement 实例的name
属性值在一级缓存 singletonObjects 以及 beanDefinitionMap 中不存在,那么通过 AutowireCapableBeanFactory 的resolveDependency()
方法来获取依赖。这说明需要根据 ResourceElement 实例中的 lookupType 来加载所需依赖 ,因为根据 name 来完成依赖的加载是不可能的了。 -
如果如果当前 ResourceElement 实例的
isDefaultName
属性值为 true,且 ResourceElement 实例的name
属性值在一级缓存 singletonObjects 以及 beanDefinitionMap 中存在;或者当前 ResourceElement 实例的isDefaultName
属性值为 false,那么则委派 AutowireCapableBeanFactory 的resolveBeanByName()
方法来拿到所需的依赖。这说明直接根据 ResourceElement 实例中的 name 来获取所需依赖即可 。
下面分小节对 AutowireCapableBeanFactory 中的 resolveDependency() 方法与 resolveBeanByName() 方法进行分析。
1.1 resolveDependency()
resolveDependency() 方法具体会委派doResolveDependency()
方法去干活。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// descriptor 实际为 LookupDependencyDescriptor 类型
// descriptor.getDependencyType() 就是 ResourceElement 实例中的 lookupType
Class<?> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String strValue) {
String resolvedValue = resolveEmbeddedValue(strValue);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(resolvedValue, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
} catch (UnsupportedOperationException ex) {
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
// lookupType 如果为 Array、Collection 或 Map
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
// lookupType 为简单的引用类型,执行到这里说明所需要的依赖是单个 Bean,不会是集合之类的
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;
// 如果根据 lookupType 获取到多个依赖 Bean,那么需要根据 @Primary 和 @Priority 过滤
// 确保最终只有一个依赖 Bean
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
} else {
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);
}
}
}
敲黑板!!!从上述内容来看,doResolveDependency() 方法极度依赖一个方法,它就是findAutowireCandidates()
。findAutowireCandidates() 方法是干嘛的呢?它可以根据Class<?>
类型的参数 requiredType 来匹配出所有的 beanName,然后通过 beanName 来加载 Bean 实例,最后封装为一个Map<String, Object>
类型的结果;在该 Map 中,key 为 beanName、value 为 Bean 实例。至于 findAutowireCandidates() 的内容,需要大家自行去阅读源码了。
如何根据
Class<?>
类型的参数 requiredType 来匹配出所有的 beanName ?
String[] candidateNames
= BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
根据 requiredType 只是匹配到所有的 beanName,还不是 Bean 实例,那么 Bean 实例是如何加载出来的?
public class DependencyDescriptor extends InjectionPoint implements Serializable {
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
throws BeansException {
return beanFactory.getBean(beanName);
}
}
可见,resolveDependency() 方法先是根据 lookupType 来拿到所需要的依赖名称,然后根据名称加载出真正的依赖。
1.2 resolveBeanByName()
相较于 resolveDependency() 方法,resolveBeanByName() 逻辑可就直白多了,直接根据 ResourceElement 实例中的 name 来加载 Bean。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
@Override
public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
// descriptor 实际为 LookupDependencyDescriptor 类型
// descriptor.getDependencyType() 就是 ResourceElement 实例中的 lookupType
// name 就是 ResourceElement 实例中的 name
return getBean(name, descriptor.getDependencyType());
} finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
}
在开始加载由 @Resource 注解标识的依赖之前,会先走
factory.containsBean(name)
这个逻辑,以决定究竟是基于 byType 策略,还是基于 byName 策略去加载特定依赖。如果 factory.containsBean(name) 为 true,那肯定可以根据 name 加载出所需的依赖,这样最省事,否则不得不根据 type 来加载了。正因为是先走了 factory.containsBean(name) 这一逻辑,所以说 @Resource 注解是优先基于 byName 策略来加载依赖,然后才是 byType 策略!
2 @Autowired
在 AutowiredAnnotationBeanPostProcessor 中,postProcessProperties() 方法负责为 Bean 实例内由 @Autowired 注解标识的 成员变量、setter 方法 和 构造方法 注入依赖。
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
}
InjectionMetadata 在第一章节已经介绍过,这里直接去 AutowiredElement 中找 getResourceToInject() 方法。在 AutowiredElement 的两个实现类AutowiredFieldElement
、AutowiredMethodElement
中并没有发现 getResourceToInject() 方法的身影,因为它俩直接覆盖了 inject() 方法。本文这里选取更为常用的 AutowiredFieldElement 作为分析目标。
private class AutowiredFieldElement extends AutowiredAnnotationBeanPostProcessor.AutowiredElement {
public AutowiredFieldElement(Field field, boolean required) {
super(field, null, required);
}
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value = resolveFieldValue(field, bean, beanName);
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
} catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
return value;
}
}
在 AutowiredFieldElement 内,resolveFieldValue() 方法承担了加载依赖的重任,其内部实现依然是委托 resolveDependency() 方法,这个方法在上一章节已经介绍过了。
@Resource 注解是优先基于 byType 策略来加载出依赖的名称,然后基于 byName 策略来加载出真正的依赖!
3 最后
建议大家自行阅读 resolveDependency() 方法内关于 泛型 Bean 的注入逻辑。
原文始发于微信公众号(程序猿杜小头):@Autowired 与 @Resource 有何不同
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/222206.html