都用过@Autowired,但你知道它是怎么实现的吗
前言
在使用Spring
开发的时候,配置的方式主要有两种,一种是xml
的方式,另外一种是 java config
的方式。在使用的过程中java config
,我们难免会与注解进行各种打交道,其中,我们使用最多的注解应该就是@Autowired
注解了。这个注解的作用就是注入一个定义好的bean
。
那么,除了我们常用的属性注入方式之外,还有哪些方式可以使用这个注解呢?在代码层面是如何实现的?
欢迎关注个人公众号【JAVA旭阳】交流学习~~
如何使用@Autowired注解?
将@Autowired
注解应用于构造函数,如以下示例所示:
@Component
public class BeanConfig{
@Autowired
private BeanConfig beanConfig;
@Autowired
public BeanConfig(BeanConfig beanConfig) {
this.beanConfig = beanConfig;
}
}
直接应用于字段是我们使用最多的方式,但是从代码层面使用构造函数注入会更好。因为构造器注入的方式,能够保证注入的依赖不可变,并确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
此外,还有以下几种不太常用的方法,见下面的代码:
@Autowired
private List<BeanConfig> beanConfigList;
@Autowired
private Set<BeanConfig> beanConfigSet;
@Autowired
private Map<String, BeanConfig> beanConfigMap;
虽然我们经常使用这个注解,但是我们真的了解它的作用吗?
首先从它的作用域来看,其实这个注解是属于容器配置的Spring
注解,其他属于容器配置注解:@Required
, @Primary
, @Qualifier
等。
其次,我们可以直接看字面意思,autowire
,这个词的意思就是自动装配的意思。
自动装配是什么意思?这个词的本意是指在一些行业中用机器代替人自动完成一些需要装配的任务。在Spring
的世界里,自动组装是指使用我们需要这个bean
的class
自动组装Spring
容器中的bean
。
所以这个注解作用的就是自动将Spring
容器中的bean
和我们需要这个bean
一起使用的类组装起来。
接下来,让我们看看这个注解背后工作的原理。
如何实现@Autowired 注解?
Java注解实现的核心技术是反射。让我们通过一些例子和自己实现一个注解来了解它的工作原理。
我们拿到target
之后就可以用反射给他实现一个逻辑,这种逻辑在这些方法本身的逻辑之外,这让我们想起proxy、aop等知识,我们相当于为这些方法做了一个逻辑增强。
其实注解的实现主要逻辑大概就是这个思路。总结一下一般步骤如下:
- 使用反射机制获取类的
Class
对象。 - 通过这个类对象,可以得到它的每一个方法方法,或者字段等。
Method
、Field
等类提供了类似getAnnotation
的方法来获取某个字段的所有注解。- 拿到注解后,我们可以判断该注解是否是我们要实现的注解,如果是,则实现注解逻辑。
下面我们来实现这个逻辑,代码如下:
public void postProcessProperties() throws Exception {
Class<BeanConfig> beanConfigClass = BeanConfig.class;
BeanConfig instance = beanConfigClass.newInstance();
Field[] fields = beanConfigClass.getDeclaredFields();
for (Field field : fields) {
// getAnnotation,判断是否有Autowired
Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
if (autowired != null) {
String fileName = field.getName();
Class<?> declaringClass = field.getDeclaringClass();
Object bean = new Object();
field.setAccessible(true);
field.set(bean, instance);
}
}
}
从上面的实现逻辑不难发现,借助Java反射,我们可以直接获取一个类中的所有方法,然后获取方法上的注解。当然,我们也可以获取字段上的注解。在反射的帮助下,我们几乎可以得到属于一个类的任何东西。这样,我们自己简单做了一个实现。
知道了上面的知识,我们就不难想到,上面的注解虽然简单,但是@Autowired
和他最大的区别应该只是注解的实现逻辑,其他的如使用反射获取注解等步骤应该是相同的。
接下来我们看在Spring中,@Autowired
是如何实现的呢?
Spring中源码解析
我们来看@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
对这个注解的逻辑实现。
在Spring
源码中,@Autowired
注解位于包中org.springframework.beans.factory.annotation
。经过分析不难发现,Spring对自动装配注解的实现逻辑位于类: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修饰,返回autowired相关属性
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
// 检查静态方法上是否使用了自动装配注解
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}
// 判断是否指定了required
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
//和上面的逻辑一样,但是方法是通过反射来处理
ReflectionUtils.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);
}
最后,此方法返回一个InjectionMetadata
包含所有autowire
注解的集合。
这个类由两部分组成:
public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
this.targetClass = targetClass;
this.injectedElements = elements;
}
一个是我们要处理的目标类,一个是elements
上面方法得到的集合。
有了目标类和所有需要注入的元素,我们就可以实现自动装配的依赖注入逻辑。实现方法如下。
@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;
}
它调用的inject
方法就是定义在InjectionMetadata
。
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);
}
}
}
/**
* 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();
}
}
}
上面代码中,方法的参数getResourceToInject
是要注入的名称,bean这个方法的作用是根据名称获取bean
。
以上就是@Autowire
注解实现逻辑的完整解析。
下面是spring容器实现@Autowired
自动注入的时序图。
总结
本文讲解了Spring中最常用的注解之一@Autowired
, 平时我们可能都是使用属性注入的,但是后续建议大家慢慢改变习惯,使用构造器注入。同时也讲解了这个注解背后的实现原理,希望对大家有帮助。
欢迎关注个人公众号【JAVA旭阳】交流学习~~
都用过@Autowired,但你知道它是怎么实现的吗的更多相关文章
- 欲哭无泪的@Autowired注入对象为NULL
欲哭无泪啊...一下午的时间就这么被浪费了...一个基于spring mvc和spring data jpa的小项目,当我写完一个controller的测试用例后,一运行却报空指针,跟了下是一个dao ...
- struts2的action中@Autowired注入为null的解决方案
今天遇到类似问题,记录下来以便以后查阅: @Aspect作用于action,致使action中的@Autowired注入为null的解决方案,以下三种任选一种: 1.去掉@Autowired,改用se ...
- Bean装配之@Autowired注解
@Required(不常用) @Autowired(常用) 下面用例子解释以上内容: @Autowired注解的三种方式如下,第一种是直接在属性名上加注解,这样就可以不用在写set方法进行注入,这种方 ...
- 从头认识Spring-2.3 注解装配-@autowired(4)-required(2)
这一章节我们来继续具体讨论一下@autowired里面的參数required.在多构造器注入的情况. 1.domain(重点) 蛋糕类: package com.raylee.my_new_sprin ...
- Spring注解之@Autowired
前言 说起Spring的@Autowired注解,想必大家已经熟悉的不能再熟悉了.本文就针对此最常用的注解,梳理一下它的功能和原理,争取从源码的角度将此注解讲通,如有写的不准确的地方,欢迎各位园友拍砖 ...
- @autowired、@Qualifier、@Primary注解
@autowired 可以自动帮你把Bean里面引用的对象的setter/getter方法省略,自动帮你set/get. 启动spring IoC时,容器自动装载了一个AutowiredAnnotat ...
- 聊聊依赖注入注解@Resource和@Autowired
1. 前言 @Resource和@Autowired注解都可以在Spring Framework应用中进行声明式的依赖注入.而且面试中经常涉及到这两个注解的知识点.今天我们来总结一下它们. 2. @R ...
- @Autowired还可以注入List和Map
@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collection ...
- (转)@Autowired(required=false)注入注意的问题
1.前言 在使用spring开发过程中,我们基本上都是使用@Autowired这个注解,用来注入已有的bean.但是有些时候,会注入失败.当我们加上参数(required=false)就能解决.今天整 ...
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
随机推荐
- Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块
这是一篇系列博文,我将使用Abp.Zero搭建一套集成手机号免密登录验证与号码绑定功能的用户系统: Abp.Zero 手机号免密登录验证与号码绑定功能的实现(一):验证码模块 Abp.Zero 手机号 ...
- CSS 动画一站式指南
CSS 动画一站式指南 目录 CSS 动画一站式指南 1. CSS 动画 1.1 变换 1.1.1 变换属性介绍 1.1.2 变换动画实践 1.2 过渡 1.2.1 过渡属性介绍 1.2.2 过渡动画 ...
- 19_Vue如何监测到对象类型数据发生改变的?
数据更新 关于监视 我们之前讲过,我们在data当中配置的属性,最终会挂载在vue实例身上,而data这个配置项,最终也会在vue身上成为一个新的属性 == _data 当我们在页面DOM当中,去使用 ...
- java学习之socket编程
0x00前言和思维导图 Socks实际上是什么:实际上是提供了精彩通信的端口,在通信之前双方都必须要创造一个端点才能通信,其实感觉socket跟计算机的三次握手有些相似,分为三个步骤: (1)服务器监 ...
- 嵌入式-C语言基础:指针数组(和数组指针区分开来)
指针数组:一个数组,若其元素均为指针类型的数据,称为指针数组,指针数组存放的是指针类型的数据,也就是指针数组的每个元素都存放一个地址.下面定义一个指针数组: int * p[4];//[]的优先级是比 ...
- java8 (jdk 1.8) 新特性——Lambda
java8 (jdk 1.8) 新特性 --初步认识 1. 什么是lambda? 目前已知的是,有个箭头 -> 说一大段官方话,也没有任何意义 我们直接看代码: 之前我们创建线程是这样的 Ru ...
- 网络yum源下载
思路一: 按照本地网罗源,然后使用reposync直接将源同步下载到本地 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/re ...
- windows安装wsl,在windows中使用ubuntu
WSL(Windows Subsystem for Linux)即适用于 Linux 的 Windows 子系统,它是随 Windows 操作系统一起提供. WSL是windows操作系统的子系统,算 ...
- AArch32/AArch64应用程序级内存模型(五)
本文主要为了记录在学习armv8的过程中的一些感悟.由于原文部分章节晦涩难懂,作者参考了网上很多优秀博主的部分章节(可能是直接摘录)并结合自己的理解重新整理了当前这个版本.文中不免有部分章节讲解很浅, ...
- Objects.requireNonNull的意义是什么
Objects.requireNonNull方法的源码是这样: public static <T> T requireNonNull(T obj) { if (obj == null) t ...