摘要:说实话,这个问题虽然看起来简单,但真要说清楚,背后的技术细节那是相当多!
哈喽大家好,我是小米!
今天这篇文章,咱们来聊聊一个Spring面试中“永远的经典题”:
使用 @Autowired 注解自动装配的过程是怎样的?
说实话,这个问题虽然看起来简单,但真要说清楚,背后的技术细节那是相当多!
别怕,我今天就用一个“讲故事”的方式,带你从表层用法一直追到底层源码逻辑,把这个知识点讲得明明白白、通通透透,面试官听了都得说一句“懂得真细”~
我们先来设个场景:
我刚跳槽到一家互联网公司的架构组,公司用的是Spring Boot,全套微服务架构,Bean 管理基本都是用的注解。
我负责一个叫 订单中台 的模块,代码大致是这样的:
你看,很简单,就是通过 @Autowired 把 PaymentService 注入进来。
但是你有没有想过:
1、Spring 是怎么知道 paymentService 是谁?
2、它是在什么时候把这个对象“塞”进来的?
3、如果有多个 PaymentService 实现,它又是怎么判断该用哪个的?
这些问题一层层抛出来,就像是剥洋葱,越剥越精彩!
“帮你把需要的 Bean 自动装配进来。”
它可以用在:
构造方法上字段上Setter方法上Spring 通过它来做 依赖注入(Dependency Injection,简称DI)。
小知识点:
Spring 支持三种依赖注入方式:构造器注入、Setter注入和字段注入,@Autowired 三种都支持。
自动装配的原理,其实可以分三步理解:
第一步:找所有需要装配的地方
Spring 在 初始化 Bean 的时候,会分析这个类中是否有被 @Autowired 注解标记的字段、方法、构造函数。如果有,它就会把这个地方标记为“需要注入”。
这部分的操作是在 Bean 后处理器(BeanPostProcessor) 里完成的。
核心类:AutowiredAnnotationBeanPostProcessor
它是 Spring 容器中非常重要的一个 Bean 后置处理器,它的作用是:
在 Spring 初始化 Bean 时,解析所有标注了 @Autowired 和 @Value 的字段或方法,并完成注入。
这一步其实发生在 Instantiation -> Populate -> Initialization 的过程中,属于 Bean 的生命周期中的“populate”阶段。
第二步:找到合适的Bean
Spring 会根据字段的类型来找出所有可以注入的 Bean 候选者。
假如你有这样两个 Bean:
然后你在 OrderService 中这么写:
这时 Spring 就会很“头疼”——
“哎哟我去,这两个 Bean 都能注入,到底选谁?”
这时候,如果你没加任何说明(比如没加 @Qualifier),就会抛出 NoUniqueBeanDefinitionException 异常。
所以,Spring 的选择逻辑其实是这样的:
先根据类型找候选Bean;如果有多个候选,就看是否有使用 @Qualifier 限定;如果没有,就看有没有标注了 @Primary 的 Bean;如果还是有歧义,就报错。你可以通过 @Primary 或 @Qualifier("beanName") 指定注入哪个 Bean。
第三步:执行注入
当找到了合适的 Bean,Spring 就会用 反射 的方式把这个 Bean 设置进对应的字段或者调用 Setter 方法,或者作为构造函数参数注入。
核心代码是通过 Java 的反射 API 实现,比如 Field.setAccessible(true) + Field.set。
注意!Spring 中依赖注入是通过反射完成的,而不是传统的 new。
这一过程的实现就在 AutowiredAnnotationBeanPostProcessor 中。
程序员不能只讲道理,咱得看源码!
我来带你看几个关键方法:
类:AutowiredAnnotationBeanPostProcessor
这是核心处理器,继承了 InstantiationAwareBeanPostProcessorAdapter,所以它可以参与 Bean 的创建过程。
方法一:postProcessProperties
它做了两件事:
找到需要注入的字段和方法;执行注入操作。方法二:findAutowiringMetadata
这个方法会扫描整个类,找出哪些字段、方法上有 @Autowired。
内部会构建一个 InjectionMetadata 对象,里面存着所有“要注入的点”。
方法三:inject
这个方法是真正干活的!
它会遍历所有注入点,通过 BeanFactory.resolveDependency 找到 Bean,然后反射注入进去。
最终注入依赖的核心就是:
就是这么朴实无华地把对象塞进去了
这时候处理器是另外一个阶段介入的——Spring 会在创建对象时,就用构造器来传入依赖。
构造器注入的处理主要由:
这个方法负责解析最合适的构造方法,并注入依赖。
它的选择规则是:
小米的经验总结:
字段注入易写易读,但不利于测试;构造器注入代码更健壮,便于单测和依赖控制。
构造器注入的优点包括:
明确依赖关系不依赖 Spring 的反射注入,纯 Java 构造可测试能确保对象创建时依赖齐全如果你以后写一些核心服务类,建议用构造器注入!
当然有!下面是几个延伸问题,面试中很可能被问到:
问题一:@Autowired 和 @Resource 有什么区别?
问题二:@Autowired 什么时候起作用?
在 Bean 实例化之后,初始化之前(也就是 populate 阶段)。
可以结合 Bean 生命周期流程复习一下:
实例化设置属性(populate)初始化(调用 init-method)AOP代理增强等今天我们从一个最简单的注解 @Autowired 出发,一层层剖析了 Spring 的自动装配机制,过程中经历了:
Bean 后置处理器的介入依赖查找与解析反射注入的实现原理构造器与字段注入的异同你会发现,看似一个简单的注解,背后竟然藏着这么多技术细节!
如果你在面试中遇到这个问题,可以这样回答:
“@Autowired 是 Spring 提供的自动装配注解,它通过 AutowiredAnnotationBeanPostProcessor 实现依赖注入。这个处理器在 Bean 初始化阶段介入,扫描字段或方法的注解,找到需要注入的地方,然后根据类型从容器中查找 Bean,通过反射完成注入。如果存在多个实现,可以通过 @Qualifier 或 @Primary 来指定注入对象。构造器注入则在实例化时完成,推荐用于强依赖场景。”
来源:中国在线教育智库网