Spring 中 @Autowired 和 @Resource 有什么区别?

背景

做为一名 Java 程序员,日常开发中使用最多的便是 Spring,工作了很多年,很多人都停留在使用的层面上,甚至连最基本的概念都没搞懂。笔者在 Java 领域也辛勤耕耘了几年,为了避免浮于表面,在今年6月份开始看 Spring 的源码,其优秀的设计确实值得每一个 Java 开发者去学习。使用 Spring 进行依赖注入我们最常使用的注解是 @Autowired,最近有同事用到了 @Resource 注解,可能了解到我在看 Spring,因此有向我请教和 @Autowired 有什么区别。鉴于多数人可能对这两者的区别不甚理解,而网上大多数文章又只给出结论,甚至结论中也存在一些错误,因此有必要开一篇文章进行深入分析。知其然,还要知其所以然。

@Autowired 和 @Resource 的区别

这里先给出结论,后面我们将会带着结论去分析,结论到底是否正确。

  • 区别一:所属不同。
    • @Autowired 是 spring-beans 模块提供的注解。
    • @Resource 是 JSR 250 规范提出的注解,由 JDK 自带。
  • 区别二:装配方式不同。两者都可以标注在属性或 setter 方法上。
    • @Autowired 注解只能按照类型装配依赖,如果需要按照名称装配还需要指定 @Qualifier 注解。
    • @Resource 默认依赖 bean 的名称为属性名,并且可以通过其属性 name 进行指定。默认依赖的 bean 的类型为属性的类型,并且可以通过 type 属性进行指定。 如果未指定依赖的 bean 的名称并且属性名对应的 bean 在容器中不存在时才会按照类型进行注入,否则按照名称进行注入依赖。
  • 区别三:是否强依赖不同。
    • @Autowired 指定的依赖默认必须存在,可以通过 requied 属性指定不存在。
    • @Resource 指定的依赖必须存在。

原理分析

@Autowired 注入分析

Spring 使用 AutowiredAnnotationBeanPostProcessor 对 @Autowired 进行处理。具体来说注入属性的方法位于 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject,注入方法参数的方法位于 AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject,它们的处理方式基本类似。以注入属性的流程为例进行分析,查看属性注入相关代码如下。

	private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {
     
		
		public AutowiredFieldElement(Field field, boolean required) {
     
			super(field, null);
			this.required = required;
		}
		
		// 注入属性
		@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
     
			Field field = (Field) this.member;
			Object 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);
				Assert.state(beanFactory != null, "No BeanFactory available");
				TypeConverter typeConverter = beanFactory.getTypeConverter();
				try {
     
					// 解析依赖
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				} catch (BeansException ex) {
     
					throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
				}
				... 省略部分代码
			}
			if (value != null) {
     
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}
	}

可以看到,Spring 对 @Autowired 标注的属性的注入只是将属性转换为依赖描述符 DependencyDescriptor ,然后调用 BeanFactory 解析依赖的方法。转换为依赖描述符时需要指定是否依赖是必须的,这个值在实例化 AutowiredFieldElement 时指定。AutowiredAnnotationBeanPostProcessor 实例化 AutowiredFieldElement 的相关代码如下。

	// 注解属性名
	private String requiredParameterName = "required";
	// 依赖必须存在的属性值
	private boolean requiredParameterValue = true;
	
	// 构建注入元数据
	private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
     
		if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
     
			return InjectionMetadata.EMPTY;
		}

		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;

		do {
     
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

			ReflectionUtils.doWithLocalFields(targetClass, field -> {
     
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
     
					if (Modifier.isStatic(field.getModifiers())) {
     
						... 省略日志打印
						return;
					}
					// 循环属性,将 @Autowired 标注的属性转换为 AutowiredFieldElement
					boolean required = determineRequiredStatus(ann);
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});

			... 省略方法参数上 @Autowired 注解的处理

			elements.addAll(0, currElements);
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);

		return InjectionMetadata.forElements(elements, clazz);
	}
	
	// 判断依赖是否必须存在
	protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
     
		// The following (AnnotationAttributes) cast is required on JDK 9+.
		return determineRequiredStatus((AnnotationAttributes)
				ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
	}
	
	// 判断依赖是否必须存在
	@Deprecated
	protected boolean determineRequiredStatus(AnnotationAttributes ann) {
     
		return (!ann.containsKey(this.requiredParameterName) ||
				this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
	}
			

Spring 构建注入元数据时通过反射获取属性,然后将属性转换为 AutowiredFieldElement ,而 required 的值的获取则会读取注解中的 required 属性,如果不存在或值为 true 则依赖必须存在。

至此,可以看出 @Autowired 的处理只是把属性或方法参数转换为依赖描述符,然后调用 BeanFactory 依赖注入的方法,至于依赖是否必须存在则可以由 @Autowired 的 required 属性值进行指定。AutowireCapableBeanFactory#resolveDependency 方法完成真正的依赖注入,其会根据依赖的类型注入 0 到多个 bean 。例如如果依赖是一个 Map,则 Spring 会把 Map 值类型对应的 bean 全部存在到 map 中,而如果依赖的是普通的类型而 Spring 中存在多个相同类型的 bean,Spring 又无法确定使用哪一个则需要使用 @Primary 指定主要的 bean 或使用 @Qualifier 指定依赖的 bean 的名称。由于篇幅问题,感兴趣的小伙伴可自行查阅相关代码。

@Resource 注入分析

除了对 Spring 自带注解的支持,Spring 对 JSR 250 等各种规范也进行了支持,对 @Resource 的处理 Spring 使用 CommonAnnotationBeanPostProcessor 进行。注入 @Resource 标注的属性或方法的相关代码如下。

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
		implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
     
	protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {
     

		Object resource;
		Set<String> autowiredBeanNames;
		String name = element.name;

		if (factory instanceof AutowireCapableBeanFactory) {
     
			AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
			DependencyDescriptor descriptor = element.getDependencyDescriptor();
			// fallbackToDefaultTypeMatch 默认为 true, isDefaultName 表示是否使用默认的属性名称,name 表示属性名称
			if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
     
				// 优先按照 bean 名称进行依赖注入,不存在则按照类型进行注入
				autowiredBeanNames = new LinkedHashSet<>();
				resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
				if (resource == null) {
     
					throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
				}
			} else {
     
				resource = beanFactory.resolveBeanByName(name, descriptor);
				autowiredBeanNames = Collections.singleton(name);
			}
		} else {
     
			resource = factory.getBean(name, element.lookupType);
			autowiredBeanNames = Collections.singleton(name);
		}

		if (factory instanceof ConfigurableBeanFactory) {
     
			ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
			for (String autowiredBeanName : autowiredBeanNames) {
     
				if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
     
					beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
				}
			}
		}

		return resource;
	}

}

可以看到,Spring 会对根据 LookupElement 进行判断,如果使用默认的属性名,并且属性名在 Spring 中不存在对应的 bean,则会委托给 AutowireCapableBeanFactory#resolveDependency 进行处理,此时和 @Autowired 注解保持一致。如果 @Resource 指定了 bean 名称或者属性名对应的 bean 在容器中存在,Spring 会按照名称进行依赖注入。而非绝对的未指定名称则按照类型注入,这点需要注意。

总结

本篇介绍了 Spring 依赖注入中 @Autowired 和 @Resource 的区别,并从源码角度进行了分析。网上的博客质量参差不齐,初学 Spring 时我也在网上查了一些资料,一度认为 @Resource 未指定名称就会按照类型进行注入,这种错误的想法在看到相关源码之后自然能够一眼辨认出来,希望大家看网上文章时时刻持有怀疑态度,经过验证之后才能确认。最后欢迎大家留言评论交流。

你可能感兴趣的:(重学,Spring,spring,java,javaee)