较真!@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}  是否真的只是按照名称来进行依赖注入
 *
 *
 * **/
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。

你可能感兴趣的:(Spring,Context,java,spring,spring,boot,ioc,bean)