0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

@Autowired的使用及源码分析

Android编程精选 来源:CSDN技术社区 作者:CSDN技术社区 2022-06-14 17:04 次阅读
@Autowired使用

构造函数注入

publicClassOuter{
privateInnerinner;
@Autowired
publicOuter(Innerinner){
this.inner=inner;
}
}

属性注入

publicClassOuter{
@Autowired
privateInnerinner;
}

方法注入

publicClassOuter{
privateInnerinner;
publicInnergetInner(){
returninner;
}
@Autowired
publicvoidsetInner(Innerinner){
this.inner=inner;
}
}

目前绝大部分的代码都使用第2、第3种。第1种在bean实例化时完成,而第2、第3种的实现原理都是一样的,在属性填充时完成。本篇将介绍第二第三种的是实现原理

在开始之前,如果我们自己设计@Autowired,我们应该怎么实现?我想做法还是比较简单的

  • 通过反射查找bean的class下所有注解了@Autowired的字段和方法
  • 获取到字段,通过getBean(字段)获取到对应bean,然后再通过反射调用field的set将bean注入

@Autowired源码分析

AutowiredAnnotationBeanPostProcessor

该类是@Autowired的具体实现类,先预览一下类方法

c1d7d10e-e657-11ec-ba43-dac502259ad0.png

发现实际有机会介入bean的创建操作只有可能是后置处理器,用于后置处理的有3个方法,其中一个过时不用,分别是postProcessMergedBeanDefinitionpostProcessProperties后置处理,我们再看一下这2个方法的具体代码

publicclassAutowiredAnnotationBeanPostProcessorextendsInstantiationAwareBeanPostProcessorAdapter
implementsMergedBeanDefinitionPostProcessor,PriorityOrdered,BeanFactoryAware{

...

@Override
publicvoidpostProcessMergedBeanDefinition(RootBeanDefinitionbeanDefinition,ClassbeanType,StringbeanName){
//1.寻找bean中所有被@Autowired注释的属性,并将属性封装成InjectedElement类型
InjectionMetadatametadata=findAutowiringMetadata(beanName,beanType,null);
metadata.checkConfigMembers(beanDefinition);
}

...

@Override
publicPropertyValuespostProcessProperties(PropertyValuespvs,Objectbean,StringbeanName){
//1.寻找通过@Autowired注解的属性或者方法
InjectionMetadatametadata=findAutowiringMetadata(beanName,bean.getClass(),pvs);
try{
//2.注入
metadata.inject(bean,beanName,pvs);
}
catch(BeanCreationExceptionex){
throwex;
}
catch(Throwableex){
thrownewBeanCreationException(beanName,"Injectionofautowireddependenciesfailed",ex);
}
returnpvs;
}

...
}

跟我们的猜想是一样的,首先先找出所有注解了@Autowired的属性或者方法,然后进行注入,当然postProcessMergedBeanDefinition后置处理器的调用肯定是在postProcessProperties之前的,这里我们回顾一下spring bean的创建过程。

2个处理器我已用黄色标出

c2169da8-e657-11ec-ba43-dac502259ad0.png

1. 查找所有@Autowired

//寻找bean中所有被@Autowired注释的属性,并将属性封装成InjectedElement类型
InjectionMetadatametadata=findAutowiringMetadata(beanName,beanType,null);
privateInjectionMetadatafindAutowiringMetadata(StringbeanName,Classclazz,@NullablePropertyValuespvs){
//Fallbacktoclassnameascachekey,forbackwardscompatibilitywithcustomcallers.
//获取缓存的key值,一般以beanName做key
StringcacheKey=(StringUtils.hasLength(beanName)?beanName:clazz.getName());
//Quickcheckontheconcurrentmapfirst,withminimallocking.
//从缓存中获取metadata
InjectionMetadatametadata=this.injectionMetadataCache.get(cacheKey);
//检测metadata是否需要更新
if(InjectionMetadata.needsRefresh(metadata,clazz)){
synchronized(this.injectionMetadataCache){
metadata=this.injectionMetadataCache.get(cacheKey);
if(InjectionMetadata.needsRefresh(metadata,clazz)){
if(metadata!=null){
metadata.clear(pvs);
}
//通过clazz类,查找所有@Autowired的属性或者方法,并封装成InjectionMetadata类型
metadata=buildAutowiringMetadata(clazz);
//将metadata加入缓存
this.injectionMetadataCache.put(cacheKey,metadata);
}
}
}
returnmetadata;
}

可以看到spring依然在用缓存的方式提高性能,继续跟踪核心代码buildAutowiringMetadata(clazz)

privateInjectionMetadatabuildAutowiringMetadata(finalClassclazz){
//查看clazz是否有Autowired注解
if(!AnnotationUtils.isCandidateClass(clazz,this.autowiredAnnotationTypes)){
returnInjectionMetadata.EMPTY;
}
//这里需要注意AutowiredFieldElement,AutowiredMethodElement均继承了InjectionMetadata.InjectedElement
//因此这个列表是可以保存注解的属性和被注解的方法的
Listelements=newArrayList<>();
ClasstargetClass=clazz;

//1.通过dowhile循环,递归的往直接继承的父类寻找@Autowired
do{
finalListcurrElements=newArrayList<>();

//2.通过反射,获取所有属性,doWithLocalFields则是循环的对每个属性应用以下匿名方法
ReflectionUtils.doWithLocalFields(targetClass,field->{
//判断当前field属性是否含有@Autowired的注解
MergedAnnotationann=findAutowiredAnnotation(field);
if(ann!=null){
//返回该属性在类中的修饰符,如果等于static常量,则抛出异常,@Autowired不允许注解在静态属性上
if(Modifier.isStatic(field.getModifiers())){
if(logger.isInfoEnabled()){
logger.info("Autowiredannotationisnotsupportedonstaticfields:"+field);
}
return;
}
//@Autowired有required属性,获取required的值,默认为true
booleanrequired=determineRequiredStatus(ann);
//3.将field封装成InjectedElement,并添加到集合中,这里用的是AutowiredFieldElement
currElements.add(newAutowiredFieldElement(field,required));
}
});

//4.@Autowired可以注解在方法上
ReflectionUtils.doWithLocalMethods(targetClass,method->{
MethodbridgedMethod=BridgeMethodResolver.findBridgedMethod(method);
if(!BridgeMethodResolver.isVisibilityBridgeMethodPair(method,bridgedMethod)){
return;
}
MergedAnnotationann=findAutowiredAnnotation(bridgedMethod);
if(ann!=null&&method.equals(ClassUtils.getMostSpecificMethod(method,clazz))){
if(Modifier.isStatic(method.getModifiers())){
if(logger.isInfoEnabled()){
logger.info("Autowiredannotationisnotsupportedonstaticmethods:"+method);
}
return;
}
if(method.getParameterCount()==0){
if(logger.isInfoEnabled()){
logger.info("Autowiredannotationshouldonlybeusedonmethodswithparameters:"+
method);
}
}
booleanrequired=determineRequiredStatus(ann);
PropertyDescriptorpd=BeanUtils.findPropertyForMethod(bridgedMethod,clazz);
//5.将方法封装成InjectedElement,并添加到集合中,这里用的是AutowiredMethodElement
currElements.add(newAutowiredMethodElement(method,required,pd));
}
});

elements.addAll(0,currElements);
//返回直接继承的父类
targetClass=targetClass.getSuperclass();
}
//如果父类不为空则需要把父类的@Autowired属性或方法也找出
while(targetClass!=null&&targetClass!=Object.class);
//6.newInjectionMetadata(clazz,elements),将找到的所有的待注入属性或方法生成metadata返回
returnInjectionMetadata.forElements(elements,clazz);
}
  • 外层 do … while … 的循环被用于递归的查找父类的@Autowired属性或方法

  • 通过反射的方式获取到所有属性并循环验证每一个属性是否被@Autowired注解

  • 将查找到包含@Autowired注解的filed封装成AutowiredFieldElement,加入到列表中

  • 循环查找在方法上的注解

  • 将找到的方法封装成AutowiredMethodElement,并加入列表

这里需要特别强调一点,InjectedElementAutowiredFieldElementAutowiredMethodElement所继承,他们都有各自的inject函数,实现各自的注入。因此改ArrayList elements是拥有2种类型的属性

c24a2466-e657-11ec-ba43-dac502259ad0.png
  • 将找到的所有元素列表和clazz作为参数生成metadata数据返回

2. 注入

//注入
metadata.inject(bean,beanName,pvs);
publicvoidinject(Objecttarget,@NullableStringbeanName,@NullablePropertyValuespvs)throwsThrowable{
//获取所有需要被注入的元素
CollectioncheckedElements=this.checkedElements;
CollectionelementsToIterate=
(checkedElements!=null?checkedElements:this.injectedElements);
//迭代的元素不为空
if(!elementsToIterate.isEmpty()){
for(InjectedElementelement:elementsToIterate){
if(logger.isTraceEnabled()){
logger.trace("Processinginjectedelementofbean'"+beanName+"':"+element);
}
//循环注入,这里有可能是AutowiredFieldElement也可能AutowiredMethodElement,因此调用的inject是2个不同的方法
element.inject(target,beanName,pvs);
}
}
}

利用for循环,遍历刚刚我们查到到的elements列表,进行注入。

在上面有特别提醒,这里的element有可能是AutowiredFieldElement类型、或AutowiredMethodElement类型。各自代表@Autowired注解在属性上、以及注解在方法上的2种不同元素。因此他们调用的element.inject(target, beanName, pvs);也是不一样的

2.1 字段注入(AutowiredFieldElement)
privateclassAutowiredFieldElementextendsInjectionMetadata.InjectedElement{
@Override
protectedvoidinject(Objectbean,@NullableStringbeanName,@NullablePropertyValuespvs)throwsThrowable{
Fieldfield=(Field)this.member;
Objectvalue;
if(this.cached){
value=resolvedCachedArgument(beanName,this.cachedFieldValue);
}
else{
//专门用于注入的包装类,包装构造函数参数,方法参数或字段
DependencyDescriptordesc=newDependencyDescriptor(field,this.required);
//设置class
desc.setContainingClass(bean.getClass());
//需要被自动注入的beanNames,这里只有可能=1,方法注入时才有可能为多个
SetautowiredBeanNames=newLinkedHashSet<>(1);
Assert.state(beanFactory!=null,"NoBeanFactoryavailable");
TypeConvertertypeConverter=beanFactory.getTypeConverter();//获取类型转换器
try{
//通过beanFactory获取属性对应的值,比如需要调用getBean("b")获取依赖的属性单例,并且通过自动转型转为需要的类型
value=beanFactory.resolveDependency(desc,beanName,autowiredBeanNames,typeConverter);
}
catch(BeansExceptionex){
thrownewUnsatisfiedDependencyException(null,beanName,newInjectionPoint(field),ex);
}
synchronized(this){
if(!this.cached){
if(value!=null||this.required){
this.cachedFieldValue=desc;
//注册依赖,
registerDependentBeans(beanName,autowiredBeanNames);
//因为是属性注入,因此这里只有可能等于1
if(autowiredBeanNames.size()==1){
StringautowiredBeanName=autowiredBeanNames.iterator().next();
if(beanFactory.containsBean(autowiredBeanName)&&
beanFactory.isTypeMatch(autowiredBeanName,field.getType())){
//缓存当前value
this.cachedFieldValue=newShortcutDependencyDescriptor(
desc,autowiredBeanName,field.getType());
}
}
}
else{
this.cachedFieldValue=null;
}
this.cached=true;
}
}
}
if(value!=null){
//通过反射,将value值设置到bean中
ReflectionUtils.makeAccessible(field);
field.set(bean,value);
}
}
}

上方大部分的工作都在做待注入bean的获取以及类型的转换,如果深究下去可以再把spring Ioc讲一遍,但是核心还是getBean(字段)获取到对应bean…我们这里就关心核心的语句,就是这2句

if(value!=null){
//通过反射,将value值设置到bean中
ReflectionUtils.makeAccessible(field);
field.set(bean,value);
}

spring通过反射的方式,调用field的set进行属性的注入

2.2 方法注入(AutowiredMethodElement)
privateclassAutowiredMethodElementextendsInjectionMetadata.InjectedElement{


@Override
protectedvoidinject(Objectbean,@NullableStringbeanName,@NullablePropertyValuespvs)throwsThrowable{
if(checkPropertySkipping(pvs)){
return;
}
//@Autowired标注在方法上
Methodmethod=(Method)this.member;
Object[]arguments;
if(this.cached){
//Shortcutforavoidingsynchronization...
//有缓存
arguments=resolveCachedArguments(beanName);
}
else{
//没缓存,直接获取方法上所有的参数
intargumentCount=method.getParameterCount();
arguments=newObject[argumentCount];
DependencyDescriptor[]descriptors=newDependencyDescriptor[argumentCount];
SetautowiredBeans=newLinkedHashSet<>(argumentCount);
Assert.state(beanFactory!=null,"NoBeanFactoryavailable");
TypeConvertertypeConverter=beanFactory.getTypeConverter();
//循环所有参数
for(inti=0;i< arguments.length; i++) {
     MethodParameter methodParam = newMethodParameter(method,i);
DependencyDescriptorcurrDesc=newDependencyDescriptor(methodParam,this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i]=currDesc;
try{
//通过beanFactory,获取代注入的bean,并进行类型转换
Objectarg=beanFactory.resolveDependency(currDesc,beanName,autowiredBeans,typeConverter);
if(arg==null&&!this.required){
arguments=null;
break;
}
arguments[i]=arg;
}
catch(BeansExceptionex){
thrownewUnsatisfiedDependencyException(null,beanName,newInjectionPoint(methodParam),ex);
}
}
synchronized(this){
if(!this.cached){
if(arguments!=null){
DependencyDescriptor[]cachedMethodArguments=Arrays.copyOf(descriptors,arguments.length);
//注册依赖
registerDependentBeans(beanName,autowiredBeans);
//如果自动注入的个数=参数个数,则缓存
if(autowiredBeans.size()==argumentCount){
Iteratorit=autowiredBeans.iterator();
Class[]paramTypes=method.getParameterTypes();
for(inti=0;i< paramTypes.length; i++) {
         String autowiredBeanName = it.next();
         if(beanFactory.containsBean(autowiredBeanName)&&
beanFactory.isTypeMatch(autowiredBeanName,paramTypes[i])){
//缓存
cachedMethodArguments[i]=newShortcutDependencyDescriptor(
descriptors[i],autowiredBeanName,paramTypes[i]);
}
}
}
//缓存方法
this.cachedMethodArguments=cachedMethodArguments;
}
else{
this.cachedMethodArguments=null;
}
this.cached=true;
}
}
}
if(arguments!=null){
try{
//反射调用注入方法,将获取到的所有bean作为参数
ReflectionUtils.makeAccessible(method);
method.invoke(bean,arguments);
}
catch(InvocationTargetExceptionex){
throwex.getTargetException();
}
}
}

}

这里与属性注入最大的区别在于,@Autowired注解在方法上,方法可以拥有多个参数,因此这里需要通过循环将一个个获取,而获取bean的方式于上面一样,本质都是通过getBean获取。

而核心语句还是2句

//反射调用注入方法,将获取到的所有bean作为参数
ReflectionUtils.makeAccessible(method);
method.invoke(bean,arguments);

与属性注入不同的是,当@Autowired注解在方法上,例如我们注解在setter方法上,则只需要直接调用该setter方法将参数数组传入即可以,即使用invoke触发方法,具体属性赋值的过程在setter方法中由用户自行编写

原文标题:谈谈 @Autowired 的实现原理

文章出处:【微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 源码
    +关注

    关注

    8

    文章

    571

    浏览量

    28573
  • 注解
    +关注

    关注

    0

    文章

    18

    浏览量

    2635

原文标题:谈谈 @Autowired 的实现原理

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    分享主成分分析源码

    一个主成分分析源码
    发表于 05-13 11:00

    u-boot源码分析与移植 适合初学者

    u-boot源码分析与移植u-boot源码分析与移植
    发表于 05-21 12:57

    Linux内核源码之我见——内核源码分析方法

    的代码高手。透过阅读Linux内核代码的方式,我们学习到的不光是内核相关的知识,在我看来更具价值的是学习和体会它们的编程技巧以及对计算机的理解。我也是通过一个项目接触了Linux内核源码分析,从源码
    发表于 05-11 07:00

    鸿蒙源码分析系列(总目录) | 给HarmonyOS源码逐行加上中文注释

    同步更新。鸿蒙源码分析系列篇|- 鸿蒙内核源码分析 |-图解鸿蒙源码逐行注释分析(内存概念篇)
    发表于 11-20 11:24

    uCOS2源码分析

    uCOS2源码分析1-BSP部分-第4季第2部分视频课程 互联网课程品牌《朱...
    发表于 07-20 06:48

    互斥量源码分析测试

    文章目录互斥量源码分析测试参考资料:RTT官网文档关键字:分析RT-Thread源码、stm32、RTOS、互斥量。互斥量在其他书籍中的名称:mutex :互斥锁,互斥量,互斥体。从信
    发表于 08-24 06:01

    uCOS2源码分析

    uCOS2源码分析3-RTOS核心代码视频课程-第4季第4部分 互联网课程品...
    发表于 01-12 06:28

    OpenHarmony Camera源码分析

    积累了一些经验,我将围绕着这三个核心功能对OpenHarmony Camera源码进行详细的分析。二、OpenHarmony相机子系统(1)系统简介相机组件支持相机业务的开发,开发者可以通过已开放的接口
    发表于 09-06 11:21

    nucleus plus源码分析下载

    |Nucleus PLUS源码分析Nucleus PLUS Internals 相关文档Nucleus PLUS 参考手册,Accelerated Technology编著,描述如何操作
    发表于 07-07 15:18 36次下载

    基于stm32_TFT液晶屏显示源码分析

    本文档详细的对stm32TFT液晶屏显示源码进行分析
    发表于 08-29 14:22 13次下载

    UCOS-III OS_CPU_PendSVHandler源码分析

    UCOS-III OS_CPU_PendSVHandler源码分析
    发表于 08-28 10:48 13次下载

    uboot源码分析,思路还算清晰

    uboot源码分析,思路还算清晰
    发表于 10-24 15:25 19次下载
    uboot<b class='flag-5'>源码</b><b class='flag-5'>分析</b>,思路还算清晰

    Java反射的工作原理和源码分析

    Java反射的工作原理和源码分析
    发表于 07-08 15:11 14次下载
    Java反射的工作原理和<b class='flag-5'>源码</b><b class='flag-5'>分析</b>

    十二个Pixhawk源码笔记分析资源下载

    十二个Pixhawk源码笔记分析资源下载
    发表于 04-02 09:20 4次下载
    十二个Pixhawk<b class='flag-5'>源码</b>笔记<b class='flag-5'>分析</b>资源下载

    epoll源码分析

    对上述4个函数进行源码分析源码来源 由于epoll的实现内嵌在内核中,直接查看内核源码的话会有一些无关代码影响阅读。为此在GitHub上写的简化版TCP/IP协议栈,里面实现了ep
    的头像 发表于 11-13 11:49 496次阅读
    epoll<b class='flag-5'>源码</b><b class='flag-5'>分析</b>