Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》?

2023-10-29

@Autowired@Resource 属性注入分别是由 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 处理,本文将从源码出发,解析这两种注解对属性注入区别。

一、@Autowired

先看下 @Autowired 属性是如何注入的,AutowiredAnnotationBeanPostProcessor UML 类图如下
在这里插入图片描述

1. @Autowired / @Value / @Inject 注解发现

从图中可以看出,其实现 MergedBeanDefinitionPostProcessor 接口,在该接口 org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 方法实现中使用反射在 bean 的 Class 文件中查找被 @Autowired / @Value / @Inject 注解的 Field / Method,并将查找结果封装为 AutowiredFieldElement / AutowiredMethodElement 并缓存到 injectionMetadataCache 属性中,为了讨论方便,后文只关注 AutowiredFieldElement ,即 @Autowired 注解到成员变量上情况,
调用堆栈如下,

buildAutowiringMetadata:473, AutowiredAnnotationBeanPostProcessor // 实际查找功能在这里完成
findAutowiringMetadata:454, AutowiredAnnotationBeanPostProcessor // 处理缓存
postProcessMergedBeanDefinition:246, AutowiredAnnotationBeanPostProcessor
// 将 MergedBeanDefinitionPostProcessors 应用于指定的 BeanDefinition,调用它们的 {@code postProcessMergedBeanDefinition} 方法
applyMergedBeanDefinitionPostProcessors:1116, AbstractAutowireCapableBeanFactory
doCreateBean:594, AbstractAutowireCapableBeanFactory
createBean:542, AbstractAutowireCapableBeanFactory
lambda$doGetBean$0:335, AbstractBeanFactory
getObject:-1, 1241938981
getSingleton:234, DefaultSingletonBeanRegistry
doGetBean:333, AbstractBeanFactory
getBean:208, AbstractBeanFactory
preInstantiateSingletons:944, DefaultListableBeanFactory
finishBeanFactoryInitialization:918, AbstractApplicationContext
refresh:583, AbstractApplicationContext
refresh:144, ServletWebServerApplicationContext
refresh:771, SpringApplication
refresh:763, SpringApplication
refreshContext:438, SpringApplication
run:339, SpringApplication
run:1329, SpringApplication
run:1318, SpringApplication
main:69, Demo1Application
α. 发现原理 —— 反射

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata 方法源码,

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<>();

		// 遍历Class每个Field
		ReflectionUtils.doWithLocalFields(targetClass, field -> {
			// 查找 @Autowired 等注解
			MergedAnnotation<?> ann = findAutowiredAnnotation(field);
			if (ann != null) {
				// @Autowired / @Value / @Inject 不支持静态属性
				if (Modifier.isStatic(field.getModifiers())) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation is not supported on static fields: " + field);
					}
					return;
				}
				boolean required = determineRequiredStatus(ann);
				currElements.add(new AutowiredFieldElement(field, required));
			}
		});

		ReflectionUtils.doWithLocalMethods(targetClass, method -> {
			// 解决JDK 1.5后引入泛型后, 兼容之前字节码引入的桥接方法, 参考https://blog.csdn.net/ruizhige/article/details/120105227
			// 如果method是桥接方法, 则返回原方法, 否则直接返回method
			Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
			// 避免重复处理, 如果非桥接方法则直接返回
			if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
				return;
			}
			MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
			if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
				// @Autowired  / @Value / @Inject 不支持静态方法
				if (Modifier.isStatic(method.getModifiers())) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation is not supported on static methods: " + method);
					}
					return;
				}
				if (method.getParameterCount() == 0) {
					if (logger.isInfoEnabled()) {
						logger.info("Autowired annotation should only be used on methods with parameters: " +
								method);
					}
				}
				boolean required = determineRequiredStatus(ann);
				// Set方法设置的属性
				PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
				currElements.add(new AutowiredMethodElement(method, required, pd));
			}
		});

		elements.addAll(0, currElements);
		// 递归从继承树中查找
		targetClass = targetClass.getSuperclass();
	}
	while (targetClass != null && targetClass != Object.class);

	return InjectionMetadata.forElements(elements, clazz);
}
β. 发现时机

这里有几个地方需要注意

  • AutowiredAnnotationBeanPostProcessor 不仅可以处理 @Autowired, 还可以处理 @Value@Inject 注解;
  • org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 方法调用时机是 bean 刚实例化完成,org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation,属性赋值以及初始化都还没进行,在后续属性赋值时将用到缓存的 AutowiredFieldElement
  • AutowiredFieldElement 类图如下,
    在这里插入图片描述

2. 依赖注入

真正对@Autowired / @Value / @Inject 注解属性赋值逻辑在org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessProperties 方法实现。

α. 注入时机

org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessProperties调用时机在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 方法中,部分源码如下,

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	
	// ...

	// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
	// state of the bean before properties are set. This can be used, for example,
	// to support styles of field injection.
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
			// 如果有bp.postProcessAfterInstantiation返回false, 将会跳过后续逻辑, 即不会对 @Resource / @Autowired 等注解属性赋值
			// 可以在 bp.postProcessAfterInstantiation 中自定义属性赋值逻辑
			if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
				return;
			}
		}
	}

	// ...
	
	boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
	boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

	PropertyDescriptor[] filteredPds = null;
	if (hasInstAwareBpps) {
		if (pvs == null) {
			pvs = mbd.getPropertyValues();
		}
		for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
			// AutowiredAnnotationBeanPostProcessor 实现该方法对 @Autowired 等注解属性赋值
			// CommonAnnotationBeanPostProcessor 实现该方法对 @Resource 等注解属性赋值
			PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
			if (pvsToUse == null) {
				if (filteredPds == null) {
					filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
				}
				pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
				if (pvsToUse == null) {
					return;
				}
			}
			pvs = pvsToUse;
		}
	}
	// ...
}

这里需要注意,如果在 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 中自定义属性注入且返回 false,那么将跳过 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor@Autowired@Resource 注解属性赋值逻辑。
看下 AutowiredAnnotationBeanPostProcessorpostProcessProperties 实现,

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	// postProcessMergedBeanDefinition 已经完成对@Autowired注解查找, findAutowiringMetadata方法可以直接从缓存中获取
	// InjectionMetadata内部Collection<InjectedElement> injectedElements 字段包含所有AutowiredFieldElement
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
		metadata.inject(bean, beanName, pvs);
	}
	catch (BeanCreationException ex) {
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
	}
	return pvs;
}

org.springframework.beans.factory.annotation.InjectionMetadata#inject 循环调用属性 injectedElements 中所有 InjectedElementinject 方法,

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		// 这里element为InjectedElement实现类AutowiredFieldElement
		for (InjectedElement element : elementsToIterate) {
			element.inject(target, beanName, pvs);
		}
	}
}

所以真正 @Autowired 属性注入是通过 AutowiredFieldElement 完成的,看下相关源码,

private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {

	private final boolean required;

	private volatile boolean cached;

	@Nullable
	private volatile Object cachedFieldValue;

	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; // @Autowired 注解字段
		Object value;
		if (this.cached) {
			try {
				// prototype Bean 非首次创建, 属性赋值时会走到这里
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Unexpected removal of target bean for cached argument -> re-resolve
				value = resolveFieldValue(field, bean, beanName);
			}
		}
		else {
			value = resolveFieldValue(field, bean, beanName); // 解析 @Autowired 注解字段值
		}
		if (value != null) {
			ReflectionUtils.makeAccessible(field); // 使给定字段可访问, 以便给final字段赋值
			field.set(bean, value); // 反射设置属性值
		}
	}

	@Nullable
	private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
		DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
		desc.setContainingClass(bean.getClass());
		// 存储解析成功的Bean名称, 由于@Autowired可以给 collections / array 字段注入所有同类型Bean
		// 所以满足条件bean可能存在多个, 这里用Set存储
		Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
		Assert.state(beanFactory != null, "No BeanFactory available");
		TypeConverter typeConverter = beanFactory.getTypeConverter();
		Object value;
		try {
			// beanFactory -> DefaultListableBeanFactory 提供实现解析逻辑,
			value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
		}
		catch (BeansException ex) {
			throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
		}
		// prototype Bean可能同时创建, 所以需要保证缓存 cached 线程安全
		synchronized (this) {
			if (!this.cached) { // check
				Object cachedFieldValue = null;
				if (value != null || this.required) {
					cachedFieldValue = desc; // 默认
					// 向 beanFactory 注册 bean 之间依赖关系
					registerDependentBeans(beanName, autowiredBeanNames);
					if (autowiredBeanNames.size() == 1) { // 跳过 @Value等情况, @Value不依赖其他bean, 
						String autowiredBeanName = autowiredBeanNames.iterator().next();
						if (beanFactory.containsBean(autowiredBeanName) &&
								// collections / array 字段类型和bean类型不会匹配
								beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
							// 非 collections / array 字段注入
							cachedFieldValue = new ShortcutDependencyDescriptor(
									desc, autowiredBeanName, field.getType());
						}
					}
				}
				this.cachedFieldValue = cachedFieldValue;
				this.cached = true;
			}
		}
		return value;
	}
}
β. 依赖解析

可以看出,解析依赖 bean 功能主要由 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set<java.lang.String> 实现,其处理逻辑参考《Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析》。
从这篇博文可以看出,该方法解析依赖属于类型驱动,其大致步骤为先从 BeanFactory 中获取所有和 @Autowired 注解变量类型相同所有 bean 名称,然后再根据 限定符注解@Qualifier)/ autowireCandidate属性等条件筛选,如果变量非集合类型但是解析结果包含多个 bean 时,还可能根据 @Primary / 优先级等条件筛选一个。

二、@Resource

@Autowired 功能虽说非常强大,但是也有些不足之处。比如,它跟 Spring 强耦合了,如果换成了 JFinal 等其他框架,功能就会失效。而@Resource 是 JSR-250 提供的,它是 Java 标准,绝大部分框架都支持。
另外,他们作用范围也不同 —— @Resource 只能注解到类/成员变量/方法上,但 @Autowired 可以注解到构造器/方法/参数/成员变量/注解上。

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired { ... }

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource { ... }

@Resource 注解由 CommonAnnotationBeanPostProcessor 处理, 看下该类 UML 类图(为了简洁省去了部分不重要继承关系),在这里插入图片描述AutowiredAnnotationBeanPostProcessor 类似, CommonAnnotationBeanPostProcessor 同样也实现了MergedBeanDefinitionPostProcessor 接口,在该接口 org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 方法中发现 javax.xml.ws.WebServiceRef / javax.ejb.EJB / Resource 注解,本文只讨论 Resource 注解。和 AutowiredAnnotationBeanPostProcessor 不同的是,发现 Resource 注解后, CommonAnnotationBeanPostProcessor 会把标有该注解的成员变量或方法统一封装成 ResourceElement

private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
	if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
		return InjectionMetadata.EMPTY;
	}

	List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
	Class<?> targetClass = clazz;

	do {
		final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

		ReflectionUtils.doWithLocalFields(targetClass, field -> { // 在成员变量中查找注解
			if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {
				// ... @WebServiceRef
			}
			else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
				// ... @EJB
			}
			else if (field.isAnnotationPresent(Resource.class)) {
				if (Modifier.isStatic(field.getModifiers())) { // @Resource同样不支持静态成员变量
					throw new IllegalStateException("@Resource annotation is not supported on static fields");
				}
				if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
					// 将该成员变量封装为ResourceElement
					currElements.add(new ResourceElement(field, field, null)); 
				}
			}
		});

		ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 在方法中查找
			Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
			if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
				return;
			}
			if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
				if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
					// ... @WebServiceRef
				}
				else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
					// ... @EJB
				}
				else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
					if (Modifier.isStatic(method.getModifiers())) {
						throw new IllegalStateException("@Resource annotation is not supported on static methods");
					}
					Class<?>[] paramTypes = method.getParameterTypes();
					if (paramTypes.length != 1) {
						throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
					}
					if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
						PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
						// 将方法及其桥接方法和PropertyDescriptor封装为ResourceElement
						currElements.add(new ResourceElement(method, bridgedMethod, pd));
					}
				}
			}
		});

		elements.addAll(0, currElements);
		targetClass = targetClass.getSuperclass(); // 递归处理继承树
	}
	while (targetClass != null && targetClass != Object.class);

	return InjectionMetadata.forElements(elements, clazz);
}

看下 ResourceElement
在这里插入图片描述

private class ResourceElement extends LookupElement {

		private final boolean lazyLookup;

		public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
			super(member, pd);
			Resource resource = ae.getAnnotation(Resource.class); // 从成员变量/方法中获取@Resource注解
			// @Resource可以通过name和type属性指定依赖bean名称和类型
			String resourceName = resource.name();
			Class<?> resourceType = resource.type();
			// 如果没有指定依赖bean名称, 则使用默认名称
			this.isDefaultName = !StringUtils.hasLength(resourceName); 
			if (this.isDefaultName) {
				// 默认依赖bean名称取成员变量/方法名称
				resourceName = this.member.getName();
				if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { 
					// 方法名需要去掉开头"set", 并将剩下首字符小写
					resourceName = Introspector.decapitalize(resourceName.substring(3));
				}
			}
			else if (embeddedValueResolver != null) {
				resourceName = embeddedValueResolver.resolveStringValue(resourceName);
			}
			if (Object.class != resourceType) {
				// 指定类型要能赋值给待注入变量, 即指定类型需要和待注入变量类型相同, 或为其子类(父类引用指向子类对象)
				checkResourceType(resourceType);
			}
			else {
				// No resource type specified... check field/method.
				// 未指定, 则注入bean类型为成员变量类型/setter函数设置的成员变量类型/函数第一个参数类型
				resourceType = getResourceType();
			}
			this.name = (resourceName != null ? resourceName : "");
			this.lookupType = resourceType;
			String lookupValue = resource.lookup();
			this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
			Lazy lazy = ae.getAnnotation(Lazy.class);
			this.lazyLookup = (lazy != null && lazy.value());
		}

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

同样 CommonAnnotationBeanPostProcessor 会在 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties 方法中进行属性赋值,其处理逻辑和 AutowiredAnnotationBeanPostProcessor 类似,这里不再赘述。该方法最终会调用到 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject 获取注入变量值,该方法最终又会调用到 org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource

// element为由@Resource注解成员变量/方法封装而成的ResourceElement
// requestingBeanName为@Resource注解成员变量/方法所属bean
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
		throws NoSuchBeanDefinitionException {

	Object resource;
	Set<String> autowiredBeanNames;
	String name = element.name;
	// class org.springframework.beans.factory.support.DefaultListableBeanFactory继承AbstractAutowireCapableBeanFactory类
	// AbstractAutowireCapableBeanFactory类实现AutowireCapableBeanFactory接口
	if (factory instanceof AutowireCapableBeanFactory) {
		AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
		DependencyDescriptor descriptor = element.getDependencyDescriptor();
		// fallbackToDefaultTypeMatch默认为true
		// 如果@Resource未指定依赖bean名称(此时name默认取成员变量名/方法名)且BeanFactory中不包含默认bean名称
		// 此时和@Autowired一样,通过类型驱动查找依赖bean
		if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
			autowiredBeanNames = new LinkedHashSet<>();
			resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
			if (resource == null) {
				throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
			}
		}
		else {
			// 名称驱动查找依赖bean
			// 不会校验resource和待注入变量类型是否一致,如果不一致,反射赋值时会报错
			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;
}

从以上源码可以看出,@Resource 在以下情况下是通过名称查找 bean,

  • @Resource 注解通过 name 属性指定依赖bean名称;
  • 未指定 bean 名称,但默认 bean 名称(成员变量名/去 "set" 前缀及首字母小写方法名)所对应 bean 在 BeanFactory 中存在;

通过名称查找 bean 方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName 参考《Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析》。

三、总结

根据前文内容,先将 @Resource@Autowired 使用异同总结如下,

1. 功能相同情况

其实,如果 @Resource 未指定依赖 bean 名称且 BeanFactory 中不包含默认 bean 名称所对应 bean,则 @Resource@Autowired 注解 required 属性为 true 时处理依赖注入逻辑一样,均为类型驱动注入,也可以使用限定符注解@Qualifier)。
例如,有如下限定符注解

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier()
public @interface CustomizedQualifier {
    String property() default "";
}

通过 Configuration 配置类向 Spring IOC 容器中注入如下 bean,

@Configuration
public class AppConfig {
    @Bean("b")
    @CustomizedQualifier(property = "foo")
    public B b(){
        return new B();
    }

    @Bean("b1")
    @CustomizedQualifier(property = "bar")
    public B b1(){
        return new B();
    }
}

bean A 中依赖注入 bean b,

@Component("A")
public class A implements InitializingBean{

	// 报错, Bean named 'b' is expected to be of type 'java.util.Map' but was actually of type 'B'
	// 虽然未在@Resource注解中指定依赖bean名称, 但是BeanFactory中存在类型默认名称为"b"的bean, 但是类型不匹配
	// 这里还是通过bean名称查找bean
    @Resource
    @CustomizedQualifier(property = "foo")
    private Map<String,B> b;

    @Override public void afterPropertiesSet() throws Exception {
        b.keySet().forEach(System.err::println);
    }
}

接下来修改成员变量名 "b""bb",此时 BeanFactory 中不存在名称为 "bb" 的 bean,则按照类型驱动注入,和使用 @Autowired 处理依赖注入逻辑一样,

@Component("A")
public class A implements InitializingBean{

    // 只有bean b注入进来, 说明限定符注解生效
    @Resource
    @CustomizedQualifier(property = "foo")
    private Map<String,B> bb;

    @Override public void afterPropertiesSet() throws Exception {
        // b
        bb.keySet().forEach(System.err::println);
    }
}

2. 区别

除以上情况外, @Resource@Autowired 主要有以下区别,

α. 解析依赖方式不同

这几乎是它们之间最主要差别,@Autowired 属于类型驱动解析, @Autowired 属于名称驱动解析,关于这两种解析具体详细逻辑,参考《Spring Boot v2.4.4源码解析(十)依赖注入原理下 —— 依赖解析》;

β. 注解作用范围不同

@Resource 只能注解到类/成员变量/方法上,但 @Autowired 可以注解到构造器/方法/参数/成员变量/注解上;

γ. 出处不同

@Autowired 是 Spring 定义的注解,而 @Resource 是 JSR-250 定义的注解,@Autowired只能在 Spring 框架下使用,而 @Resource 则可以与其他框架一起使用;

δ. 属性不同

@Autowired 只包含一个属性 —— required,默认 true,表示该依赖是否必须,如果该依赖必须但解析不到同类型 bean 时会报错。
@Resource 包含七个属性,其具体含义参考注释,

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    /**
     * The JNDI name of the resource.  For field annotations,
     * the default is the field name.  For method annotations,
     * the default is the JavaBeans property name corresponding
     * to the method.  For class annotations, there is no default
     * and this must be specified.
     */
    String name() default "";

    /**
     * The name of the resource that the reference points to. It can
     * link to any compatible resource using the global JNDI names.
     *
     * @since Common Annotations 1.1
     */

    String lookup() default "";

    /**
     * The Java type of the resource.  For field annotations,
     * the default is the type of the field.  For method annotations,
     * the default is the type of the JavaBeans property.
     * For class annotations, there is no default and this must be
     * specified.
     */
    Class<?> type() default java.lang.Object.class;

    /**
     * The two possible authentication types for a resource.
     */
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }

    /**
     * The authentication type to use for this resource.
     * This may be specified for resources representing a
     * connection factory of any supported type, and must
     * not be specified for resources of other types.
     */
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;

    /**
     * Indicates whether this resource can be shared between
     * this component and other components.
     * This may be specified for resources representing a
     * connection factory of any supported type, and must
     * not be specified for resources of other types.
     */
    boolean shareable() default true;

    /**
     * A product specific name that this resource should be mapped to.
     * The name of this resource, as defined by the <code>name</code>
     * element or defaulted, is a name that is local to the application
     * component using the resource.  (It's a name in the JNDI
     * <code>java:comp/env</code> namespace.)  Many application servers
     * provide a way to map these local names to names of resources
     * known to the application server.  This mapped name is often a
     * <i>global</i> JNDI name, but may be a name of any form. <p>
     *
     * Application servers are not required to support any particular
     * form or type of mapped name, nor the ability to use mapped names.
     * The mapped name is product-dependent and often installation-dependent.
     * No use of a mapped name is portable.
     */
    String mappedName() default "";

    /**
     * Description of this resource.  The description is expected
     * to be in the default language of the system on which the
     * application is deployed.  The description can be presented
     * to the Deployer to help in choosing the correct resource.
     */
    String description() default "";
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》? 的相关文章

  • 在 Java 中连接和使用 Cassandra

    我已经阅读了一些关于 Cassandra 是什么以及它可以做什么的教程 但我的问题是如何在 Java 中与 Cassandra 交互 教程会很好 如果可能的话 有人可以告诉我是否应该使用 Thrift 还是 Hector 哪一个更好以及为什
  • Java new Date() 打印

    刚刚学习 Java 我知道这可能听起来很愚蠢 但我不得不问 System out print new Date 我知道参数中的任何内容都会转换为字符串 最终值是 new Date 返回对 Date 对象的引用 那么它是如何打印这个的呢 Mo
  • Spring Batch 多线程 - 如何使每个线程读取唯一的记录?

    这个问题在很多论坛上都被问过很多次了 但我没有看到适合我的答案 我正在尝试在我的 Spring Batch 实现中实现多线程步骤 有一个包含 100k 条记录的临时表 想要在 10 个线程中处理它 每个线程的提交间隔为 300 因此在任何时
  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • Java EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • 多个 Maven 配置文件激活多个 Spring 配置文件

    我想在 Maven 中构建一个环境 在其中我想根据哪些 Maven 配置文件处于活动状态来累积激活多个 spring 配置文件 目前我的 pom xml 的相关部分如下所示
  • Spring Data JPA 应用排序、分页以及 where 子句

    我目前正在使用 Spring JPA 并利用此处所述的排序和分页 如何通过Spring data JPA通过排序和可分页查询数据 https stackoverflow com questions 10527124 how to query
  • 磁模拟

    假设我在 n m 像素的 2D 表面上有 p 个节点 我希望这些节点相互吸引 使得它们相距越远吸引力就越强 但是 如果两个节点之间的距离 比如 d A B 小于某个阈值 比如 k 那么它们就会开始排斥 谁能让我开始编写一些关于如何随时间更新
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • Eclipse Java 远程调试器通过 VPN 速度极慢

    我有时被迫离开办公室工作 这意味着我需要通过 VPN 进入我的实验室 我注意到在这种情况下使用 Eclipse 进行远程调试速度非常慢 速度慢到调试器需要 5 7 分钟才能连接到远程 jvm 连接后 每次单步执行断点 行可能需要 20 30
  • Java执行器服务线程池[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果我使用 Executor 框架在
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will

随机推荐

  • 分布式事务有这一篇就够了!

    分布式事务 文章目录 分布式事务 1 基础概念 1 1 什么是事务 1 2 本地事务 1 3 分布式事务 1 4 分布式事务产生的情景 2 分布式事务基础理论 2 1 CAP理论 2 1 1 理解CAP C Consistency A Av
  • 盒子的宽度超出元素的盒子的解决办法box-sizing

    padding用在哪里超出的 box sizing就放在哪 盒子的宽度超出元素的盒子的解决办法 未修改的代码与样式 rpx与px都是单位 如果不了解就当px处理 1 calc 减轻相当于两侧的padding值 不推荐 了解 2 border
  • 计算机专业毕业设计选题原则

    计算机技术变化很快 新技术新观念每年都会涌现很多 计算机专业的毕业设计 是一次非常好的把理论知识结合实践的好机会 所以 选好自己的毕业设计题目 相当重要 我们建议 你要结合自己的职业理想来做 1 如果你将来想从事信息系统类开发 建议选择C
  • SpringBoot项目整合RabbitMQ

    1 简介 消息队列 Message Queue 是分布式系统中常用的组件 它允许不同的应用程序之间通过发送和接收消息进行通信 Spring Boot提供了简单且强大的方式来整合消息队列 其中包括RabbitMQ ActiveMQ Kafka
  • openwrt控制天翼网关定时重启

    由于天翼网关不支持定时重启 另外app里设置定时重启也没有作用 所以查找了一些相关工具和用法 再结合前辈的经验 参考的博客网址 自己最终把该功能实现了 首先需要用winscp登陆到刷了openwrt的路由器中 然后在 usr bin 目录下
  • CC2530学习(一)环境配置

    CC2530F256是一款将各种丰富的功能系统地集成到一片LSI Large Scale Integrated circuit 大规模集成电路 的片上系统 System on Chip 简称SoC 内嵌增强型51内核的单片机 芯片后缀256
  • docker安装redis(镜像安装)

    目录 安装 1 1 安装redis镜像 2 查看redis镜像 3 运行容器 4 查看容器安装成功 安装2 1 安装redis镜像 其中latest是镜像版本 根据程序需要 选择适合的版本 2 新建data和conf两个文件夹 位置随意 3
  • frida native hook简单demo

    记录frida hook native的笔记 整体代码如下 Java perform function console log Inside java perform function function jstring2Str jstrin
  • Basic Level 1027 打印沙漏 (20分)

    题目 本题要求你写个程序把给定的符号打印成沙漏的形状 例如给定17个 要求按下列格式打印 所谓 沙漏形状 是指每行输出奇数个符号 各行符号中心对齐 相邻两行符号数差2 符号数先从大到小顺序递减到1 再从小到大顺序递增 首尾符号数相等 给定任
  • pacmaker与corosync实现高可用集群

    一 pacmaker与corosync的简单介绍 1 pacmaker pacemaker是一个开源的高可用资源管理器 CRM 位于HA集群架构中资源管理 资源代理 RA 这个层次 它不能提供底层心跳信息传递的功能 要想与对方节点通信需要借
  • gtest学习笔记(二)gtest自带的sample1---Func函数使用

    文章目录 前言 源码学习 utest语法 运行 前言 第一章中已经编译出自带的sample例子 在build googletest目录下可以看到sample的各种例子的可执行程序 Google Test 附带了10个单元测试用例 难度由浅及
  • Android logcat解析与问题分析

    logcat是Android中一个命令行工具 可以用于得到程序的log信息 一 logcat 结构 1 这个结构在AS或者exlipse上面看的更清楚 二 logcat中地址反差backtrace 当程序崩溃的时候会在loacat中打印出b
  • 最左前缀原则、覆盖索引、索引下推、联合索引的概念

    最左前缀原则 什么是最左匹配原则 举个例子 比如有abc组成的联合索引 能走的索引可以是a ab abc 不能是bc这样的 如果条件上先b后c 那么就是全表查询 不走索引了 可以使联合索引的最左边的N个字段 也可以是字符串的前m个字符 覆盖
  • 用matlab写一段时间序列预测

    我可以为你提供一些指引 但具体的实现方法还需要你自己去探索 首先 你可以使用Matlab的时间序列函数来分析数据 从而找出特征 这些特征可以帮助预测未来的数据 其次 你可以使用一些机器学习技术 如回归分析 贝叶斯网络和神经网络 来构建时间序
  • 06_在机器学习中,为什么使用均方误差而不使用绝对值误差

    问题背景 为什么机器学习在解决回归问题的时候一般使用的是平方损失 均方损失 问题分析 损失函数是衡量模型预测结果与真实结果之间的一种距离度量 可以计算出每一个样本预测值与其真实值之间的距离 全部加起来就得到了所谓的损失函数 而距离的度量是没
  • ORACLE触发器详解

    本文对原文做了格式和目录调整方便阅读 文章总结的很全面 感谢原作的辛勤付出 原文链接 http www cnblogs com huyong archive 2011 04 27 2030466 html comments ORACLE P
  • 华为交换机怎么查看接口流量

    今天我们整理了华为交换机的监控命令 其中比较重要的几个命令我记一下 也方便能看到这篇文章的同学 首先得ssh登录到设备终端或者用sshpass命令 display current configuration 显示当前配置 display i
  • 输入10个数,求它们的平均值,并输出大于平均值的数据的个数

    题目描述 输入10个数 求它们的平均值 并输出大于平均值的数据的个数 输入 10个数 输出 大于平均数的个数 样例输入 1 2 3 4 5 6 7 8 9 10 样例输出 5 include
  • mqtt协议调用示例(包括MQTT一键启动服务+测试工具 MQTTFX云盘下载),对捷顺门禁温感一体机进行人员信息下发

    hello 大家好 我是一只不是在戏精 就是在戏精路上的极品二哈 新年上班第一天 给大家贡献一篇 MQTT 协议使用示例文章 也是本汪自己的一篇实用笔记 本汪先总的说下 MQTT协议进行数据交互 一共有两种方式 第一种 请求时不带任何参数的
  • Spring Boot v2.4.4源码解析(八)依赖注入原理上 —— 由一道面试题引起的思考《@Autowired和@Resource的区别》?

    Autowired 和 Resource 属性注入分别是由 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 处理 本文将从源码出发 解析这两种