Spring中的注解@Autowired实现过程全解(@Autowired 背后的故事)_第1页
Spring中的注解@Autowired实现过程全解(@Autowired 背后的故事)_第2页
Spring中的注解@Autowired实现过程全解(@Autowired 背后的故事)_第3页
Spring中的注解@Autowired实现过程全解(@Autowired 背后的故事)_第4页
Spring中的注解@Autowired实现过程全解(@Autowired 背后的故事)_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第Spring中的注解@Autowired实现过程全解(@Autowired背后的故事)现在面试,基本上都是面试造火箭,工作拧螺丝。而且是喜欢问一些Spring相关的知识点,比如@Autowired和@Resource之间的区别。魔高一丈,道高一尺。很快不少程序员学会了背诵面试题,那我反过来问“Spring中的注解@Autowired是如何实现的?”,“说说@Autowired的实现原理?”等等,背诵面试题的就露馅了。基于此,今天我们来说一说@Autowired背后的故事!

使用Spring开发时,进行配置主要有两种方式,一是xml的方式,二是Javaconfig的方式。Spring技术自身也在不断的发展和改变,从当前Springboot的火热程度来看,Javaconfig的应用是越来越广泛了,在使用Javaconfig的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是@Autowired注解了。这个注解的功能就是为我们注入一个定义好的bean。那么,这个注解除了我们常用的属性注入方式之外还有哪些使用方式呢?它在代码层面又是怎么实现的呢?这是本篇文章着重想讨论的问题。

@Autowired注解用法

在分析这个注解的实现原理之前,我们不妨先来回顾一下@Autowired注解的用法。

将@Autowired注解应用于构造函数,如以下示例所示

`publicclassMovieRecommender{`

`privatefinalCustomerPreferenceDaocustomerPreferenceDao;`

`@Autowired`

`publicMovieRecommender(CustomerPreferenceDaocustomerPreferenceDao){`

`this.customerPreferenceDao=customerPreferenceDao;`

`//...`

`}`

将@Autowired注释应用于setter方法

`publicclassSimpleMovieLister{`

`privateMovieFindermovieFinder;`

`@Autowired`

`publicvoidsetMovieFinder(MovieFindermovieFinder){`

`this.movieFinder=movieFinder;`

`//...`

`}`

将@Autowired注释应用于具有任意名称和多个参数的方法

`publicclassMovieRecommender{`

`privateMovieCatalogmovieCatalog;`

`privateCustomerPreferenceDaocustomerPreferenceDao;`

`@Autowired`

`publicvoidprepare(MovieCatalogmovieCatalog,`

`CustomerPreferenceDaocustomerPreferenceDao){`

`this.movieCatalog=movieCatalog;`

`this.customerPreferenceDao=customerPreferenceDao;`

`//...`

`}`

您也可以将@Autowired应用于字段,或者将其与构造函数混合,如以下示例所示

`publicclassMovieRecommender{`

`privatefinalCustomerPreferenceDaocustomerPreferenceDao;`

`@Autowired`

`privateMovieCatalogmovieCatalog;`

`@Autowired`

`publicMovieRecommender(CustomerPreferenceDaocustomerPreferenceDao){`

`this.customerPreferenceDao=customerPreferenceDao;`

`//...`

`}`

直接应用于字段是我们使用的最多的一种方式,但是使用构造方法注入从代码层面却是更加好的,具体原因我就不细说了,有不懂的可以留言区评论。除此之外,还有以下不太常见的几种方式。

将@Autowired注释添加到需要该类型数组的字段或方法,则Spring会从ApplicationContext中搜寻符合指定类型的所有bean,如以下示例所示:

`publicclassMovieRecommender{`

`@Autowired`

`privateMovieCatalog[]movieCatalogs;`

`//...`

`}`

数组可以,我们可以马上举一反三,那容器也可以吗,答案是肯定的,下面是set以及map的例子:

`publicclassMovieRecommender{`

`privateSetMovieCatalogmovieCatalogs;`

`privateMapString,MovieCatalogmovieCatalogs;`

`@Autowired`

`publicvoidsetMovieCatalogs(SetMovieCatalogmovieCatalogs){`

`this.movieCatalogs=movieCatalogs;`

`@Autowired`

`publicvoidsetMovieCatalogs(MapString,MovieCatalogmovieCatalogs){`

`this.movieCatalogs=movieCatalogs;`

`//...`

`}`

以上就是@Autowired注解的主要使用方式,经常使用Spring的话应该对其中常用的几种不会感到陌生。

@Autowired注解的作用到底是什么

@Autowired这个注解我们经常在使用,现在,我想问的是,它的作用到底是什么呢

首先,我们从所属范围来看,事实上这个注解是属于Spring的容器配置的一个注解,与它同属容器配置的注解还有:@Required,@Primary,@Qualifier等等。因此@Autowired注解是一个用于容器(container)配置的注解。

其次,我们可以直接从字面意思来看,@autowired注解来源于英文单词autowire,这个单词的意思是自动装配的意思。自动装配又是什么意思?这个词语本来的意思是指的一些工业上的用机器代替人口,自动将一些需要完成的组装任务,或者别的一些任务完成。而在Spring的世界当中,自动装配指的就是使用将Spring容器中的bean自动的和我们需要这个bean的类组装在一起。

因此,笔者个人对这个注解的作用下的定义就是:将Spring容器中的bean自动的和我们需要这个bean的类组装在一起协同使用。

接下来,我们就来看一下这个注解背后到底做了些什么工作。

@Autowired注解是如何实现的

事实上,要回答这个问题必须先弄明白的是Java是如何支持注解这样一个功能的。

Java的注解实现的核心技术是反射,让我们通过一些例子以及自己实现一个注解来理解它工作的原理。

例子注解@Override

@Override注解的定义如下:

`@Target(ElementType.METHOD)`

`@Retention(RetentionPolicy.SOURCE)`

`public@interfaceOverride{`

`}`

@Override注解使用Java官方提供的注解,它的定义里面并没有任何的实现逻辑。注意,所有的注解几乎都是这样的,「注解只能是被看作元数据,它不包含任何业务逻辑」。「注解更像是一个标签,一个声明,表面被注释的这个地方,将具有某种特定的逻辑」。

那么,问题接踵而至,注解本身不包含任何逻辑,那么注解的功能是如何实现的呢?答案必然是别的某个地方对这个注解做了实现。以@Override注解为例,他的功能是重写一个方法,而他的实现者就是JVM,Java虚拟机,Java虚拟机在字节码层面实现了这个功能。

但是对于开发人员,虚拟机的实现是无法控制的东西,也不能用于自定义注解。所以,如果是我们自己想定义一个独一无二的注解的话,则我们需要自己为注解写一个实现逻辑,「换言之,我们需要实现自己注解特定逻辑的功能」。

自己实现一个注解

在自己写注解之前我们有一些基础知识需要掌握,那就是我们写注解这个功能首先是需要Java支持的,Java在jdk5当中支持了这一功能,「并且在java.lang.annotation包中提供了四个注解,仅用于编写注解时使用」,他们是:

注解

作用

「@Documented」

表明是否在javadoc中添加Annotation

「@Retention」

定义注释应保留多长时间,即有效周期。有以下几种策略:

「RetentionPolicy.SOURCE」-在编译期间丢弃。编译完成后,这些注释没有任何意义,因此它们不会写入字节码。示例@Override,@SuppressWarnings

「RetentionPolicy.CLASS」-在类加载期间丢弃。在进行字节码级后处理时很有用。有点令人惊讶的是,这是默认值。

「RetentionPolicy.RUNTIME」-不要丢弃。注释应该可以在运行时进行反射。这是我们通常用于自定义注释的内容。

「@Target」

指定可以放置注解的位置。如果不指定,则可以将注解放在任何位置。若我们只想要其中几个,则需要定义对应的几个。

下面是这8个属性:

ElementType.TYPE(类,接口,枚举)

ElementType.FIELD(实例变量)

ElementType.METHOD

ElementType.PARAMETER

ElementType.CONSTRUCTOR

ElementType.LOCAL_VARIABLE

ElementType.ANNOTATION_TYPE(在另一个注释上)

ElementType.PACKAGE(记住package-info.java)

「@Inherited」

控制注解是否对子类产生影响。

下面我们开始自己实现一个注解,注解仅支持primitives,string和enumerations这三种类型。注解的所有属性都定义为方法,也可以提供默认值。我们先实现一个最简单的注解。

`importjava.lang.annotation.ElementType;`

`importjava.lang.annotation.Retention;`

`importjava.lang.annotation.RetentionPolicy;`

`importjava.lang.annotation.Target;`

`@Target(ElementType.METHOD)`

`@Retention(RetentionPolicy.RUNTIME)`

`public@interfaceSimpleAnnotation{`

`Stringvalue();`

`}`

上面这个注释里面只定义了一个字符传,它的目标注释对象是方法,保留策略是在运行期间。下面我们定义一个方法来使用这个注解:

`publicclassUseAnnotation{`

`@SimpleAnnotation("testStringValue")`

`publicvoidtestMethod(){`

`//dosomethinghere`

我们在这里使用了这个注解,并把字符串赋值为:testStringValue,到这里,定义一个注解并使用它,我们就已经全部完成。

简单的不敢相信。但是,细心一想的话,我们虽然写了一个注解也用了它,可是它并没有产生任何作用啊。也没有对我们这里方法产生任何效果啊。是的现在确实是这样的,原因在于我们前面提到的一点,我们还没有为这个注解实现它的逻辑,现在我们就来为这个注解实现逻辑。

应该怎么做呢?我们不妨自己来想一想。首先,我想给标注了这个注解的方法或字段实现功能,我们必须得知道,到底有哪些方法,哪些字段使用了这个注解吧,因此,这里我们很容易想到,这里应该会用到反射。其次,利用反射,我们利用反射拿到这样目标之后,得为他实现一个逻辑,这个逻辑是这些方法本身逻辑之外的逻辑,这又让我们想起了代理,aop等知识,我们相当于就是在为这些方法做一个增强。事实上的实现主借的逻辑也大概就是这个思路。梳理一下大致步骤如下:

利用反射机制获取一个类的Class对象

通过这个class对象可以去获取他的每一个方法method,或字段Field等等

Method,Field等类提供了类似于getAnnotation的方法来获取这个一个字段的所有注解

拿到注解之后,我们可以判断这个注解是否是我们要实现的注解,如果是则实现注解逻辑

现在我们来实现一下这个逻辑,代码如下:

`privatestaticvoidannotationLogic(){`

`ClassuseAnnotationClass=UseAnnotation.class;`

`for(Methodmethod:useAnnotationClass.getMethods()){`

`SimpleAnnotationsimpleAnnotation=(SimpleAnnotation)method.getAnnotation(SimpleAnnotation.class);`

`if(simpleAnnotation!=null){`

`System.out.println("MethodName:"+method.getName());`

`System.out.println("value:"+simpleAnnotation.value());`

`System.out.println("---------------------------");`

`}`

在这里我们实现的逻辑就是打印几句话。从上面的实现逻辑我们不能发现,借助于Java的反射我们可以直接拿到一个类里所有的方法,然后再拿到方法上的注解,当然,我们也可以拿到字段上的注解。借助于反射我们可以拿到几乎任何属于一个类的东西。

一个简单的注解我们就实现完了。现在我们再回过头来,看一下@Autowired注解是如何实现的。

@Autowired注解实现逻辑分析

知道了上面的知识,我们不难想到,上面的注解虽然简单,但是@Autowired和他最大的区别应该仅仅在于注解的实现逻辑,其他利用反射获取注解等等步骤应该都是一致的。先来看一下@Autowired这个注解在Spring的源代码里的定义是怎样的,如下所示:

`packageorg.springframework.beans.factory.annotation;`

`importjava.lang.annotation.Documented;`

`importjava.lang.annotation.ElementType;`

`importjava.lang.annotation.Retention;`

`importjava.lang.annotation.RetentionPolicy;`

`importjava.lang.annotation.Target;`

`@Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.ANNOTATION_TYPE})`

`@Retention(RetentionPolicy.RUNTIME)`

`@Documented`

`public@interfaceAutowired{`

`booleanrequired()defaulttrue;`

`}`

阅读代码我们可以看到,Autowired注解可以应用在构造方法,普通方法,参数,字段,以及注解这五种类型的地方,它的保留策略是在运行时。下面,我们不多说直接来看Spring对这个注解进行的逻辑实现.

在Spring源代码当中,Autowired注解位于包org.springframework.beans.factory.annotation之中,该包的内容如下:

经过分析,不难发现Spring对autowire注解的实现逻辑位于类:AutowiredAnnotationBeanPostProcessor之中,已在上图标红。其中的核心处理代码如下:

`privateInjectionMetadatabuildAutowiringMetadata(finalClassclazz){`

`LinkedListInjectionMetadata.InjectedElementelements=newLinkedList();`

`ClasstargetClass=clazz;//需要处理的目标类`

`do{`

`finalLinkedListInjectionMetadata.InjectedElementcurrElements=newLinkedList();`

`/*通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历每一个字段的所用注解,并如果用autowired修饰了,则返回auotowired相关属性*/`

`ReflectionUtils.doWithLocalFields(targetClass,field-{`

`AnnotationAttributesann=findAutowiredAnnotation(field);`

`if(ann!=null){//校验autowired注解是否用在了static方法上`

`if(Modifier.isStatic(field.getModifiers())){`

`if(logger.isWarnEnabled()){`

`logger.warn("Autowiredannotationisnotsupportedonstaticfields:"+field);`

`return;`

`}//判断是否指定了required`

`booleanrequired=determineRequiredStatus(ann);`

`currElements.add(newAutowiredFieldElement(field,required));`

`});`

`//和上面一样的逻辑,但是是通过反射处理类的method`

`ReflectionUtils.doWithLocalMethods(targetClass,method-{`

`MethodbridgedMethod=BridgeMethodResolver.findBridgedMethod(method);`

`if(!BridgeMethodResolver.isVisibilityBridgeMethodPair(method,bridgedMethod)){`

`return;`

`AnnotationAttributesann=findAutowiredAnnotation(bridgedMethod);`

`if(ann!=nullmethod.equals(ClassUtils.getMostSpecificMethod(method,clazz))){`

`if(Modifier.isStatic(method.getModifiers())){`

`if(logger.isWarnEnabled()){`

`logger.warn("Autowiredannotationisnotsupportedonstaticmethods:"+method);`

`return;`

`if(method.getParameterCount()==0){`

`if(logger.isWarnEnabled()){`

`logger.warn("Autowiredannotationshouldonlybeusedonmethodswithparameters:"+`

`method);`

`booleanrequired=determineRequiredStatus(ann);`

`PropertyDescriptorpd=BeanUtils.findPropertyForMethod(bridgedMethod,clazz);`

`currElements.add(newAutowiredMethodElement(method,required,pd));`

`});`

`//用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理`

`elements.addAll(0,currElements);`

`targetClass=targetClass.getSuperclass();`

`while(targetClass!=nulltargetClass!=Object.class);`

`returnnewInjectionMetadata(clazz,elements);`

`}`

博主在源代码里加了注释,结合注释就能看懂它做的事情了,最后这个方法返回的就是包含所有带有autowire注解修饰的一个InjectionMetadata集合。这个类由两部分组成:

`publicInjectionMetadata(ClasstargetClass,CollectionInjectedElementelements){`

`this.targetClass=targetClass;`

`this.injectedElements=elements;`

`}`

一是我们处理的目标类,二就是上述方法获取到的所以elements集合。

有了目标类,与所有需要注入的元素集合之后,我们就可以实现autowired的依赖注入逻辑了,实现的方法如下:

`@Override`

`publicPropertyValuespostProcessPropertyValues(`

`PropertyValuespvs,PropertyDescriptor[]pds,Objectbean,StringbeanName)throwsBeanCreationException{`

`InjectionMetadatametadata=findAutowiringMetadata(beanName,bean.getClass(),pvs);`

`try{`

`metadata.inject(bean,beanName,pvs);`

`catch(BeanCreationExceptionex){`

`throwex;`

`catch(Throwableex){`

`thrownewBeanCreationException(beanName,"Injectionofautowireddependenciesfailed",ex);`

`returnpvs;`

`}`

它调用的方法是InjectionMetadata中定义的inject方法,如下

`publicvoidinject(Objecttarget,@NullableStringbeanName,@NullablePropertyValuespvs)throwsThrowable{`

`CollectionInjectedElementcheckedElements=this.checkedElements;`

`CollectionInjectedElementelementsToIterate=`

`(checkedElements!=nullcheckedElements:this.injectedElements);`

`if(!elementsToIterate.isEmpty()){`

`for(InjectedElementelement:elementsToIterate){`

`if(logger.isTraceEnabled()){`

`logger.trace("Processinginjectedelementofbean'"+beanName+"':"+element);`

`element.inject(target,beanName,pvs);`

`}`

其逻辑就是遍历,然后调用inject方法,inject方法其实现逻辑如下:

`/**`

`*Eitherthisor{@link#getResourceToInject}needstobeoverridden.`

`*/`

`protectedvoidinject(Objecttarget,@NullableStringrequestingBeanName,@NullablePropertyValuespvs)`

`throwsThrowable{`

`if(this.isField){`

`Fieldfield=(Field)this.member;`

`ReflectionUtils.makeAccessible(field);`

`field.set(target,getResourceToInject(target,requestingBeanName));`

`else{`

`if(checkPropertySkipping(pvs)){`

`return;`

`try{`

`Methodmethod=(Method)this.member;`

`ReflectionUtils.makeAccessible(method);`

`method.invoke(target,getResourceToInject(target,requestingBeanName));`

`catch(InvocationTargetExceptionex){`

`throwex.getTargetException();`

`}`

在这里的代码当中我们也可以看到,是inject也使用了反射技术并且依然是分成字段和方法去处理的。在代码里面也调用了makeAccessible这样的可以称之为暴力破解的方法,但是反射技术本就是为框架等用途设计的,这也无可厚非。

对于字段的话,本质上就是去set这个字段的值,即对对象进行实例化和赋值,例如下面代码:

`@Autowired`

`ObjectTestobjectTest;`

那么在这里实现的就相当于给这个obj

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论