Spring依赖注入源码学习:基于注解的DI源码解析

当搭建SpringBoot或者SpringCloud的项目时,几乎不再使用XML文件的方式定义bean加载和依赖注入的方式,通常使用注解@Autowired@Resource的来注入指定的bean。

​   Spring本身也支持基于注解的依赖注入,只需要在XML文件中引入新的命名空间:

xml 复制代码
<context:component-scan base-package="com.ayanokoujimonki"/>

base-package:指定Spring要扫描的包路径。

​   关于加了命名空间后,加载beanDefinition的源码可以参考这篇博客:Spring源码学习(四):component-scan加载beanDefinition_componentscanbeandefinitionparser-CSDN博客

​   省流版如下:

​   加了命名空间后,Spring的ClassPathBeanDefinitionScanner类会去加载指定base-package 下的被相关注解修饰的bean,例如被@Component注解及其衍生注解@Service@Controller修饰的bean。

Demo如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.source.ioc"/>
</beans>
java 复制代码
/**
 * @Author:chenmq
 * @Date:2025/3/26
 */
package com.source.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Author chenmq
 * @Date 2025/03/26
 **/
@Component
public class SpringIOC {
    @Component(value = "springIOC1")
    public static class SpringIOC1 {
        private String property;

        public SpringIOC1() {
            this.property = "i am SpringIOC1";
        }

        @Override
        public String toString() {
            return "SpringIOC1{" +
                    "property1='" + property + '\'' +
                    '}';
        }
    }
    @Component(value = "springIOC2")
    public static class SpringIOC2 {
        private String property;

        public SpringIOC2() {
            this.property = "i am SpringIOC2";
        }

        @Override
        public String toString() {
            return "SpringIOC2{" +
                    "property1='" + property + '\'' +
                    '}';
        }
    }
    @Component(value = "springIOC3")
    public static class SpringIOC3 {
        private String property;
        @Autowired
        private SpringIOC1 springIOC1;
        @Autowired
        private SpringIOC2 springIOC2;

        public SpringIOC3() {
        }

        public SpringIOC3(SpringIOC1 springIOC1) {
            this.springIOC1 = springIOC1;
        }

        public void setSpringIOC2(SpringIOC2 springIOC2) {
            this.springIOC2 = springIOC2;
        }

        @Override
        public String toString() {
            return "SpringIOC3{" +
                    "property='" + property + '\'' +
                    ", springIOC1=" + springIOC1 +
                    ", springIOC2=" + springIOC2 +
                    '}';
        }
    }

}

1 @Autowired

1.1 @Autowired修饰属性字段

​   我们先来看看@Autowired注解,当加了@Autowired注解后,SpringIOC3的beanDefinition如下:

​   再看看实例化代码createBeanInstance

​   之前有提到过,基于XML的bean实例化determineConstructorsFromBeanPostProcessors 方法返回都是null,在这里解释一下,determineConstructorsFromBeanPostProcessors代码如下:

​   而基于XML定义bean实例化时,程序执行到这一步beanFactory是指定类型后处理器的,所以该方法一直返回为null。

​   而当我们引入了新的命名空间后,beanFactory如下:

​   当前beanFactory中有确实有两个后处理器是SmartInstantiationAwareBeanPostProcessor类型的:ImportAwareBeanPostProcessorAutowiredAnnotationBeanPostProcessor

​   这部分源码我在Spring源码学习(三):finishBeanFactoryInitialization-CSDN博客里面有详细的源码分析,这里省流版是AutowiredAnnotationBeanPostProcessordetermineCandidateConstructors方法里面有这部分代码:

!NOTE

可以看到当类中所有构造方法都没有被@Autowired修饰,类中有一个构造方法,该构造方法会被加入到候选构造方法中。

​   带入到开头的Demo中,这部分逻辑就会变成:

tex 复制代码
1.空参构造+SpringIOC1为参数的有参构造:走逻辑4,最后ctors为null
2.只有SpringIOC1为参数的有参构造:走逻辑2,最后ctors是该有参构造
3.只有空参构造:走逻辑4,最后ctors为null

​   实例化完成后,继续往下:

​   另一个重要的方法是applyMergedBeanDefinitionPostProcessors

​   可以看到当前beanFactory中的后处理器中,有三个都是MergedBeanDefinitionPostProcessor类型的,分别去回调里面的postProcessMergedBeanDefinition方法。

​   CommonAnnotationBeanPostProcessor后处理器处理@WebServiceRef、@EJB、@Resource注解。

​   AutowiredAnnotationBeanPostProcessor后处理器处理@Autowired、@Value、@Inject注解。

​   ApplicationListenerDetector后处理器主要来缓存单例的ApplicationListener

​   重点来看看AutowiredAnnotationBeanPostProcessorpostProcessMergedBeanDefinition方法:

​   我们来看看最后的metadata长啥样:

​   可以看到,包含了待注入字段的目标类的全路径名。

​   我们继续往下看,属性填充部分:

​   这部分逻辑在未开启component-scan时是不会触发的,可以看到还是通过后处理器来处理属性填充,依旧是InstantiationAwareBeanPostProcessor类型的后处理器。

​   主要逻辑还是AutowiredAnnotationBeanPostProcessorpostProcessProperties方法完成:

​   调用beanFactory的resolveDependency解析属性依赖的值:

​   可以看到,在开启注解扫描后,通过@Autowired注解完成属性注入时,主要依赖AutowiredAnnotationBeanPostProcessor后处理器,而AutowiredAnnotationBeanPostProcessor也只会在开启注解扫描后注册到beanFactory中。

​   简单总结一下AutowiredAnnotationBeanPostProcessor的工作流程,

tex 复制代码
1.在创建好bean的实例后,Spring会回调AutowiredAnnotationBeanPostProcessor的postProcessMergedBeanDefinition方法构建"元数据"(也就是metadata),这一过程主要是扫描并记录bean中被@Autowired修饰的字段和方法,供后续属性注入时使用。
2.元数据构建成功后,在属性填充阶段Spring又会回调AutowiredAnnotationBeanPostProcessor的postProcessProperties方法,该方法会找到当前bean的metadata,得到需要注入的属性值,并通过反射完成属性注入。

​   现在我们知道了@Autowired属性注入的大致流程,现在我们再来探究为什么说@Autowired是按照类型注入的。我们 重点来看看这一行代码:

java 复制代码
try {
	value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
	throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}

​   这里调用beanFactory(DefaultListableBeanFactory)的resolveDependency 方法解析@Autowired修饰的成员变量的值:

​   doResolveDependency方法较长,我们来看看关键部分:

​   我们再来看看,如果出现多个匹配的Bean,Spring该如何处理,也就是determineAutowireCandidate方法:

​   可以看到,如果匹配了多个bean,优先级从高到底为:

tex 复制代码
1.候选bean使用了@Primary注解
2.候选bean实现了Order接口或者使用了@Order注解
3.待注入字段使用了@Qualifier注解,根据@Qualifier中的值取匹配beanName

​   到这里,和我们平时背的八股一一对应上了。

​   和之前基于XML的依赖注入不同,@Autowired注解修饰在字段上时,直接通过反射将字段赋值,并没有调用该字段对应的setter方法。

1.2 @Autowired修饰构造方法

​   上面我们介绍了@Autowired注解修饰在字段上完成依赖注入的情形。

​   其实很容易就想到,@Autowired用到构造方法上时,会通过构造器注入完成依赖注入,我们重新来看看创建bean实例的源码部分createBeanInstance ,之前我们有讲过,createBeanInstance方法里面有一段决定选择使用哪个构造器的逻辑:

java 复制代码
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
		mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
	return autowireConstructor(beanName, mbd, ctors, args);
}

​   在使用XML时,ctors返回的一直是null,而开启注解后,会触发AutowiredAnnotationBeanPostProcessordetermineCandidateConstructors方法:

​   我们需要注意的是,determineCandidateConstructors方法内部会主动抛出两个异常:

​   这里涉及到两个属性值:

tex 复制代码
1.requiredConstructor:构造方法被@Autowired修饰,且required的值为true(默认是true)
2.candidates:候选构造器集合,被@Autowired修饰的构造器,都会被放到这个集合中

​   这里我们可以总结出@Autowired使用在构造方法上的一些规律:

tex 复制代码
一个bean中只能一个构造方法使用@Autowired注解,如果有多个构造方法使用@Autowired注解,需要保证它们的required为false。

1.3 @Autowired修饰setter方法

​   上面我们介绍了@Autowired常见的两种情形,其实在实际开发工作中时,我们通常会使用lombok包中的@Data注解来自动生成构造方法和字段的setter 方法,因此最常用的是@Autowired修饰属性字段来完成依赖注入。

​   这里我们再顺便简单讲一下@Autowired注解修饰在setter 方法上的情形,这种情形其实也是通过setter方法完成依赖注入。

​   Demo:

​   @Autowired修饰属性字段时我们有讲过,AutowiredAnnotationBeanPostProcessorpostProcessMergedBeanDefinition 方法会构建元数据(metadata),这一过程主要是扫描并记录bean中被@Autowired修饰的字段和方法。我们来看看该方法是怎么处理被@Autowired修饰的成员方法:

​   针对本小节的Demo,构建的metadata如下:

​   然后再看看属性填充的部分,也就是AutowiredAnnotationBeanPostProcessorpostProcessProperties方法:

​   由于成员变量和成员方法对应的element的类型不一致,这里会调用AutowiredMethodElementinject方法:

2 @Resource

​   熟悉八股的朋友肯定知道,@Resource是与@Autowired齐名的注解。不同的是,@Resource注解是根据名称完成依赖注入。

​   @Autowired是Spring提供的第三方注解,@Resource是java原生注解。他俩用在字段上时,本质都是通过反射给字段设置值,在熟悉了@Autowired后,我们来看看@Resource是如何工作的。

​   前文中我们有提到过@Autowired是通过AutowiredAnnotationBeanPostProcessorpostProcessMergedBeanDefinition 方法构建"元数据",再通过AutowiredAnnotationBeanPostProcessorpostProcessProperties方法完成依赖注入。

​   @Resource也分为这两步,不过后处理器的类型不同,处理@Resource的后处理器为CommonAnnotationBeanPostProcessor

​   先来看看CommonAnnotationBeanPostProcessorpostProcessMergedBeanDefinition方法:

​   @Resource也可以作用在方法和字段,我们先来看看它作用在字段上时的构建元数据逻辑:

​   再来看看它作用在方法上时的构建元数据逻辑:

​   可以看到CommonAnnotationBeanPostProcessor构建元数据时和AutowiredAnnotationBeanPostProcessor是有区别的:

tex 复制代码
1.CommonAnnotationBeanPostProcessor字段类型和方法入参类型都要求不在ignoredResourceTypes里面
2.CommonAnnotationBeanPostProcessor构建方法元数据时,要求方法参数有且仅有一个

​   因此,从上面逻辑中我们可以知道,@Resource注解修饰成员方法时,该方法有且只能有一个入参。(其实也很少用@Resource修饰成员方法)

​   构建元数据的整体流程其实和@Autowired大致相同,只是细节实现有些区别。接下来我们再来看看CommonAnnotationBeanPostProcessorpostProcessProperties方法是如何完成依赖注入的:

​   CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor构建的元数据类型相同,均为InjectMetaData。但是,其中的member属性类型不同,前者的元数据类型为InjectedElement,后者的元数据类型为AutowiredFieldElementAutowiredMethodElementInjectedElement是这两个的父类。

​   我们来看看InjectedElementinject方法:

​   不管是成员变量还是成员方法,都会通过getResourceToInject 方法去解析得到依赖的值,getResourceToInject 最终会调到CommonAnnotationBeanPostProcessorautowireResource方法

​   可以看到,@Resource会优先根据beanName去注入依赖,如果没有找到匹配的beanName,会进一步使用类型完成依赖注入。

3 Lombok

​   在实际开发过程中,为了保证代码的间接性(尽可能少写代码),我们通常会引入Lombok包。这个包下有许多实用的注解,比如:

tex 复制代码
@Data:会为字段生成setter和getter方法,并生成一个无参构造
@AllArgsConstructor:所有字段的全参构造
@NoArgsConstructor:无参构造
@RequiredArgsConstructor:只包含 final 或 @NonNull 字段的构造方法

​   在1.1节中我们提到过,如果类中只有一个构造方法,即使这个构造方法没有被@Autowired修饰,AutowiredAnnotationBeanPostProcessordetermineCandidateConstructors方法也会将该构造方法加入候选构造方法集合中。

​   因此,实际开发过程中,我们想要完成依赖注入,通常会有如下途径:

tex 复制代码
1.在想要注入的字段上使用@Autowired和@Resource注解
2.@AllArgsConstructor修饰在类上,为类中所有字段生成构造方法,相当于给所有字段完成依赖注入
3.@RequiredArgsConstructor+final/@NonNull:@RequiredArgsConstructor修饰在类上,在想要依赖注入的字段上使用@NonNul,或者将该字段定义成final
相关推荐
星沁城8 分钟前
172. 阶乘后的零
java·算法·leetcode
小猫咪怎么会有坏心思呢14 分钟前
华为OD机考-小明减肥-DFS(JAVA 2025B卷)
java·华为od·深度优先
Cosmoshhhyyy18 分钟前
Spring-AI-Alibaba快速体验(配置流程和注意事项)
java·spring boot·spring
饕餮争锋31 分钟前
设计模式笔记_创建型_单例模式
java·笔记·设计模式
why1511 小时前
6.15 操作系统面试题 锁 内存管理
后端·性能优化
Y1_again_0_again1 小时前
Java 包装类详解
java·开发语言
丘山子1 小时前
如何确保 Go 系统在面临超时或客户端主动取消时,能够优雅地释放资源?
后端·面试·go
武子康1 小时前
Java-52 深入浅出 Tomcat SSL工作原理 性能优化 参数配置 JVM优化
java·jvm·后端·servlet·性能优化·tomcat·ssl
别骂我h1 小时前
容器技术技术入门与Docker环境部署
java·spring cloud·docker
OnlyLowG1 小时前
SpringSecurity导致redis压力大问题解决
后端