# 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实际上是目标对象,而非代理对象,所以代理会失效,也就不会执行增强逻辑
解决方案:
将 B 方法放到另一个 Service 中
从 Spring 容器中获取自己的代理对象:
((ServiceA) AopContext.currentProxy()).methodB()@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
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属性:
先按字段/方法名查找Bean(
userService)如果找不到,再按类型(
UserService.class)查找如果按类型找到多个,则抛出异常
补充:
@Primary:当有同类型的bean时,标注哪个为优先使用
@Value
注入具体值 (
@Value(18))读取配置文件中的值
设置配置文件中没有的属性的默认值
注入系统环境变量和JVM参数
注入其他 Bean 的属性,假设容器中有一个名为 'dataSource' 的 Bean,我们直接获取它的 url 属性 @Value("#{dataSource.url}")
🟢 Bean的生命周期
实例化
set方法注入属性
前置方法
初始化
后置方法
实例的使用
bean的销毁