Java Autowired注解深入分析

 更新时间:2023年01月31日 15:43:11   作者:风轻扬777  
@Autowired注解是Spring中非常重要且常见的,接下来就简要的介绍一下它的用法。@Autowired默认是通过set方法,按照类型自动装配JavaBean,set方法可省略不写,它主要是修饰在成员变量上

今天是正月初八,先祝大家新年快乐。前几天遇见了一次Autowired注入失败的问题,所以找时间研究了一下相关的Spring源码,分享一下。如果哪位大佬发现问题,请帮忙反馈。
分享之前,先给一个小建议。Spring源码庞大,其中的扩展点众多,贸然全篇吸收,很容易劝退。我的思路是想看哪部分知识,就只看和这部分相关的,其他细节,先放放,只捋主线,这样做,效率比较高。
下面进入正文。
我写了一个非常简单的web工程,便于调试源码。测试代码如下:
Controller代码

@RestController
public class HomeController {
    @Autowired
    private HomeService homeService;
    @RequestMapping("/index")
    public String index() {
        return homeService.testService();
    }
}

Service代码

//接口
public interface HomeService {
    String testService();
}
//实现类
@Service
public class HomeServiceImpl implements HomeService{
    @Override
    public String testService(){
        return "I'm a test service";
    }
}

我们的目的是:把HomeService通过Autowired注解注入HomeController中,从而在index方法中实现对HomeService方法的内部调用。
我们想探究的是:
为什么我们给字段加一个Autowired注解,Spring就知道需要给bean注入这个字段对应的服务呢?
想知道为什么Spring知道给HomeController中注入另外一个bean,我们肯定就得看HomeController,这个bean是怎么创建出来的。
直接看AbstractAutowireCapableBeanFactory方法的doCreateBean方法,里面是Spring创建bean的逻辑。有2个地方和Autowired有关系。
我去掉了无关逻辑,只留下了和Autowired有关系的逻辑
1、applyMergedBeanDefinitionPostProcessors()
2、populateBean(beanName, mbd, instanceWrapper)

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		//遍历所有的beanPostProcessor,寻找bean中被Autowired注解修饰的成员变量
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}
		Object exposedObject = bean;
		try {
			//给bean填充其他bean。对应到我们的demo,就是给HomeController注入HomeService。
			populateBean(beanName, mbd, instanceWrapper);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}
	}

applyMergedBeanDefinitionPostProcessors方法,这个方法的作用是寻找HomeController的字段里,有没有哪个字段添加了Autowired注解。

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
		for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
			processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
		}
	}

这段逻辑实际上是在遍历容器中的BeanPostProcessor,然后执行BeanPostProcessor中的逻辑,我们需要关注的是AutowiredAnnotationBeanPostProcessor,这个类是实现Autowired逻辑的核心类,大家重点关注。

@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}

可以看到逻辑很少。第一行代码就是找HomeController中被Autowired注解修饰的字段。
我们进入findAutowiringMetadata中看一下。其中有一个内部方法调用是buildAutowiringMetadata(clazz),我们再进入这个方法

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
			//寻找字段上有被Autowired注解修饰的字段
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
				//static修饰的静态字段不能进行注入
					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));
				}
			});
		return InjectionMetadata.forElements(elements, clazz);
	}

以上逻辑执行完,我们就拿到了待注入的homeService的元信息对象,即:InjectionMetadata。
上面逻辑中有一行代码,是判断字段是否被static修饰,如果是,不可以被注入。
小节一下。
绕了这么一圈,其实很简单。就是Spring在创建HomeController这个bean的时候,会检测其成员字段有没有被Autowired修饰。
对应到我们的demo代码,我们知道了HomeController中有一个被Autowired注解修饰的字段HomeService。
接下来,之后就是给HomeController注入这个HomeService。

populateBean(beanName, mbd, instanceWrapper);我们进入这个方法看一下。

重要逻辑是:pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
对应的实现类还是AutowiredAnnotationBeanPostProcessor。注入的核心逻辑是

@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		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;
	}

只有两行逻辑。
第一行:
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);这行代码,我们上面说到过,第一次执行时,是为了寻找被Autowired修饰字段的元信息。然后放入了缓存中。这一次再执行,我们就是要从缓存中拿到待注入的数据元信息InjectionMetadata。
第二行:
开始注入。
先看第一行代码的具体逻辑,获取待注入的数据元信息。

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
		//构建缓存key
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		//第一次执行,缓存中没有数据,先构建,然后放入缓存。第二次执行有数据,直接从缓存中获取
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					//第一次执行,缓存中不存在,先放入缓存。
					metadata = buildAutowiringMetadata(clazz);
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}

可以看到,上面有一个双检锁的设计,应该是防止并发情况下,多次创建InjectionMetadata对象。
再看第二行的具体逻辑,metadata.inject(bean, beanName, pvs);核心逻辑在AutowiredAnnotationBeanPostProcessor的626行,inject方法中。重点就是下面的代码。找到这个待注入的homeService对象,然后用反射注入到HomeController中。

@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			else {
			//到Spring容器中寻找待注入的bean
				value = resolveFieldValue(field, bean, beanName);
			}
			//找到之后,反射注入
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}

找的过程,就是先从Spring子容器中找,然后到Spring父容器中找,不详细说了,感兴趣的,可以继续跟进resolveFieldValue方法看。其实,大部分的Autowired注入失败,都是由于未在Spring容器中找到待注入的bean。所以Spring容器的加载过程很重要,一定要读读那部分源码。
至此,Autowired的主要执行流程,我们就分享完了。
总结下来就是:看bean中是否有被Autowired注解修饰的成员变量。如果有,从Spring容器中找到这个成员变量对应的bean,注入进去。

到此这篇关于Java Autowired注解深入分析的文章就介绍到这了,更多相关Java Autowired内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springcloud 服务降级的实现方法

    springcloud 服务降级的实现方法

    这篇文章主要介绍了springcloud 服务降级的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • spring-integration连接MQTT全过程

    spring-integration连接MQTT全过程

    这篇文章主要介绍了spring-integration连接MQTT全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java批量向PDF文件中添加图像水印实现细节

    Java批量向PDF文件中添加图像水印实现细节

    这篇文章主要为大家介绍了Java批量向PDF文件中添加图像水印实现细节,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • java中接口和事件监听器的深入理解

    java中接口和事件监听器的深入理解

    这篇文章主要给大家介绍了关于java中接口和事件监听器的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-12-12
  • java中为什么要谨慎使用Arrays.asList、ArrayList的subList

    java中为什么要谨慎使用Arrays.asList、ArrayList的subList

    这篇文章主要介绍了java中为什么要谨慎使用Arrays.asList、ArrayList的subList,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • Java异常分类处理抛出捕获操作详解

    Java异常分类处理抛出捕获操作详解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等
    2022-09-09
  • java实现微信公众号发送模版消息

    java实现微信公众号发送模版消息

    这篇文章以订单推送为例,主要为大家详细介绍了java实现微信公众号发送模版消息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • Java程序流程控制:判断结构、选择结构、循环结构原理与用法实例分析

    Java程序流程控制:判断结构、选择结构、循环结构原理与用法实例分析

    这篇文章主要介绍了Java程序流程控制:判断结构、选择结构、循环结构原理与用法,结合实例形式分析了Java流程控制中判断结构、选择结构、循环结构相关原理、用法及操作注意事项,需要的朋友可以参考下
    2020-04-04
  • spring boot加载资源路径配置和classpath问题解决

    spring boot加载资源路径配置和classpath问题解决

    这篇文章主要介绍了spring boot加载资源路径配置和classpath问题解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • 深入解析Java中的数据类型与变量

    深入解析Java中的数据类型与变量

    这篇文章主要介绍了深入解析Java中的数据类型与变量,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09

最新评论