# 04.面试八股-Spring篇

# 🟢 第一问

面试官:Spring IoC 是什么?解决了什么问题?

Ioc是控制反转,他实现了将所有对象交给IOC容器管理,需要的时候可以从IOC容器中获取,将对象进行了统一的管理

解决的问题:

  • 降低了代码的耦合度,不用到处new

  • 统一管理对象生命周期(单例)

  • 方便进行依赖注入

# 🟡 第二问(追问)

面试官:DI 的几种注入方式?你推荐哪种?为什么?

DI有三种依赖注入的方式

  • 属性注入

  • 构造器注入

  • set方法注入

我推荐构造器注入的方式,因为构造器注入

  • 可以加final字段,一旦初始化,对象不能改变,线程更安全

  • 能确保对象在实例化完成时,所有必须的依赖都已经准备就绪。避免了在使用对象时出现 NullPointerException

  • 直接抛出循环依赖异常,提前暴露问题;字段注入能隐式解决循环依赖,埋下隐患。

# 🔴 第三问(深度追问)

面试官:构造器注入能解决循环依赖,那 Spring 用字段注入时,循环依赖是怎么解决的?

Spring 通过三级缓存解决循环依赖(仅限单例 + 字段/setter 注入)

  • 一级缓存 SingletonObjects (成品对象)

  • 二级缓存 earlySingletonObjects(半成品对象)

  • 三级缓存 singletonFactories(对象工厂,用于生成代理)

1.实例化A,放入3级缓存

2.发现需要B,去寻找B

3.实例化B,放入3级缓存

4.注入B属性,发现需要A,从3级缓存拿到A的工厂,创建代理后放入2级缓存

5.B实例化完成,放入一级缓存

6.继续初始化A

# 🟢 第一问

面试官:AOP 是什么?有哪些核心概念?

AOP是面向切面编程,在不改变原有代码的基础上,对代码进行增强逻辑

核心概念:连接点、切入点、切面、通知、织入

概念 说明
切面(Aspect) 横切关注点的模块化(如日志切面)
连接点(JoinPoint) 可以被拦截的方法
通知(Advice) 切面在具体连接点执行的动作(@Before、@After、@Around)
切点(Pointcut) 匹配连接点的表达式(切入点表达式)
织入(Weaving) 将切面应用到目标对象的过程

# 🟡 第二问(追问)

面试官:Spring AOP 底层用的是 JDK 动态代理还是 CGLIB?什么情况下用哪个?

条件 代理方式
目标类实现了接口 默认 JDK 动态代理(代理接口)
目标类没有实现接口 CGLIB(生成子类)
强制使用 CGLIB @EnableAspectJAutoProxy(proxyTargetClass = true)

区别

  • JDK 动态代理:基于接口,被代理类必须有接口

  • CGLIB:基于继承,不能代理 final 类和方法

补充知识JDK动态代理

底层:反射 + 接口

关键两个角色:

  • 被代理类:必须实现接口
  • InvocationHandler:调用处理器,增强逻辑写在这里
  • Proxy:工具类,用来生成代理对象

执行流程:

  • 目标类实现业务接口
  • 自定义 InvocationHandler,重写 invoke() 方法(前置 / 后置 / 环绕增强
  • 通过 Proxy.newProxyInstance() 创建代理对象
  • 调用代理对象方法 → 进入 invoke() → 执行增强 + 原目标方法

补充知识CGLIB动态代理

Cglib 是基于字节码 ASM 技术,通过「继承目标类生成子类」来做代理,不需要接口,直接代理普通类。

关键角色:

  • Enhancer:Cglib 核心工具类,用来创建代理对象

  • MethodInterceptor:方法拦截器,等同于 JDK 的 InvocationHandler增强逻辑写在这里

  • 目标类:普通 Java 类,不用实现任何接口

执行流程:

  • 创建 Enhancer 对象

  • 设置父类(目标类)

  • 设置回调拦截器MethodInterceptor,动态生成字节码、创建代理子类对象

  • 调用代理对象的方法(其中包括了原始逻辑和增强逻辑)

# 🔴 第三问(深度追问)

面试官:同一个类中,方法 A 调用方法 B(B 有 @Transactional),事务会生效吗?为什么?

事务不会生效

原因:事务是基础Spring Aop 实现的,方法A用this.方法B中的this实际上是目标对象,而非代理对象,所以代理会失效,也就不会执行增强逻辑

解决方案

  1. 将 B 方法放到另一个 Service 中

  2. 从 Spring 容器中获取自己的代理对象:((ServiceA) AopContext.currentProxy()).methodB()

  3. @EnableAspectJAutoProxy(exposeProxy = true) 开启 exposeProxy

补充知识:事务失效的几种情况

  • 方法非 public;(SpringAOP只拦截public方法)
  • 本类内部自调用没走代理;(上述的情况)
  • 方法被 final、static 修饰;(final不能被重写,不能走代理,static方法不属于实例,属于类,代理对象拦不住)
  • 类没交给 Spring 容器管理;(Spring不会帮你生成代理对象)
  • 异常被 try-catch 吞掉没抛出;(不会触发回滚,事务失效)
  • 受检异常没指定 rollbackFor;(默认不回滚,必须手动指定)
  • 数据库引擎是 MyISAM 不支持事务;
  • 多线程跨线程事务;(和主线程事务互不影响,无法统一回滚。)
  • 事务传播行为配置错误;
  • 手动修改自动提交、隔离级别配置错误。

# 🟢 第一问

面试官@Transactional 的原理是什么?

原理:AOP为事务方法创建代理对象

代理对象内部的事务拦截器执行:

  • 开启事务

  • 执行业务方法

  • 无异常提交事务

  • 有异常回滚事务

# 🟡 第二问(追问)

面试官@Transactional 默认在什么异常下回滚?怎么指定特定异常不回滚?

  • 默认回滚RuntimeException 和 Error(非受检异常)

  • 默认不回滚Exception 的子类(受检异常,如 IOException)

可通过rollbackFor强制回滚

# 🔴 第三问(深度追问)

面试官@Transactional 的传播机制有哪些?REQUIRED 和 REQUIRES_NEW 的区别是什么?

传播行为常量 中文含义 核心逻辑
REQUIRED(默认) 支持当前事务,无则新建 1. 若外层有事务,内层复用外层事务;2. 若外层无事务,内层新建事务。(内失败,外跟着失败,同生共死)
REQUIRES_NEW 新建事务,挂起当前事务 1. 无论外层是否有事务,内层都新建独立事务;2. 外层事务和内层事务互不影响。(保存日志,外层失败,内层成功)
NESTED 嵌套事务 1. 内层事务是外层事务的 “子事务”;    2. 外层回滚则内层必回滚,内层回滚不影响外层。(下单送积分,积分失败不影响下单,下单失败,影响积分)
SUPPORTS 支持当前事务,无则无事务 1. 外层有事务则复用;
2. 外层无事务则以非事务方式执行。
NOT_SUPPORTED 不支持事务,挂起当前事务 1. 无论外层是否有事务,内层都以非事务方式执行;
2. 外层事务会被挂起,执行完恢复。
MANDATORY 必须有当前事务,否则报错 1. 外层必须有事务,否则抛 IllegalTransactionStateException
2. 强制依赖外层事务。
NEVER 必须无当前事务,否则报错 1. 外层不能有事务,否则抛 IllegalTransactionStateException
2. 强制非事务执行。

# 🟢 第一问

面试官:Spring Boot 的自动配置原理是什么?

SpringBootApplication=Configuration+ComponentScan+EnableAutoConfiguration

加载+过滤

  • @SpringBootApplication找到注解中的@EnableAutoConfiguration

  • @EnableAutoConfiguration找到@AutoConfigurationPackage,通过 Registrar 将启动类所在的包路径注册到 Spring 容器中

  • 加载自动配置类:AutoConfigurationImportSelector读取spring-boot-autoConfiguration包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置类列表

  • Conditional注解判断是否加载某个类

“Spring Boot 自动配置的核心原理,其实就是加载 + 过滤
首先,启动类上的 @SpringBootApplication 包含了 @EnableAutoConfiguration,它通过 @Import 导入了 AutoConfigurationImportSelector 类。
这个类会利用 加载候选配置(SPI 机制) 机制,去读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,把 Spring Boot 预定义好的上百个自动配置类加载进来。
但是,这些配置类不会全部生效。Spring Boot 会通过 @Conditional 系列条件注解(比如判断类路径下有没有某个类、容器里缺不缺某个 Bean)进行按需过滤。只有满足条件的配置才会生效,并且遵循‘用户自定义优先’的原则。这就是 Spring Boot 能够实现‘开箱即用’和‘约定大于配置’的底层原理。

# 🟡 第二问(追问)

面试官:如何排除某个自动配置(比如不想用 DataSource 自动配置)?

方式一@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

方式二:配置文件

spring:
 autoconfigure:
    exclude:
     - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
1
2
3
4

# 🟡 题:Bean 的作用域与线程安全

面试官:Spring Bean 默认是单例的,那 Controller、Service 中有成员变量,线程安全吗?

答:线程不安全,因为Spring Bean默认是单例的,所有线程共享这一实例,比如在某个类中定义一个count成员变量,定义一个count++方法,那么这个count++会计数崩溃。

解决方法:

  • 定义局部变量,不定义成员变量

  • 使用ThreadLocal进行线程隔离

# 🟡 题:@Autowired 与 @Resource 的区别

面试官@Autowired 和 @Resource 有什么区别?

@Autowired:自动按类型注入依赖对象

@Qualifier(配合@Autowired使用):当容器中有多个同类型 Bean 时,通过名称指定要注入的 Bean,解决 @Autowired 按类型匹配的冲突问题。

@Resource:按名称注入依赖对象(默认),也可指定按类型注入,是 @Autowired + @Qualifier 的替代方案。

未指定name属性:

  1. 先按字段/方法名查找Bean(userService

  2. 如果找不到,再按类型UserService.class)查找

  3. 如果按类型找到多个,则抛出异常

补充:

@Primary:当有同类型的bean时,标注哪个为优先使用

@Value

  • 注入具体值 (@Value(18)

  • 读取配置文件中的值

  • 设置配置文件中没有的属性的默认值

  • 注入系统环境变量和JVM参数

  • 注入其他 Bean 的属性,假设容器中有一个名为 'dataSource' 的 Bean,我们直接获取它的 url 属性 @Value("#{dataSource.url}")

🟢 Bean的生命周期

  • 实例化

  • set方法注入属性

  • 前置方法

  • 初始化

  • 后置方法

  • 实例的使用

  • bean的销毁

最近更新: 9/19/2026, 1:27:08 PM
04.面试八股-Spring篇

编程NOTE   |