環境:Spring5.3.23
源碼解讀:如何正確使用@Resource和@Autowired注解?
1.注解區別
@Resource 和 @Autowired 都可以用于,依賴注入。但它們之間存在一些明顯的區別。
1.提供方:
- @Autowired 是 Spring 提供的注解。
- @Resource 是 JDK提供的注解。
2.裝配方式:
- @Autowired 默認按類型裝配,即默認情況下必須要求依賴對象存在。如果要允許 null 值,可以設置它的 required 屬性為
false。如果想使用名稱裝配可以結合 @Qualifier 注解進行使用。 - @Resource 默認按照名稱進行裝配,名稱可以通過 name 屬性進行指定。如果沒有指定 name
屬性,當注解寫在字段上時,默認取字段名進行名稱查找。如果注解寫在 setter 方法上默認取屬性名進行裝配。當找不到與名稱匹配的 bean時才按照類型進行裝配。
綜合來看,@Resource 和 @Autowired 在提供方和裝配方式上存在明顯的區別。
2.源碼分析
這里都將以字段注入的方式分析。
@Resource注解
該注解的處理器是CommonAnnotationBeanPostProcessor。
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 獲取所有使用@Resource注解的字段,InjectionMetadata包含了一個List集合InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);metadata.inject(bean, beanName, pvs);return pvs;
}
InjectionMetadata注入核心類
/*** InjectionMetadata類* target 待注入的實例對象 * beanName 當前Bean的名稱* pvs 因為是基于字段注入,所以這里沒有用*/
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection<InjectedElement> elementsToIterate = ...for (InjectedElement element : elementsToIterate) {// 注入;這里當前的element實例是ResourceElement, 這里是調用父類InjectedElement方法element.inject(target, beanName, pvs);}
}
public abstract static class InjectedElement {protected void inject(...) {// 基于字段注入if (this.isField) {Field field = (Field) this.member;ReflectionUtils.makeAccessible(field);// getResourceToInject獲取實例field.set(target, getResourceToInject(target, requestingBeanName));}}
}
private class ResourceElement extends LookupElement {public ResourceElement() {Resource resource = ae.getAnnotation(Resource.class);// 是否指定了name屬性;指定要注入的beanNameString resourceName = resource.name();Class<?> resourceType = resource.type();// 如果設置了name,則為false,否則使用字段名,也就是truethis.isDefaultName = !StringUtils.hasLength(resourceName);if (this.isDefaultName) {// 獲取字段名resourceName = this.member.getName();// ...}// ...this.name = (resourceName != null ? resourceName : "");Lazy lazy = ae.getAnnotation(Lazy.class);// 字段上是否使用了@Lazy注解,我們這里不考慮@Lazy情況this.lazyLookup = (lazy != null && lazy.value());}protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {// 字段沒有添加@Lazy注解,所以為false,執行elsereturn (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :// 執行這里getResource方法getResource(this, requestingBeanName));}
}
接下來進入到CommonAnnotationBeanPostProcessor中getResource方法
public class CommonAnnotationBeanPostProcessor {protected Object getResource(LookupElement element, @Nullable String requestingBeanName) {// ...return autowireResource(this.resourceFactory, element, requestingBeanName); }protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) {String name = element.name;if (factory instanceof AutowireCapableBeanFactory) {AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;// 默認如果@Resource沒有指定name屬性,所以這里的name為字段名// factory.containsBean(name) 判斷當前容器中是否有以該字段為名的 Bean// 不存在則進入;先按照名稱匹配,如果不存在則進入if,if中則按照類型查找if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {autowiredBeanNames = new LinkedHashSet<>();// 這里的邏輯就是按照當前字段類型在容器中查找Beanresource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);if (resource == null) {throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");}} else {// 如果容器中存在字段名的Bean,則以beanName在容器中查找Beanresource = beanFactory.resolveBeanByName(name, descriptor);autowiredBeanNames = Collections.singleton(name);}}}
}
以上就是@Resource注解的原理
@Autowired注解
該注解的處理器是AutowiredAnnotationBeanPostProcessor。
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 獲取所有使用@Autowired注解的字段,InjectionMetadata包含了一個List集合InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);// 進入InjectionMetadata#inject方法metadata.inject(bean, beanName, pvs);return pvs;
}
/*** InjectionMetadata* target 待注入的實例對象 * beanName 當前Bean的名稱* pvs 因為是基于字段注入,所以這里沒有用*/
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Collection<InjectedElement> elementsToIterate = ...for (InjectedElement element : elementsToIterate) {// 注入;這里當前的element實例是AutowiredFieldElement, 這里是調用父類InjectedElement方法element.inject(target, beanName, pvs);}
}
進入AutowiredFieldElement#inject方法
private class AutowiredFieldElement {protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;// 首次cached=falseif (this.cached) {// ...} else { // 解析字段獲取bean對象value = resolveFieldValue(field, bean, beanName);}if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}}private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());try {// 進入DefaultListableBeanFactory方法value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}}
}
DefaultListableBeanFactory
public class DefaultListableBeanFactory {public Object resolveDependency(DependencyDescriptor descriptor, ...) {// ...Object result = ...result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);return result}public Object doResolveDependency(DependencyDescriptor descriptor, ...) {// 該方法中就會按照類型進行查找相應的bean。// 當有多個相同類型的bean時會調用下面的方法進行處理if (matchingBeans.size() > 1) {// 存在多個相同類型時,進行處理autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);}}protected String determineAutowireCandidate() {// 有沒有@Primary注解String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);if (primaryCandidate != null) {return primaryCandidate;}// 有沒有@Priority注解,值越小,優先級越高String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);if (priorityCandidate != null) {return priorityCandidate;}// Fallback 回退處理,如果以上情況都不存在則按照名稱匹配for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateName = entry.getKey();Object beanInstance = entry.getValue();if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||// 按照名稱匹配matchesBeanName(candidateName, descriptor.getDependencyName())) {return candidateName;}}return null;}
}
到這里你應該清楚了@Resource@Autowired注入的區別了,自身再通過源碼走一遍流程,以后就不用在死記硬背這些東西了。