一、Spring IoC 容器 高频面试题
1. 什么是Spring IoC?IoC和DI的区别与联系是什么?(必问)
答案:
① 定义:IoC(Inversion of Control,控制反转)是Spring的核心思想,本质是反转Bean的创建、依赖管理和生命周期的控制权——传统开发中,开发者手动new对象、维护依赖;IoC模式下,控制权转移到Spring IoC容器,容器负责创建Bean、注入依赖、管理Bean生命周期,开发者仅需配置Bean,无需关心具体实现。
② 区别与联系:
- 联系:DI(Dependency Injection,依赖注入)是IoC的具体实现方式,没有DI,IoC思想无法落地;没有IoC,DI也失去存在意义,二者不可分割。
- 区别:IoC是「设计思想/原则」,强调“控制权反转”;DI是「实现手段」,强调“通过容器将依赖注入到目标Bean”,是IoC思想的具体落地操作。
2. Spring IoC容器的核心接口有哪些?BeanFactory和ApplicationContext的区别是什么?(必问)
答案:
① 核心接口:顶层接口是BeanFactory,核心实现接口是ApplicationContext(BeanFactory的子接口)。
② 核心区别(3点核心,面试重点):
| 对比维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载方式 | 懒加载(仅调用getBean()时才创建Bean) | 预加载(容器启动时,自动创建所有单例Bean,除非配置懒加载) |
| 功能范围 | 基础功能(获取Bean、判断Bean存在等),无扩展功能 | 继承BeanFactory所有功能,新增国际化、事件发布、资源加载、AOP集成等增强功能 |
| 实际应用 | Spring内部使用,开发中极少直接使用 | 开发首选,如ClassPathXmlApplicationContext、AnnotationConfigApplicationContext(SpringBoot底层核心) |
3. Spring Bean的生命周期是什么?(必问,重点)
答案:
Spring Bean的生命周期贯穿「创建→初始化→使用→销毁」,核心4个阶段,按顺序执行(ApplicationContext容器下):
-
Bean定义阶段:容器加载Bean配置(XML/注解),解析为BeanDefinition对象,存储到BeanDefinitionRegistry(注册表),记录Bean的类名、作用域、依赖等信息。
-
Bean实例化阶段:容器根据BeanDefinition,调用Bean的构造器(默认无参构造)创建Bean实例(仅分配内存,未注入依赖)。
-
Bean初始化阶段(核心):
- 注入依赖:容器将依赖的Bean通过构造器/setter/字段注入到当前Bean。
- 调用Aware接口方法:依次调用BeanNameAware(注入Bean名称)、BeanFactoryAware(注入容器实例)、ApplicationContextAware(注入ApplicationContext,仅ApplicationContext容器支持)。
- BeanPostProcessor前置增强:调用postProcessBeforeInitialization(),对Bean进行初始化前增强(AOP动态代理前置操作)。
- 自定义初始化:执行@PostConstruct注解方法(优先)或init-method配置的方法。
- BeanPostProcessor后置增强:调用postProcessAfterInitialization(),生成AOP动态代理对象(核心,AOP实现关键)。
-
Bean销毁阶段:容器关闭时(仅单例Bean,原型Bean由开发者手动管理),执行@PreDestroy注解方法(优先)或destroy-method配置的方法,释放资源。
4. Spring Bean的作用域有哪些?单例和原型的核心区别是什么?(高频)
答案:
① 核心作用域(5种,重点前2种):
- singleton(单例,默认):容器中仅存在一个Bean实例,所有请求共享该实例,生命周期由容器管理。
- prototype(原型):每次调用getBean(),容器都会创建一个新的Bean实例,生命周期由开发者手动管理(容器不负责销毁)。
- request(Web场景):每个HTTP请求创建一个新Bean,请求结束后销毁。
- session(Web场景):每个HTTP Session创建一个新Bean,Session过期后销毁。
- globalSession(分布式场景):多服务器共享一个Bean,仅用于Portlet环境(极少用)。
② 单例和原型的核心区别:
- 实例数量:单例仅1个,原型每次请求新实例。
- 生命周期:单例由容器管理(创建→销毁),原型容器仅负责创建,销毁由开发者手动处理。
- 性能:单例性能优(无需频繁创建实例),原型性能略差(频繁创建/销毁)。
5. Spring IoC如何解决循环依赖?三级缓存分别是什么?(高频难点)
答案:
① 循环依赖定义:两个/多个Bean互相依赖(如A依赖B,B依赖A),导致容器无法正常初始化Bean。
② Spring支持情况:仅支持「单例Bean + setter注入/字段注入」,不支持「单例Bean + 构造器注入」「原型Bean(无论哪种注入)」。
③ 三级缓存核心原理(解决循环依赖的关键):
- 一级缓存(singletonObjects):存储「完全初始化完成」的单例Bean(可用状态)。
- 二级缓存(earlySingletonObjects):存储「提前暴露」的、未完全初始化的单例Bean(仅实例化,未注入依赖)。
- 三级缓存(singletonFactories):存储Bean的工厂对象(ObjectFactory),用于生成提前暴露的Bean实例(解决AOP动态代理的循环依赖)。
④ 简单流程:A实例化后放入三级缓存 → A需要注入B,B实例化后放入三级缓存 → B需要注入A,从三级缓存获取A的工厂,生成A的提前暴露实例(放入二级缓存) → B注入A后完成初始化(放入一级缓存) → A注入B后完成初始化(放入一级缓存)。
6. Spring Bean的注入方式有哪些?@Autowired和@Resource的区别是什么?(必问)
答案:
① 核心注入方式(3种):
- 构造器注入(推荐):通过@Autowired标注构造器,容器调用带参构造器注入依赖,能保证Bean注入的完整性(避免空指针)。
- setter注入:通过@Autowired标注setter方法,容器调用setter方法注入依赖,适用于可选依赖。
- 字段注入:通过@Autowired标注类字段,无需构造器和setter,简洁高效,但可读性略差,不推荐用于复杂依赖。
② @Autowired和@Resource的区别(3点核心):
- 来源不同:@Autowired是Spring自带注解;@Resource是JSR-250规范注解(Java原生)。
- 注入方式不同:@Autowired默认按「类型(byType)」注入,多个实现类需配合@Qualifier指定Bean名称;@Resource默认按「名称(byName)」注入,名称匹配失败再按类型注入。
- 支持范围不同:@Autowired可用于构造器、setter、字段;@Resource可用于字段、setter,不支持构造器注入。
二、Spring AOP 高频面试题
1. 什么是Spring AOP?AOP和OOP的区别与联系是什么?(必问)
答案:
① 定义:AOP(Aspect-Oriented Programming,面向切面编程)是Spring核心特性,与OOP互补,核心是「关注点分离」——将日志、事务、权限等通用功能(切面)从业务逻辑中剥离,动态织入到目标方法中,实现通用功能复用和解耦。
② 区别与联系:
- 联系:二者互补,OOP负责核心业务逻辑的封装(纵向复用),AOP负责通用功能的复用(横向复用),共同构成Spring的核心架构。
- 区别:OOP以「类」为核心,解决纵向代码重复;AOP以「切面」为核心,解决横向代码重复(如所有方法的日志记录)。
2. Spring AOP的核心概念有哪些?(必问,记准)
答案:
核心7个概念,简洁明了,面试直接答:
- 切面(Aspect):封装通用功能的类(如日志切面、事务切面),包含切入点和通知。
- 通知(Advice):切面中的具体逻辑(要做什么),Spring提供5种通知类型。
- 切入点(Pointcut):定义“在哪些方法上执行通知”,通过表达式指定目标方法(在哪里做)。
- 连接点(JoinPoint):程序执行中可插入切面的所有点(如方法调用、异常抛出),切入点是连接点的子集。
- 目标对象(Target):被切面增强的业务逻辑对象。
- 代理对象(Proxy):Spring AOP动态生成的对象,包含目标对象逻辑和切面通知逻辑,开发者实际调用。
- 织入(Weaving):将切面通知织入到目标方法的过程(Spring AOP是运行时织入)。
3. Spring AOP的5种通知类型是什么?执行顺序如何?(必问)
答案:
① 5种通知类型(按执行时机排序):
- 前置通知(@Before):目标方法执行前执行,无法阻止目标方法执行。
- 环绕通知(@Around):包裹目标方法,执行前后都能增强,可控制目标方法执行(需调用proceed()),功能最强。
- 返回通知(@AfterReturning):目标方法正常执行完成后执行(无异常)。
- 异常通知(@AfterThrowing):目标方法抛出异常后执行。
- 后置通知(@After):目标方法执行后执行(无论是否异常,都会执行),常用于资源释放。
② 执行顺序(单一切面):
@Before → 目标方法 → @AfterReturning/@AfterThrowing → @After; 环绕通知:@Around(前置部分) → 目标方法 → @Around(后置部分),环绕通知会包裹其他所有通知。
③ 多切面(按@Order注解排序,值越小优先级越高):
优先级高的切面:@Before先执行 → 优先级低的切面:@Before执行 → 目标方法 → 优先级低的切面:@AfterReturning/@AfterThrowing → 优先级低的切面:@After → 优先级高的切面:@AfterReturning/@AfterThrowing → 优先级高的切面:@After。
4. Spring AOP的实现原理是什么?JDK动态代理和CGLIB动态代理的区别是什么?(必问难点)
答案:
① 核心实现原理:Spring AOP基于「动态代理」,运行时动态生成代理对象,将切面通知织入到目标方法,不修改目标对象源码,符合开闭原则。
② 两种动态代理方式及区别:
| 对比维度 | JDK动态代理(默认) | CGLIB动态代理(补充) |
|---|---|---|
| 实现基础 | 基于Java自带的Proxy类和InvocationHandler接口 | 基于第三方CGLIB库,通过继承目标类实现 |
| 适用场景 | 目标对象必须实现至少一个接口 | 目标对象可无接口(也支持有接口) |
| 限制 | 无法代理无接口的类 | 无法代理final类、final方法(无法继承/重写) |
| 性能 | 效率高(JDK原生,无需额外依赖) | 效率略低(需动态生成子类),Spring已集成CGLIB |
③ Spring选择规则:目标对象有接口 → JDK动态代理;目标对象无接口 → CGLIB动态代理;SpringBoot 2.x+ 默认配置proxy-target-class=true,无论是否有接口,优先使用CGLIB。
5. 同一类中,被AOP增强的方法A调用方法B,B的AOP增强为什么会失效?如何解决?(高频实战题)
答案:
① 失效原因:Spring AOP基于动态代理,同一类中方法调用时,调用的是「目标对象自身的方法」,而非「代理对象的方法」,无法触发AOP的增强逻辑。
② 3种解决方案(优先前2种):
- 自注入:在类中通过@Autowired + @Lazy注解注入自身代理对象,调用时使用代理对象调用方法B(避免循环依赖)。
- 通过ApplicationContext获取代理对象:注入ApplicationContext,调用getBean()获取自身代理对象,再调用方法B。
- 拆分类:将方法A和方法B拆分到不同类中,避免同一类中方法调用。
示例(自注入方式):
@Service
public class UserService {
@Autowired
@Lazy // 避免循环依赖
private UserService userService; // 注入自身代理对象
@Log // AOP增强
public void methodA() {
userService.methodB(); // 调用代理对象的methodB,触发AOP
}
@Log // AOP增强
public void methodB() {
// 业务逻辑
}
}
6. Spring AOP的切入点表达式有哪些?常用的execution表达式语法是什么?(高频)
答案:
① 核心切入点表达式(3种,重点前2种):
- execution表达式(最常用):通过方法的修饰符、返回值、包名、类名、方法名、参数匹配目标方法。
- @annotation表达式:匹配被指定注解标注的方法(如@Log注解标注的方法)。
- within表达式:匹配指定包/类下的所有方法。
② execution表达式语法:
execution(修饰符 返回值类型 包名.类名.方法名(参数类型) throws 异常类型)
通配符说明:
- *:匹配任意单个字符(如返回值、包名、方法名、参数)。
- ..:匹配任意多个字符(如多级包、任意个数/类型的参数)。
常用示例:
- execution(* com.service..(..)):匹配com.service包下所有类的所有方法。
- execution(public * com.service.UserService.*(String, ..)):匹配UserService类中所有public方法,第一个参数为String。
- execution(* com.service...(..)):匹配com.service包及其子包下所有类的所有方法。
三、Spring 事务管理 高频面试题
1. 什么是事务?Spring事务的四大特性是什么?(必问)
答案:
① 事务(Transaction):是数据库操作的最小单元,一组操作要么全部成功,要么全部失败,保证数据的一致性。
② 事务四大特性(ACID,面试必须答全):
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部回滚,不可分割。
- 一致性(Consistency):事务执行前后,数据的完整性约束不被破坏(如转账前A+B=100,转账后仍为100)。
- 隔离性(Isolation):多个事务并发执行时,一个事务的操作不会影响另一个事务的执行(避免并发问题)。
- 持久性(Durability):事务执行成功后,数据会永久保存到数据库,即使系统崩溃也不会丢失。
2. Spring事务的隔离级别有哪些?默认隔离级别是什么?(必问)
答案:
① Spring事务隔离级别(基于数据库隔离级别,5种):
- DEFAULT(默认):继承数据库的默认隔离级别(MySQL默认REPEATABLE READ,Oracle默认READ COMMITTED)。
- READ_UNCOMMITTED(读未提交):最低隔离级别,允许读取未提交的数据,会出现脏读、不可重复读、幻读。
- READ_COMMITTED(读已提交):允许读取已提交的数据,避免脏读,会出现不可重复读、幻读(Oracle默认)。
- REPEATABLE_READ(可重复读):保证同一事务中多次读取同一数据结果一致,避免脏读、不可重复读,会出现幻读(MySQL默认)。
- SERIALIZABLE(串行化):最高隔离级别,事务串行执行,避免所有并发问题,性能最差(极少用)。
② 核心并发问题(对应隔离级别):
- 脏读:读取到其他事务未提交的数据(后续可能回滚,数据无效)。
- 不可重复读:同一事务中,多次读取同一数据,结果不一致(其他事务修改并提交)。
- 幻读:同一事务中,多次查询同一条件,结果行数不一致(其他事务新增/删除并提交)。
3. Spring事务的传播行为有哪些?常用的传播行为是什么?(必问)
答案:
① 事务传播行为:定义了“当一个事务方法调用另一个事务方法时,如何处理事务”(如是否新建事务、是否加入当前事务),Spring提供7种传播行为,重点记4种常用的:
- REQUIRED(默认):如果当前有事务,就加入当前事务;如果没有事务,就新建一个事务(最常用,如Service方法调用Dao方法)。
- REQUIRES_NEW:无论当前是否有事务,都新建一个事务,原事务暂停,新事务执行完成后,原事务继续执行(如日志记录,无论主事务成败,日志都要保存)。
- SUPPORTS:如果当前有事务,就加入当前事务;如果没有事务,就以非事务方式执行(不常用)。
- NESTED:如果当前有事务,就在当前事务中嵌套一个子事务;如果没有事务,就新建一个事务(子事务回滚不影响主事务,主事务回滚会带动子事务回滚)。
② 补充:NEVER(不允许有事务,有事务则抛异常)、MANDATORY(必须有事务,没有则抛异常)、NOT_SUPPORTED(以非事务方式执行,有事务则暂停)(极少用)。
4. Spring事务的实现方式有哪些?声明式事务和编程式事务的区别是什么?(必问)
答案:
① 两种实现方式:
- 编程式事务:通过代码手动控制事务(如TransactionTemplate、PlatformTransactionManager),灵活性高,代码侵入性强。
- 声明式事务:通过注解(@Transactional)或XML配置实现,无需手动写事务控制代码,代码侵入性低,是开发首选。
② 核心区别:
| 对比维度 | 编程式事务 | 声明式事务 |
|---|---|---|
| 代码侵入性 | 强(需手动写事务控制代码) | 弱(仅需标注@Transactional注解) |
| 灵活性 | 高(可手动控制事务的开始、提交、回滚) | 低(依赖注解/配置,无法灵活控制) |
| 开发效率 | 低(代码繁琐) | 高(简化开发,无需关注事务控制) |
| 适用场景 | 复杂事务场景(如多步骤事务控制) | 常规业务场景(如CRUD操作) |
5. @Transactional注解的核心属性有哪些?(高频)
答案:
@Transactional是声明式事务的核心注解,常用属性(面试重点记5个):
- propagation:事务传播行为(默认REQUIRED)。
- isolation:事务隔离级别(默认DEFAULT)。
- readOnly:是否为只读事务(默认false),设置为true时,只能执行查询操作,不能执行增删改,提升查询性能。
- rollbackFor:指定哪些异常触发事务回滚(如rollbackFor = Exception.class,所有异常都回滚),默认只回滚运行时异常(RuntimeException)和Error。
- noRollbackFor:指定哪些异常不触发事务回滚(如noRollbackFor = BusinessException.class)。
- timeout:事务超时时间(默认-1,无超时),单位秒,超时未完成则回滚。
6. Spring事务回滚失效的常见原因有哪些?(高频实战题)
答案:
核心6种原因(面试重点答,结合场景):
-
- 注解标注错误:@Transactional标注在非public方法上(Spring事务仅对public方法生效)。
-
- 异常类型不匹配:未指定rollbackFor,抛出的是检查异常(如IOException),默认不回滚。
-
- 异常被手动捕获:方法内try-catch捕获了异常,未重新抛出,Spring无法感知异常,无法回滚。
-
- 事务传播行为配置错误:如配置为SUPPORTS,当前无事务时,以非事务方式执行,无法回滚。
-
- 未开启事务管理:未在配置类上标注@EnableTransactionManagement(SpringBoot自动开启,纯Spring需手动开启)。
-
- 目标对象未被Spring管理:Bean未通过@Component、@Service等注解注册,Spring无法对其进行事务增强。
7. Spring事务的底层实现原理是什么?(难点)
答案:
Spring事务的底层依赖「AOP动态代理」和「事务管理器(PlatformTransactionManager)」,核心流程:
- 当方法标注@Transactional时,Spring AOP会为该方法生成动态代理对象。
- 代理对象调用目标方法前,通过事务管理器(如DataSourceTransactionManager)开启事务,获取数据库连接,设置事务隔离级别、传播行为等。
- 调用目标方法,执行业务逻辑和数据库操作。
- 若方法正常执行,事务管理器提交事务;若方法抛出异常(符合回滚条件),事务管理器回滚事务;若异常被捕获未抛出,事务提交。
- 事务执行完成后,释放数据库连接。
核心关键点:事务管理器是核心,不同的数据源(如JDBC、JPA)对应不同的事务管理器;AOP负责将事务控制逻辑(开启、提交、回滚)织入到目标方法中。
四、Spring 高级特性 高频面试题
1. Spring的BeanPostProcessor是什么?作用是什么?(高频)
答案:
① 定义:BeanPostProcessor是Spring IoC的核心扩展接口,称为「Bean后置处理器」,用于在Bean初始化前后对Bean进行增强处理,是AOP实现的核心基础。
② 核心作用:在Bean的初始化阶段(初始化前、初始化后)插入自定义逻辑,对Bean进行增强,无需修改Bean的源码。
③ 核心方法(2个):
- postProcessBeforeInitialization(Object bean, String beanName):Bean初始化前执行(在@PostConstruct、init-method之前),返回增强后的Bean。
- postProcessAfterInitialization(Object bean, String beanName):Bean初始化后执行(在@PostConstruct、init-method之后),返回增强后的Bean(AOP动态代理对象在此生成)。
注意:该接口的方法会对容器中所有Bean生效,若需针对性增强,可在方法中判断Bean类型。
2. Spring的FactoryBean和BeanFactory的区别是什么?(高频易混)
答案:
二者名称相似,但功能完全不同,核心区别:
| 对比维度 | BeanFactory | FactoryBean |
|---|---|---|
| 本质 | IoC容器的顶层接口,是「Bean工厂」,负责管理所有Bean的创建、依赖、生命周期 | 是一个「特殊的Bean」,用于创建复杂Bean(如数据源、SqlSessionFactory) |
| 作用 | 管理Bean(获取Bean、判断Bean存在等) | 创建目标Bean(自身是Bean,核心功能是生成其他Bean) |
| 使用方式 | 直接使用(如ApplicationContext是其实现类) | 注册到容器中,通过getBean()获取其生成的目标Bean(默认获取目标Bean,加&获取FactoryBean本身) |
示例:MyBatis的SqlSessionFactoryBean,注册到Spring容器后,getBean()获取的是SqlSessionFactory(目标Bean),而非SqlSessionFactoryBean本身。
3. Spring的事件驱动模型是什么?核心组成有哪些?(高频)
答案:
① 定义:Spring事件驱动模型基于「观察者模式」,实现组件间的解耦,允许一个组件发送事件,其他组件监听事件并做出响应,无需直接依赖。
② 核心组成(3个,缺一不可):
- 事件(ApplicationEvent):事件的载体,存储事件相关信息,自定义事件需继承ApplicationEvent。
- 事件发布者(ApplicationEventPublisher):负责发布事件,通过publishEvent()方法发送事件(Spring容器本身就是事件发布者)。
- 事件监听器(ApplicationListener):负责监听事件,实现该接口或标注@EventListener注解,当事件发布时,执行对应的处理逻辑。
示例:用户注册成功后,发布UserRegisterEvent事件,监听者监听该事件,执行发送短信、记录日志等操作,与注册逻辑解耦。
4. Spring的类型转换机制是什么?(了解,高频补充)
答案:
① 定义:Spring提供统一的类型转换机制,用于将一种数据类型转换为另一种数据类型(如String→Integer、String→Date),解决配置文件中参数类型与Java类字段类型不匹配的问题。
② 核心组件:
- Converter<S, T>:最基础的类型转换器,将S类型转换为T类型(如StringToIntegerConverter)。
- ConverterFactory:用于批量转换(如将String转换为所有Number类型)。
- Formatter:用于字符串与对象的双向转换(支持格式化,如Date与String的格式化转换),常用于Web场景。
③ 自定义类型转换:实现Converter接口,重写convert()方法,注册到Spring容器(通过@Bean或ConversionServiceFactoryBean)。
5. Spring Boot的自动配置原理和Spring的自动配置有什么区别?(结合Spring Boot,高频)
答案:
① 核心区别:Spring的自动配置是「基础能力」,Spring Boot的自动配置是「在Spring基础上的增强和简化」,本质是对Spring自动配置的封装,让配置更便捷。
② 具体区别:
- Spring自动配置:需手动开启(如@EnableAutoConfiguration),需手动引入依赖,自动配置类通过spring.factories文件注册,配置灵活但繁琐。
- Spring Boot自动配置:无需手动开启(@SpringBootApplication包含@EnableAutoConfiguration),通过Starter起步依赖自动引入相关依赖,自动配置类通过spring.factories(1.x/2.x)或AutoConfiguration.imports(3.x)注册,默认配置更完善,无需手动配置,实现“开箱即用”。
总结:Spring Boot的自动配置 = Spring自动配置 + 起步依赖 + 约定优于配置,简化了Spring应用的搭建和配置。
6. Spring的异步处理如何实现?@Async注解的使用注意事项是什么?(高频)
答案:
① 实现方式:Spring通过@Async注解实现异步处理,配合@EnableAsync注解开启异步支持,让方法异步执行(无需等待方法执行完成,提升应用响应速度)。
② 核心步骤:
- 在配置类上标注@EnableAsync,开启异步支持。
- 在需要异步执行的方法上标注@Async。
③ 使用注意事项(3点核心):
- @Async标注的方法必须是public方法(Spring异步仅对public方法生效)。
- 同一类中,异步方法A调用异步方法B,B的异步失效(原因同AOP,调用的是目标对象自身方法)。
- 异步方法的返回值建议用Future,用于获取异步方法的执行结果;若无需返回值,返回void即可。