1. 什么是Spring自动装配?

Spring的自动装配是IOC容器依据特定规则,自动给Bean注入依赖,而不需要手动new或者显式配置依赖关系。
它的触发注解是@Autowired,底层是依赖于Bean的自动注入(DI)实现的。
他的实现过程是这样:

  • 第一步:容器启动时会自动扫描带有@Component注解或者@Service@Component的子类注解,将其标注的类解析为BeanDefinition,同时实例化为Bean存到IOC容器中
  • 第二步:会通过AutowiredAnnotationPostProcessor后置处理器,扫描Bean中的的@Autowired,@Resource等注解,将其标记为依赖项
  • 第三步:会按依赖项的依赖类型去IOC容器中查找,如果只有一个类型就直接进行依赖注入;如果有多个类型会通过依赖项的名称进行依赖注入或者会通过@Qualifier指定优先级进行依赖匹配
  • (这里官方推荐的注入方式是构造注入>set注入>字段注入)
  • 第四步:将找到的依赖进行注入,完成自动装配

2. 什么是SpringBoot的自动装配?

SpringBoot的自动装配核心是基于Spring自动装配能力的扩展,不需要写大量的配置类,体现了SpringBoot约定大于配置的要点。
SpringBoot自动装配过程是这样:

  • 第一步:SpringBoot项目启动时会触发一个EnableAutoConfiguration注解,这个注解会触发AutoConfigurationImportSelector类
  • 第二步:AutoConfigurationImportSelector类会进一步调用SpringFactoriesLoader,去扫描META-INF/Spring.factories的配置文件(SpringBoot2.7以前),或者是META-INF/spring/org/springframework.boot.autoconfigure.AutoConfiguration.imports配置文件(SpringBoot2.7以后)
  • 第三步:会按需加载配置文件对应的配置类,如DateSourceAutoConfiguration,SpringMVCAutoConfiguration等(这里按需是因为这些配置类有好多Canditional(条件)注解)
  • 第四步:将满足条件的Bean进行注入到IOC容器中,再通过Spring自动装配能力完成依赖注入,这样就完成了SpringBoot的自动装配。

3. IOC和AOP的核心概念?

IOC:也被叫做控制反转,它的核心是反转对象的创建和依赖管理权,由开发者手动new对象,管理依赖,转交给Spring IOC容器进行统一的创建,存储,装配和管理。核心实现是依赖注入(DI),目的是解耦组件间依赖
IOC是Spring的核心骨架,管理所有Bean的生命周期和依赖。为AOP提供基础(AOP的切面,通知类,他们都需要IOC管理)。

AOP:也被叫做面向切面编程,核心是无侵入的进行增强代码功能,把日志,事务,鉴权校验等重复公共逻辑(切面)抽离,在目标方法执行的指定时机(切点)自动切入,不修改业务代码,实现业务与公共功能解耦
AOP是IOC的功能增强,基于IOC容器实现切面的动态织入,两者共同支撑了Spring的核心生态。

4. 出现循环依赖的问题如何解决?

先解释一下什么是循环依赖:两个及以上对象 / Bean 互相持有对方的引用,形成闭环引用,比如A依赖B,B又依赖A;或A→B→C→A的环形依赖。
对于手动的new对象,会直接导致实例化死循环 / 堆溢出(比如 A 的构造器 new B,B 的构造器 new A)。
对于Spring框架来说:框架对单例 Bean 的 setter 注入 / 字段注入做了处理,能解决大部分循环依赖,但构造器注入的单例 Bean、多例 Bean的循环依赖框架无法处理,会抛出BeanCurrentlyInCreationException。

Spring默认已解决的循环依赖场景:

Spring 容器仅能解决单例作用域下,通过setter 注入 / 字段注入(@Autowired)/ 方法注入的 Bean 循环依赖,它的底层通过三级缓存实现:

  • 一级缓存(singletonObjects):存放完全初始化完成的单例 Bean;
  • 二级缓存(earlySingletonObjects):存放提前暴露的、已实例化但未完成属性注入 / 初始化的 Bean;
  • 三级缓存(singletonFactories):存放 Bean 的工厂对象,用于动态创建代理对象并提前暴露。
    核心逻辑:将 Bean 的创建过程拆分为「实例化」和「初始化(属性注入 + 初始化方法)」两个阶段,在实例化后立即将半成品 Bean 提前暴露到三级缓存中,当后续依赖注入发现目标 Bean 正在创建时,直接从缓存中获取半成品 Bean 完成注入,从而打破 “互相等待对方创建完成” 的死循环。

Spring无法解决的循环依赖场景 & 解决方案

场景 1:构造器注入导致的单例 Bean 循环依赖(最常见)

问题:A 的构造器入参是 B,B 的构造器入参是 A,Spring 实例化时需要先创建依赖对象,形成死循环。

1. 改为 setter 注入 / 字段注入(最推荐,符合 Spring 原生支持)

移除构造器中的依赖,改用@Autowired字段注入或 setter 上标注@Autowired:

1
2
3
4
5
6
7
8
9
10
11
// 原错误:构造器注入循环依赖
@Component
public class A { private B b; public A(B b) { this.b = b; } }
@Component
public class B { private A a; public B(A a) { this.a = a; } }

// 修正:字段注入
@Component
public class A { @Autowired private B b; }
@Component
public class B { @Autowired private A a; }
2. 使用@Lazy懒加载(适合不想改注入方式的场景)

在构造器入参上标注@Lazy,让 Spring 创建代理对象而非真实对象,延迟真实对象的初始化,打破循环:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class A {
private B b;
// 对B懒加载,注入的是代理对象,首次使用时才创建真实B
public A(@Lazy B b) { this.b = b; }
}
@Component
public class B {
private A a;
public B(A a) { this.a = a; } // 无需双端加@Lazy,一端即可
}
3. 使用@DependsOn指定依赖加载顺序(兜底方案,慎用)

强制指定 Bean 的创建顺序,让其中一个 Bean 先完成初始化,适合复杂场景:

1
2
3
4
5
@Component
@DependsOn("b") // 强制先创建B,再创建A
public class A { @Autowired private B b; }
@Component
public class B { @Autowired private A a; }
场景 2:多例(prototype)Bean 的循环依赖

问题:Spring 对prototype(每次获取都新建)Bean 不做缓存,因此无法通过三级缓存提前暴露半成品对象,无论哪种注入方式,循环依赖都会报错。

1. 改为单例(singleton)Bean(最推荐,大部分业务 Bean 无需多例);
2. 手动获取 Bean(通过ApplicationContext按需获取,放弃 Spring 自动注入):

让 Bean 实现ApplicationContextAware,从容器中手动获取依赖的 Bean,避免自动注入的循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component
@Scope("prototype")
public class A implements ApplicationContextAware {
private B b;
private ApplicationContext ctx;

@Override
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}

// 按需获取B,而非初始化时注入
public B getB() {
if (b == null) {
b = ctx.getBean(B.class);
}
return b;
}
}

@Component
@Scope("prototype")
public class B implements ApplicationContextAware {
private A a;
private ApplicationContext ctx;

@Override
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}

public A getA() {
if (a == null) {
a = ctx.getBean(A.class);
}
return a;
}
}
场景 3:单例 Bean 的代理对象循环依赖(如 AOP 增强的 Bean)

问题:Bean 被 AOP(@Transactional/@Aspect)增强,Spring 会创建代理对象,若代理对象参与循环依赖,三级缓存的工厂会动态创建代理对象,若配置不当会报错。

1. 使用@Lazy配合代理(通用方案);
2. 开启 Spring 的allowRawInjectionDespiteWrapping配置(SpringBoot),允许原始对象注入(兜底)
1
2
3
4
# application.yml
spring:
main:
allow-raw-injection-despite-wrapping: true # 允许注入原始对象而非代理对象
场景 4:自定义 BeanPostProcessor 导致的循环依赖

问题:自定义的BeanPostProcessor在 Bean 初始化前做增强,若处理器中依赖了循环依赖的 Bean,会打断 Spring 的三级缓存流程。

1. 让自定义BeanPostProcessor实现PriorityOrdered,提高执行优先级;
2. 避免在BeanPostProcessor中注入循环依赖的 Bean,改用ApplicationContext手动获取。
场景 5:纯 Java 代码(非 Spring)的循环依赖解决(面试延伸题)
方案 1:拆分构造器,通过 setter 后续赋值(最常用)

先无参构造实例化对象,再通过 setter 方法设置依赖,避免构造器中互相 new:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 原错误:构造器循环new,导致栈溢出
public class A { private B b; public A() { this.b = new B(); } }
public class B { private A a; public B() { this.a = new A(); } }

// 修正:无参构造+setter赋值
public class A {
private B b;
public A() {} // 无参构造
public void setB(B b) { this.b = b; }
}
public class B {
private A a;
public B() {} // 无参构造
public void setA(A a) { this.a = a; }
}

// 调用时:先实例化,再赋值,打破循环
public class Test {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
}
方案 2:使用懒加载(延迟创建依赖对象)

通过懒加载初始化(比如第一次使用时才创建依赖),避免初始化时的循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class A {
private B b;
// 懒加载获取B,首次调用才创建
public B getB() {
if (b == null) {
b = new B();
b.setA(this); // 将当前A对象传给B
}
return b;
}
}
public class B {
private A a;
public void setA(A a) { this.a = a; }
}

// 调用:仅当使用a.getB()时,才创建B并建立引用
public class Test {
public static void main(String[] args) {
A a = new A();
B b = a.getB();
}
}
方案 3:引入中间层 / 拆分业务(从根源解决)

循环依赖的本质往往是类的职责过于臃肿,违反单一职责原则,拆分类的功能,抽离公共逻辑到中间层,从根源消除循环依赖(工程化最佳实践)。

示例:A 依赖 B 的支付功能,B 依赖 A 的订单功能→抽离PayService和OrderService作为中间层,A 和 B 分别依赖中间层,而非互相依赖。

根源规避:遵循单一职责原则拆分类、引入中间层解耦、基于接口编程,从设计上消除循环依赖。

5. 为什么JDK1.8的HashMap要引入红黑树?

JDK1.8 的 HashMap 引入红黑树,核心是优化哈希冲突后链表过长的性能问题,具体原因和设计考量如下:

  • 解决性能退化:JDK1.7 及之前是数组 + 链表,哈希冲突严重时链表会极长,导致get/put操作从 O (1) 退化到 O (n),红黑树将其稳定在 O (logn),极端场景下保证 HashMap 的性能;
  • 红黑树是最优选择:红黑树是弱平衡二叉搜索树,相比严格平衡的 AVL 树,插入 / 删除的旋转开销更小,适配 HashMap 读写频繁的场景;相比普通二叉查找树,不会因有序插入退化为链表,保证结构有效性;
  • 按需树化,避免过度优化:设置了链表转红黑树的双阈值(节点≥8、数组容量≥64),且红黑树节点≤6 时退化为链表,兼顾了不同节点数量下的效率,避免无意义的树维护开销。

6. 什么是MCP?

  • MCP 即 Mesh Configuration Protocol,服务网格配置协议,是云原生服务网格领域的标准化控制面通信协议,基于 gRPC+Protobuf 实现。
  • 它主要用于服务网格控制面组件之间、多集群控制面之间、控制面与配置源之间,完成路由策略、安全规则、服务信息的标准化、增量、可靠同步,解决早期控制面耦合、多集群配置不一致、异构配置源无法互通的问题。
  • MCP 与数据面使用的 xDS 协议互补:xDS 负责控制面到 Envoy 数据面的转发规则下发,MCP 负责控制面内部及跨集群的配置同步,是现代服务网格实现解耦、扩展、多集群统一治理的关键协议。