背景
做为一名 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 未指定名称就会按照类型进行注入,这种错误的想法在看到相关源码之后自然能够一眼辨认出来,希望大家看网上文章时时刻持有怀疑态度,经过验证之后才能确认。最后欢迎大家留言评论交流。