深入理解@Autowired注解
本文来说下@Autowired注解,这个注解在平时的开发中也是使用的比较多,但是很多人可能也就是仅仅会使用而已,本文来说下其实现原理。
文章目录
- @Autowired注解概述
- @Autowired注解用法
- @Autowired注解的作用到底是什么
- @Autowired注解是如何实现的
- @Autowired注解实现逻辑分析
- 相关问题
- 相关阅读
- 本文小结
@Autowired注解概述
使用spring开发时,进行配置主要有两种方式,一是xml的方式,二是java config的方式。
spring技术自身也在不断的发展和改变,从当前springboot的火热程度来看,java config的应用是越来越广泛了,在使用java config的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是@Autowired注解了。这个注解的功能就是为我们注入一个定义好的bean。
那么,这个注解除了我们常用的属性注入方式之外还有哪些使用方式呢?它在代码层面又是怎么实现的呢?这是本篇文章着重想讨论的问题。
@Autowired注解用法
在分析这个注解的实现原理之前,我们不妨先来回顾一下@Autowired注解的用法。
将@Autowired注解应用于构造函数,如以下示例所示
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
将@Autowired注释应用于setter方法
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// ...
}
将@Autowired注释应用于具有任意名称和多个参数的方法
public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void prepare(MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {this.movieCatalog = movieCatalog;this.customerPreferenceDao = customerPreferenceDao;}// ...
}
也可以将@Autowired应用于字段,或者将其与构造函数混合,如以下示例所示
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredprivate MovieCatalog movieCatalog;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}// ...
}
直接应用于字段是我们使用的最多的一种方式,但是使用构造方法注入从代码层面却是更加好的。除此之外,还有以下不太常见的几种方式
将@Autowired注释添加到需要该类型数组的字段或方法,则spring会从ApplicationContext中搜寻符合指定类型的所有bean,如以下示例所示:
public class MovieRecommender {@Autowiredprivate MovieCatalog[] movieCatalogs;// ...
}
数组可以,我们可以马上举一反三,那容器也可以吗,答案是肯定的,下面是set以及map的例子:
public class MovieRecommender {private Set<MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}// ...
}
public class MovieRecommender {private Map<String, MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {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 @interface Override {}
@Override注解使用java官方提供的注解,它的定义里面并没有任何的实现逻辑。注意,所有的注解几乎都是这样的,注解只能是被看作元数据,它不包含任何业务逻辑。 注解更像是一个标签,一个声明,表面被注释的这个地方,将具有某种特定的逻辑。
那么,问题接踵而至,注解本身不包含任何逻辑,那么注解的功能是如何实现的呢?答案必然是别的某个地方对这个注解做了实现。以@Override注解为例,他的功能是重写一个方法,而他的实现者就是JVM,java虚拟机,java虚拟机在字节码层面实现了这个功能。
但是对于开发人员,虚拟机的实现是无法控制的东西,也不能用于自定义注解。所以,如果是我们自己想定义一个独一无二的注解的话,则我们需要自己为注解写一个实现逻辑,换言之,我们需要实现自己注解特定逻辑的功能。
@Autowired注解实现逻辑分析
知道了上面的知识,我们不难想到,上面的注解虽然简单,但是@Autowired和他最大的区别应该仅仅在于注解的实现逻辑,其他利用反射获取注解等等步骤应该都是一致的。先来看一下@Autowired这个注解在spring的源代码里的定义是怎样的,如下所示:
package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {boolean required() default true;
}
阅读代码我们可以看到,Autowired注解可以应用在构造方法,普通方法,参数,字段,以及注解这五种类型的地方,它的保留策略是在运行时。
在Spring源代码当中,Autowired注解位于包org.springframework.beans.factory.annotation之中,该包的内容如下:
经过分析,不难发现Spring对autowire注解的实现逻辑位于类:AutowiredAnnotationBeanPostProcessor之中,已在上图标红。其中的核心处理代码如下:
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();Class<?> targetClass = clazz;//需要处理的目标类do {final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();/*通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历每一个字段的所用注解,并如果用autowired修饰了,则返回auotowired相关属性*/ ReflectionUtils.doWithLocalFields(targetClass, field -> {AnnotationAttributes ann = findAutowiredAnnotation(field);if (ann != null) {//校验autowired注解是否用在了static方法上if (Modifier.isStatic(field.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static fields: " + field);}return;}//判断是否指定了requiredboolean required = determineRequiredStatus(ann);currElements.add(new AutowiredFieldElement(field, required));}});//和上面一样的逻辑,但是是通过反射处理类的methodReflectionUtils.doWithLocalMethods(targetClass, method -> {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {if (Modifier.isStatic(method.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static methods: " + method);}return;}if (method.getParameterCount() == 0) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation should only be used on methods with parameters: " +method);}}boolean required = determineRequiredStatus(ann);PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new AutowiredMethodElement(method, required, pd));}});//用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理 elements.addAll(0, currElements);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return new InjectionMetadata(clazz, elements);}
博主在源代码里加了注释,结合注释就能看懂它做的事情了,最后这个方法返回的就是包含所有带有autowire注解修饰的一个InjectionMetadata集合。这个类由两部分组成:
public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {this.targetClass = targetClass;this.injectedElements = elements;}
一是我们处理的目标类,二就是上述方法获取到的所以elements集合。
有了目标类,与所有需要注入的元素集合之后,我们就可以实现autowired的依赖注入逻辑了,实现的方法如下:
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {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中定义的inject方法,如下
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()) {for (InjectedElement element : elementsToIterate) {if (logger.isTraceEnabled()) {logger.trace("Processing injected element of bean '" + beanName + "': " + element);}element.inject(target, beanName, pvs);}}}
其逻辑就是遍历,然后调用inject方法,inject方法其实现逻辑如下:
/*** Either this or {@link #getResourceToInject} needs to be overridden.*/
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)throws Throwable {if (this.isField) {Field field = (Field) this.member;ReflectionUtils.makeAccessible(field);field.set(target, getResourceToInject(target, requestingBeanName));}else {if (checkPropertySkipping(pvs)) {return;}try {Method method = (Method) this.member;ReflectionUtils.makeAccessible(method);method.invoke(target, getResourceToInject(target, requestingBeanName));}catch (InvocationTargetException ex) {throw ex.getTargetException();}}
}
在这里的代码当中我们也可以看到,是inject也使用了反射技术并且依然是分成字段和方法去处理的。在代码里面也调用了makeAccessible这样的可以称之为暴力破解的方法,但是反射技术本就是为框架等用途设计的,这也无可厚非。
对于字段的话,本质上就是去set这个字段的值,即对对象进行实例化和赋值,例如下面代码:
@Autowired
ObjectTest objectTest;
那么在这里实现的就相当于给这个objecTest引用赋值了。
对于方法的话,本质就是去调用这个方法,因此这里调用的是method.invoke.
getResourceToInject方法的参数就是要注入的bean的名字,这个方法的功能就是根据这个bean的名字去拿到它。
以上,就是@Autowire注解实现逻辑的全部分析。结合源代码再看一遍的话,会更加清楚一点。下面是spring容器如何实现@AutoWired自动注入的过程的图:
总结起来一句话:使用@Autowired注入的bean对于目标类来说,从代码结构上来讲也就是一个普通的成员变量,@Autowired和spring一起工作,通过反射为这个成员变量赋值,也就是将其赋为期望的类实例。
相关问题
注解的有效周期是什么
各种注释之间的第一个主要区别是,它们是在编译时使用,然后被丢弃(如@Override),还是被放在编译的类文件中,并在运行时可用(如Spring的@Component)。这是由注释的“@Retention”策略决定的。如果您正在编写自己的注释,则需要决定该注释在运行时(可能用于自动配置)还是仅在编译时(用于检查或代码生成)有用。
当用注释编译代码时,编译器看到注释就像看到源元素上的其他修饰符一样,比如访问修饰符(public/private)或.。当遇到注释时,它运行一个注释处理器,就像一个插件类,表示对特定的注释感兴趣。注释处理器通常使用反射API来检查正在编译的元素,并且可以简单地对它们执行检查、修改它们或生成要编译的新代码。
@Override是一个示例;它使用反射API来确保能够在其中一个超类中找到方法签名的匹配,如果不能,则使用@Override会导致编译错误。
注入的bean和用它的bean的关系是如何维护的
无论以何种方式注入,注入的bean就相当于类中的一个普通对象应用,这是它的实例化是spring去容器中找符合的bean进行实例化,并注入到类当中的。他们之间的关系就是普通的一个对象持有另一个对象引用的关系。只是这些对象都是spring当中的bean而已。
为什么注入的bean不能被定义为static的
从设计的角度来说 ,使用静态字段会鼓励使用静态方法。静态方法是evil的。依赖注入的主要目的是让容器为您创建对象并进行连接。而且,它使测试更加容易。
一旦开始使用静态方法,您就不再需要创建对象的实例,并且测试变得更加困难。同样,您不能创建给定类的多个实例,每个实例都注入不同的依赖项(因为该字段是隐式共享的,并且会创建全局状态)。
静态变量不是Object的属性,而是Class的属性。spring的autowire是在对象上完成的,这样使得设计很干净。 在spring当中我们也可以将bean对象定义为单例,这样就能从功能上实现与静态定义相同的目的。
但是从纯粹技术的层面,我们可以这样做:
将@Autowired可以与setter方法一起使用,然后可以让setter修改静态字段的值。但是这种做法非常不推荐。
相关阅读
- 深入理解JAVA中的注解
- 如何来自定义注解
本文小结
本文详细介绍了@Autowired注解相关的知识与实现细节。
深入理解@Autowired注解相关推荐
- Spring5:@Autowired注解、@Resource注解和@Service注解
转载:http://www.cnblogs.com/xrq730/p/5313412.html 什么是注解 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有 ...
- @autowired注解原理_SpringBoot注解大全,收藏一波!!!
一.注解(annotations)列表 二.注解(annotations)详解 三.JPA注解 四.springMVC相关注解 五.全局异常处理 <Java 2019 超神之路> < ...
- 重新理解@Resource注解
@Resource和@Autowired注解都是用来实现依赖注入的.只是@Autowired按byType自动注入,而@Resource默认按 byName自动注入. @Resource有两个重要的属 ...
- @autowired注解_SpringBoot常用注解大全
作者:tanwei81 , 链接:www.cnblogs.com/tanwei81 一.注解(annotations)列表 @SpringBootApplication: 包含了@ComponentS ...
- @autowired注解注入为null_Intellij IDEA中Mybatis Mapper自动注入警告的6种解决方案
相信使用Mybaits的小伙伴们一定会经常编写类似如下的代码: 可以看到 userMapper 下有个红色警告.虽然代码本身并没有问题,能正常运行,但有个警告总归有点恶心.本文分析原因,并列出解决该警 ...
- 为何@Autowired注解无法实现第三方类的注入?
为什么在Controller中对第三方DiscoverryClient类使用@Autowire注解无法自动注入? 这个问题来自于<master springMVC 4>这本书的源码,用Sp ...
- @autowired注解注入为null_Spring @Autowired 注解自动注入流程是怎么样?
面试中碰到面试官问:"Spring 注解是如果工作的?",当前我一惊,完了这不触及到我的知识误区了吗?,还好我机智,灵机一动回了句:Spring 注解的工作流程倒还没有看到,但是我 ...
- autowired注解_Spring系列之Spring常用注解总结
作者:平凡希来源:https://www.cnblogs.com/xiaoxi/p/5935009.html 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做 ...
- Spring中Autowired注解到底怎么实现的
前言 使用Spring开发时,进行配置主要有两种方式,一是XML的方式,二是Java Config的方式. Spring技术自身也在不断的发展和改变,从当前springboot的火热程度来看,java ...
最新文章
- SQL Server 2012 OFFSET/FETCH NEXT分页示例
- 活动目录(Active Directory)安装
- 丢失或损坏NDF文件如何附加数据库
- seata分布式事务框架原理
- Linux 网络设备驱动开发(一) —— linux内核网络分层结构
- java程序员必须会的技能
- ssm教务排课系统MVC学校专业选修课程安排选课信息jsp源代码数据库mysql
- 怀集天气预报软件测试,【天气】怀集要入夏?这份天气预报告诉你答案!
- kerberos开启kdc的debug日志
- git 代码提交,出现403错误的问题
- Java url链接生成二维码
- 随时随地掌上邮,飞邮Android版邮件客户端正式提供试用
- 记第一次编译Linux内核
- 2021数据分析师薪资大PK
- <Zhuuu_ZZ>设计模式—面向接口编程
- Navicat12.1破 解教程,亲测可用
- 计算机与数学交融的教学设计,信息技术与小学数学学科的整合 小学数学教案...
- java提高篇(二十)-----集合大家族
- Flutter开发(二十九):Flutter热重启、热加载、调试与发布应用
- windows 禁用win任务管理器