@Autowired与@Resource原理知识点详解

2 篇文章 0 订阅
1 篇文章 0 订阅

前言

现在spring可以说是一统天下了,而spring有两个核心部分:IOC和AOP,AOP之前可以看一下之前的文章:Spring AOP原理使用详解

springIOC

AOP的不多做赘述了,说下IOC:Spring IOC 解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系,而IOC有两个概念:控制反转及依赖注入。控制反转指的就是:把原有自己掌控的事交给别人去处理,更多的是一种思想或者可以理解为设计模式。
而依赖注入其实是控制反转的实现方式,通过依赖注入对象无需自行创建或者管理它的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。

依赖注入的三种方式

其实还有一种基于XML的注入方式,但是现在这种方式过于繁琐且依赖冗余复杂的配置文件,再次不做讨论,仅说下常用的三种注入方式:

属性注入(字段注入)

该方式底层其实是基于Field注入的方式,也可以成为字段注入,因为注入方式实现比较简介,没有多于代码,所以这个在实际开发过程中比较常用:
在这里插入图片描述
该方式可以使用@Autowired、@Resource及@Inject来实现。后面会着重讲解这一部分。

构造方法注入

这种方式其实是spring官方比较推荐的方式,因为构造方法注入的时候会比较靠前,是在bean的实例化阶段就进行的:
在这里插入图片描述
但是有一点需要注意一下:如果只有一个构造方法的话可以不用加上@Autowired,但是如果有无参构造方法或者其他多个构造方法的话,就需要在指定的构造方法上面添加@Autowired注解了。

setter注入

我都不想列出来,但是又怕别人说我不知道这个(哈哈哈哈哈哈)。。。因为这种方式我觉得用的会越来越少的,因为这种代码太臃肿了,且好丑。。。
在这里插入图片描述

用哪个?

如果是开发的话,我建议直接属性(字段)注入完事儿,一般的业务场景基本不会有纰漏。但是如果想在bean实例化阶段进行一些处理操作或者是不在同一个IOC容器里面的话,我建议构造函数注入。
spring4以后推荐的是通过构造函数注入:

  • 依赖不可变:这个好理解,通过构造方法注入依赖,在对象创建的时候就要注入依赖,一旦对象创建成功,以后就只能使用注入的依赖而无法修改了,这就是依赖不可变(通过 set 方法注入将来还能通过 set 方法修改)。
  • 依赖不为空:通过构造方法注入的时候,会自动检查注入的对象是否为空,如果为空,则注入失败;如果不为空,才会注入成功。
  • 完全初始化:由于获取到了依赖对象(这个依赖对象是初始化之后的),并且调用了要初始化组件的构造方法,因此最终拿到的就是完全初始化的对象了。

idea里面为什么不推荐使用属性(字段)注入:

  • 对于 IOC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。
  • 属性注入时机较晚。在一些是实列化阶段的一些操作,无法使用注入对象。

@Autowired

在这里插入图片描述
@Autowired注解,可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,@Autowired标注可以放在成员变量上,也可以放在成员变量的set方法上,也可以放在任意方法上表示,自动执行当前方法,如果方法有参数,会在IOC容器中自动寻找同类型参数为其传值。在上面已经讲解过了。
这里必须明确:@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier使用。

实现原理

源码跟踪下bean的实例化及初始化,以下源码定位流程就先不截图了,等到关键重要地方再截图:

  1. SpringApplication#run()方法的this.refreshContext

  2. AbstractApplicationContext#refresh方法的this.finishBeanFactoryInitialization(beanFactory);该方法会初始化所有非延迟加载的单例bean,包括我们在应用中声明的各种业务Bean,Bean的实例化和初始化过程逻辑都在这个函数中。

  3. 上述finishBeanFactoryInitialization方法中有this.getBean(weaverAwareName)方法。

  4. AbstractBeanFactory#doGetBean()方法中有createBean()方法。

  5. 进入AbstractAutowireCapableBeanFactory#createBean()方法,该方法内有doCreateBean()方法:
    在这里插入图片描述
    doCreateBean()方法是用来构建bean的,bean的实例化及初始化逻辑都是在doCreateBean()方法里面。该方法代码截图一下吧,重点关注两个方法:
    在这里插入图片描述

  6. 先看下applyMergedBeanDefinitionPostProcessors方法:
    在这里插入图片描述
    可以看到该方法里面调用了所有的MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法,其中有一个我们一直在找的实现类:AutowiredAnnotationBeanPostProcessor,这个就是用来处理依赖注入的后置处理器。MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法如下:
    在这里插入图片描述
    AutowiredAnnotationBeanPostProcessor会对bean进行注解扫描,如果扫描到了@Autowired和@Value注解,就会把对应的方法或者属性封装起来,最终封装成InjectionMetadata对象。

  7. 我们再看第二个重要方法populateBean(),这是个非常重要的方法:填充Bean实例属性完成依赖对象的注入,此时Bean中需要依赖注入的成员已经在第六步的applyMergedBeanDefinitionPostProcessors中被对应的后置处理器(AutowiredAnnotationBeanPostProcessor)进行了存储。
    这个方法内部进行部分截图:
    在这里插入图片描述
    这段代码肯定不会return,因为postProcessAfterInstantiation是为true的?重点定位到postProcessPropertyValues方法:
    在这里插入图片描述

  8. 可以看到有两个实现类:一个就是AutowiredAnnotationBeanPostProcessor而另一个是CommonAnnotationBeanPostProcessor。@Resource是通过CommonAnnotationBeanPostProcessor的postProcessPropertyValues实现的,我们先以@Autowired为例说明下,进入到AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法内:
    在这里插入图片描述
    在这里插入图片描述
    查看findAutowiringMetadata()方法:
    在这里插入图片描述
    查看下buildAutowiringMetadata()部分方法:
    在这里插入图片描述
    当调用findAutowiringMetadata()方法时,会根据autowiredAnnotationTypes这个全局变量中的元素类型来进行注解的解析,而这个全局变量在AutowiredAnnotationBeanPostProcessor的构造方法中进行了初始化:
    在这里插入图片描述
    构造方法初始化的时候会向autowiredAnnotationTypes全局集合变量里面加入@Autowired、@Inject、@Value
    以上说明了findAutowiringMetadata()方法只解析出bean中带有@Autowired注解、@Inject和@Value注解的属性和方法。然后调用metadata.inject()方法,进行属性填充,继续跟踪:
    在这里插入图片描述
    以上代码中:checkedElements用于检查元素防止重复注入,bean中@Autowired 注入点会被提前解析成元信息并保存到InjectionMetadata中(对于@Resource,@LookUp等注解被解析后,也会解析成对应的InjectedElement的子类:ResourceElement、LookUpElement,但是这两个注解是在CommonAnnotationBeanPostProcessor后置处理器进行处理的),遍历InjectedElement集合继续执行注入逻辑element.inject():
    在这里插入图片描述

  9. 该方法对应两个实现类分别为:AutowiredFieldElement及AutowiredMethodElement,字面意思对应分别为字段注入及方法注入实现,两者实现逻辑基本相同,我们以AutowiredFieldElement为例:
    在这里插入图片描述
    ReflectionUtils.makeAccessible(field);利用Java的反射,为属性进行赋值,我们重点看下resolveFieldValue():
    在这里插入图片描述

  10. 重点看下resolveDependency()方法,该方法主要调用了doResolveDependency()方法,接下来重点分析一下doResolveDependency()这个方法,源码如下:

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
	try {
		Object shortcut = descriptor.resolveShortcut(this);
		if (shortcut != null) {
			return shortcut;
		}

		Class<?> type = descriptor.getDependencyType();
		Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
		if (value != null) {
			if (value instanceof String) {
				String strVal = resolveEmbeddedValue((String) value);
				BeanDefinition bd = (beanName != null && containsBean(beanName) ?
						getMergedBeanDefinition(beanName) : null);
				value = evaluateBeanDefinitionString(strVal, bd);
			}
			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
			try {
				return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
			}
			catch (UnsupportedOperationException ex) {
				// A custom TypeConverter which does not support TypeDescriptor resolution...
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
		}

		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
		if (multipleBeans != null) {
			return multipleBeans;
		}

		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;

		if (matchingBeans.size() > 1) {
			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
			if (autowiredBeanName == null) {
				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
				}
				else {
					// In case of an optional Collection/Map, silently ignore a non-unique case:
					// possibly it was meant to be an empty collection of multiple regular beans
					// (before 4.3 in particular when we didn't even look for collection beans).
					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);
	}
}

其中有个resolveMultipleBeans()方法,该方法用于处理注入时属性为数组集合等情况,比如我们在bean中使用注入如下:
在这里插入图片描述
可以看下该方法的关键代码:
在这里插入图片描述
该方法内调用了findAutowireCandidates()方法主要用于根据类型查找依赖,大致可以分为三步:先查找内部依赖,再根据类型查找,最后没有可注入的依赖则进行补偿。其中@Qualifier注解就是在这个函数中处理的。实现从容器中找到所有对应类型的bean,最终会调用getBean()方法,这样当对应类型的对象还没被创建时,就会去创建。
我们再返回doResolveDependency()方法,查看下面的逻辑:
在这里插入图片描述
可以看到又一次调用了findAutowireCandidates()方法,会查找对应类型的bean,如果没有查找到并且该属性是必须要要注入的,则会抛异常。
继续往下走,可以看到:
在这里插入图片描述
如果查找对应类型bean>1,则会执行determineAutowireCandidate()方法,从字面意思来看的话是决定选择注入属性。可以看到执行完该方法后,如果autowireedBeanName为null也就是说无法决定使用哪一个,则抛异常。我们重点看下determineAutowireCandidate()方法的逻辑:
在这里插入图片描述
这个逻辑就比较明确了,相同类型的选取规则就是:首先选取@Primary注解的对象;其次根据@Priority来选取优先级高的对象;最后根据属性名称与spring的beanName来判断。如果按照这个还是没有选择出来的话,就会抛出异常(throw new NoUniqueBeanDefinitionException))。
再次返回doResolveDependency()方法,在执行完determineAutowireCandidate()方法后,会执行如下逻辑:
在这里插入图片描述
在这里插入图片描述
可以看到通过beaName及类型,调用getBean()方法向容器获取依赖bean对象,若该对象还没有构建,则会构建该对象,直到依赖的所有对象都已构建好,然后进行属性注入。后面的就是一直校验及判断逻辑了,在此不做赘述。
11. 至此AutowiredAnnotationBeanPostProcessor通过postProcessPropertyValues()方法完成了自动装配。以上就是@Autowired注解的实现原理。

@Resource

实现原理

@Resource的实现原理与@Autowired原理基本类似,不做赘述,只说明一下不同点:
1.@Resource注解的实现是通过后置处理器CommonAnnotationBeanPostProcessor类的postProcessPropertyValues()方法实现的。
2. CommonAnnotationBeanPostProcessor的findResourceMetadata()方法只解析出bean中带有@Resource注解、@WebServiceRef和@EjB注解的属性和方法
在这里插入图片描述
在这里插入图片描述
3. 查找顺序,看源码:
在这里插入图片描述
在这里插入图片描述
首先会判断是否是属性(字段)注入,我们以属性输入为例,重点看下getResourceToInject(target, requestingBeanName):

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
    return this.lazyLookup ? CommonAnnotationBeanPostProcessor.this.buildLazyResourceProxy(this, requestingBeanName) : CommonAnnotationBeanPostProcessor.this.getResource(this, requestingBeanName);
}

(这个代码为啥不换行,截图都不好截取。)
看代码描述如果是@Lazy则会返回一个代理对象,如果不是的话就执行CommonAnnotationBeanPostProcessor.this.getResource(this, requestingBeanName)逻辑,我们看下该逻辑代码:
在这里插入图片描述
继续查看this.autowireResource(this.resourceFactory, element, requestingBeanName):
在这里插入图片描述
如图示:第二与第三的执行逻辑是一样的,所以关键就是第一个的判断条件,如果this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)为true,那么获取属性的逻辑与@Autowired的逻辑一样。如果为false,是根据resource = factory.getBean(name, element.lookupType)获取属性,此时是根据ByName注入属性。
如果 !factory.containsBean(name)为true的话,说明容器内按照名称查找该name的bean是没有的,所以会执行beanFactory.resolveDependency()方法,执行@Autowired相同逻辑。

结论

根据上面源码分析,严谨来说,@Resource并不是传统意义所说的先按照name再按照type注入属性的,有三个if分支,第一个if是先判断有没有name相关bean再执行ByType—@Primary—@Priority—byName。

@Autowired与@Resource的不同

简单描述下,能说上来就行:

来源不同

@Autowired是Spring定义的注解,而@Resource是JSR-250定义的注解。

参数不同

@Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。@Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。

使用不同

@Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。

装配顺序

@Autowired默认按byType自动装配,而@Resource默认byName自动装配。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
@Autowired与@Resource是用来实现依赖注入的注解,在Spring/Spring Boot项目中使用。它们具有一些不同之处。首先,它们的来源不同,@Autowired来自于Spring框架,而@Resource来自于Java的JSR-250。其次,它们依赖查找的顺序不同,@Autowired先根据类型再根据名称查询,而@Resource先根据名称再根据类型查询。另外,它们支持的参数数量和用法也有所不同。@Autowired只支持设置一个参数,而@Resource支持设置多达七个参数。关于依赖注入的用法支持,@Autowired既支持构造方法注入,又支持属性注入和Setter注入,而@Resource只支持属性注入和Setter注入。此外,在编译器IDEA中,当注入Mapper对象时,使用@Autowired注解编译器会提示错误,而使用@Resource注解则不会提示错误。需要注意的是,@Autowired是由Spring提供,而@Resource是由Java提供。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [面试突击:@Autowired 和 @Resource 有什么区别?你学会了吗?](https://blog.csdn.net/Candyz7/article/details/126578224)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [@Resource和@Autowired的区别与理解](https://blog.csdn.net/hanhanhanxu/article/details/106433857)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰红茶不会渴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值