@Resource真的只是按名称来进行依赖注入吗?@Autowired真的只是按照类型来依赖注入吗?

背景

我们常听别人说:“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”,那么这句话到底正不正确呢?

这里我先下个定论,“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”这句话正确也不正确。要怎么理解呢?@Resource注解是优先按照名称来进行依赖注入,但如果按名称找不到对应的Bean时,还是按类型来进行依赖注入;同样,当某个接口存在多个实现类,并且这些实现类都交给IoC容器管理,那么@Autowired也会尝试根据名称来进行依赖筛选。

Talk is cheap. Show me the code

第一步:首先定义一个接口:UserService以及实现类UserServiceImpl。

package com.xxx.hyl.dependency.inject;

public interface UserService {
}

package com.xxx.hyl.dependency.inject.impl;

import com.xxx.hyl.dependency.inject.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * IoC容器默认beanName生成规则 是将类名首字母小写,这里我们手动指定beanName,来测试{@link Resource}注解是否真的只是按名称进行依赖注入
 *
 * @author 君战 *
 */
@Service("customizedBeanName")
public class UserServiceImpl implements UserService {}

第二步:编写一个启动类,使用@Resource注解来注入UserService 实现类,启动main方法。

package com.xxx.hyl.dependency.inject;

import com.xxx.hyl.dependency.inject.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.annotation.Resource;

/***
 *  演示{@link Autowired} 是否真的只是按照类型来进行依赖注入
 *  演示{@link Resource}  是否真的只是按照名称来进行依赖注入
 *
 *  @author 君战
 * **/
public class DependencyInjectionDemo {

    // 随便指定beanName
    @Resource
    private UserService bean_111;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(DependencyInjectionDemo.class, UserServiceImpl.class);
        context.refresh();
        // 如果@Resource注解真的只是按照名称来进行依赖注入,那么下面这段代码将会抛出空指针异常,因为IoC容器中没有一个名字叫做“bean_111”的Bean
        System.out.println(context.getBean(DependencyInjectionDemo.class).bean_111.hashCode());
    }

}

第三步:查看控制台。

1629911510

Process finished with exit code 0

可以发现,我们将属性名随便修改,使用@Resource注解依然可以注入。那么我们接下来再尝试下@Autowired注解是否真的只是按照类型来进行注入的。这里我们修改下代码,再增加一个UserService实现类。

package com.xxx.hyl.dependency.inject.impl;

import com.xxx.hyl.dependency.inject.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * IoC容器默认beanName生成规则 是将类名首字母小写,这里我们手动指定beanName,来测试{@link Resource}注解是否真的只是按名称进行依赖注入
 *
 * @author 君战 *
 */
@Service("customizedBeanName2")
public class UserServiceImpl2 implements UserService {}

修改启动类代码,执行main方法。

package com.xxx.hyl.dependency.inject;

import com.xxx.hyl.dependency.inject.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.annotation.Resource;

/**
 * * 演示{@link Autowired} 是否真的只是按照类型来进行依赖注入 
 *   演示{@link Resource} 是否真的只是按照名称来进行依赖注入
 *   @author 君战
 * <p>*
 */
public class DependencyInjectionDemo {

    // 随便指定beanName
    @Resource private UserService bean_111;
	// 这里我们将属性名指定为 UserServiceImpl2 @Service("customizedBeanName2") 指定的beanName
    @Autowired private UserService customizedBeanName2;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(DependencyInjectionDemo.class, UserServiceImpl.class);
        context.refresh();
        // 如果@Resource注解真的只是按照名称来进行依赖注入,那么程序将启动失败,
        // 因为IoC容器中没有一个名字叫做“bean_111”的Bean
        System.out.println("@Resource注解注入的结果:"+ context.getBean(DependencyInjectionDemo.class).bean_111.hashCode());
        // 如果@Autowired注解真的只是按照类型来进行依赖注入的,因为UserService存在多个实现类,那么
        // 程序将启动失败,@Autowired注解的required属性默认为true,即必须注入。
        System.out.println("@Autowired注解注入的结果:"+
                context.getBean(DependencyInjectionDemo.class).customizedBeanName2.hashCode());
    }
}

查看控制台。

@Resource注解注入的结果:1250391581
@Autowired注解注入的结果:1250391581

Process finished with exit code 0

通过以上代码测试,我们可以得出一个结论,@Resource并不真的只是按照名称来进行依赖注入,@Autowired也并不是真的只是按照类型来进行依赖注入。

底层原理分析

首先来分析下对于添加了@Resource注解的字段是如何处理的,处理逻辑在CommonAnnotationBeanPostProcessor的autowireResource方法中。

// CommonAnnotationBeanPostProcessor#autowireResource
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {

	Object resource;
	Set<String> autowiredBeanNames;
	String name = element.name;
	// 判断BeanFactory是否是AutowireCapableBeanFactory,DefaultListableBeanFactory实现了该接口,判断成立。
	if (factory instanceof AutowireCapableBeanFactory) {
		AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
		DependencyDescriptor descriptor = element.getDependencyDescriptor();
		// 重点是这里!!! 判断IoC容器中是否包含指定name的Bean(!factory.containsBean(name)),
		if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
			autowiredBeanNames = new LinkedHashSet<>(); 
			// 执行到这里意味着根据name(可能是字段名,也可能是方法参数名)并未找到相应的Bean,
			// 调用IoC容器的resolveDependency方法,该方法是根据类型去查找Bean。
			// 对@Autowired注解的处理也是调用该方法
			resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
			if (resource == null) {
				throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
			}
		} else {// 执行这里就意味着根据name(可能是字段名,也可能是方法参数名)
		// 找到了对应的Bean,因此根据name来获取相关Bean
			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;
}

接下来我们再分析下当再字段上添加@Autowired注解的处理逻辑-AutowiredFieldElement的inject方法(由此可以推导出来当在方法上添加@Autowired注解上,使用的就是AutowiredMethodElement)。

// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
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 { 
			// 对@Autowired注解的处理重点是这里调用AutowireCapableBeanFactory的resolveDependency方法。
			// 前面我们在分析@Resource注解处理逻辑时,已经提过,当未根据name找到对应的Bean时,
			// 也是调用AutowireCapableBeanFactory的resolveDependency方法。
				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);
		}
	}
}

这里还有一个问题就是,@Autowired注解也会根据名称来过滤依赖吗?

答案是肯定的,根据名称来进行依赖过滤的代码在doResolveDependency方法中,

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();
		// 这里是解析@Value注解的地方,不熟悉的小伙伴可以查看我的另一篇博文-
		// 《Spring-外部配置的值是如何通过@Value注解获取的?》 
		// 地址:https://blog.csdn.net/m0_43448868/article/details/111590614
		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) {
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
		}
		// 这里是解析多类型Bean的地方,如何注入某个类型的多个实现类,
		// 可以查看我的另一篇博文-《如何使用@Autowire注入某个类型的多个实现类?》 
		// 地址:https://blog.csdn.net/m0_43448868/article/details/111588584
		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
		if (multipleBeans != null) {
			return multipleBeans;
		}
		// 通常我们注入的都是某个类型的单实现类,执行findAutowireCandidates方法。
		// 延伸一点,依赖注入和依赖查找数据来源最重要的区别也是在这里完成的,
		// 详细介绍可以查看我的另一篇博文-《在Spring IoC中,依赖注入和依赖查找的数据来源一样吗?》
		// 地址:https://blog.csdn.net/m0_43448868/article/details/111866510
		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) { // 如果根据某个类型获取到匹配依赖为空
			if (isRequired(descriptor)) { // 判断该依赖是否是必须的,例如@Autowired的required属性(默认为true)
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);// 抛出异常
			}
			return null;
		}

		String autowiredBeanName;
		Object instanceCandidate;
		// 如果匹配到的依赖有多个,执行determineAutowireCandidate方法来决定到底使用哪一个。
		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();
		}

		// 删除与本次分析无关代码......
		return result;
	}
	finally {
		ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
	}
}
// DefaultListableBeanFactory#determineAutowireCandidate
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
		Class<?> requiredType = descriptor.getDependencyType();
	// 优先解析@Primary注解,看到这里应该就明白当某个类型存在多个实现类,
	// 并且多个实现类都交由IoC容器管理,为什么可以通过@Primary注解来控制使用哪一个Bean。
	String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
	if (primaryCandidate != null) {
		return primaryCandidate;
	}
	// 这里就是解析 javax中的javax.annotation.Priority注解,如果某个类型存在多个实现类,
	// 并且多个实现类都交由IoC容器管理,但是并不想使用@Primary注解来决定哪一个是主要的,
	// 那么可以使用@Priority注解。底层IoC容器也是支持的。
	String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
	if (priorityCandidate != null) {
		return priorityCandidate;
	}
	// 最后,就是根据名字来进行匹配。
	// 例如前面我们在演示代码DependencyInjectionDemo类中  @Autowired private UserService customizedBeanName2; 
	// 这里在使用@Autowired注解声明依赖UserService 时,字段名为customizedBeanName2。
	// 这样当IoC容器执行到这里时,就可以拿着beanName和字段名进行一一比对,当两者匹配时,直接return该依赖。
	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注解并不是仅仅只是按照名称来进行注入,当根据名称未找到对应bean时,也是按照类型来进行注入。而@Autowired也不仅仅只是按照来行来进行注入,例如当某个类型的Bean有多个时,也会按照名称来进行依赖筛选,过滤掉那些虽然类型匹配但是beanName和名称不匹配的Bean。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
@Resource和@Autowired是用来实现依赖注入的注解,在Spring/Spring Boot项目中使用。它们有以下几点区别: 1. 来源不同:@Autowired来自Spring框架,而@Resource来自Java的JSR-250标准。 2. 依赖查找的顺序不同:@Autowired根据类型再根据名称查询,而@Resource根据名称再根据类型查询。 3. 支持的参数不同:@Autowired只支持设置1个参数,而@Resource支持设置7个参数。 4. 依赖注入的用法支持不同:@Autowired既支持构造方法注入,又支持属性注入和Setter注入,而@Resource只支持属性注入和Setter注入。 5. 编译器IDEA的提示不同:当注入Mapper对象时,使用@Autowired注解编译器会提示错误,而使用@Resource注解则不会提示错误。\[1\] 总结来说,@Autowired和@Resource依赖注入的实现方式、参数设置和编译器提示等方面有所不同。具体使用哪个注解取决于项目需求和个人偏好。 #### 引用[.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^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [[spring学习]8、@Autowired和@Resource的区别](https://blog.csdn.net/m0_51545690/article/details/125244127)[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^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值