我们常听别人说:“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”,那么这句话到底正不正确呢?
这里我先下个定论,“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”这句话正确也不正确。要怎么理解呢?@Resource注解是优先按照名称来进行依赖注入,但如果按名称找不到对应的Bean时,还是按类型来进行依赖注入;同样,当某个接口存在多个实现类,并且这些实现类都交给IoC容器管理,那么@Autowired也会尝试根据名称来进行依赖筛选。
第一步:首先定义一个接口: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} 是否真的只是按照名称来进行依赖注入
*
*
* **/
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} 是否真的只是按照名称来进行依赖注入
*
* *
*/
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) {
// 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()));
}
}
// 这里是解析多类型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。