Spring 全家桶面试题精选
Java 后端真实面试专题 · Spring 全家桶篇
Spring / SpringBoot / SpringMVC / MyBatis 是后端地基,必考。每题三段: ① 标准答(讲透:是什么+为什么+怎么做+原理)→ ② 拓展(成体系带出关联点和必追问的)→ ③ 怎么接到你自己的项目。
年限标签:
🟢 3年内🔴 3年+
1. 🟢 说说你对 IoC 和 AOP 的理解?
标准答:
- IoC(控制反转):把对象的创建和依赖关系的管理交给 Spring 容器,对象不再自己
new依赖,而是由容器注入(DI 依赖注入)。好处是解耦——面向接口编程,改实现不动调用方。 - AOP(面向切面):把日志、事务、权限、限流这类横切逻辑用动态代理统一织入,不侵入业务代码。核心概念:切面、切点(在哪切)、通知(切入后做什么)。
拓展:
- "IoC 解决了什么痛点?"——对象间硬编码依赖,改一处动全身、难测试;交给容器后能灵活替换、方便单测(注入 mock)。
- "依赖注入有几种方式?"——构造器注入(推荐,能保证不可变和非空)、setter 注入、字段注入(
@Autowired,简洁但不利于测试)。 - "AOP 的底层?"——动态代理(JDK/CGLIB),见后面专门的题。
往项目引 ⭐:"我项目所有 Service 靠 @Autowired 注入依赖、面向接口写,多渠道支付能随时换实现;又用 AOP + 自定义注解统一打了接口耗时日志和操作审计,业务代码里一行日志都不用写——这就是 IoC 解耦 + AOP 抽横切的实际价值。"
2. 🟢 Spring Bean 的生命周期?
标准答:
- 实例化:反射调构造器创建对象。
- 属性注入:填充依赖(
@Autowired等)。 - Aware 回调:如 BeanNameAware、ApplicationContextAware,让 Bean 拿到容器信息。
- BeanPostProcessor 前置处理(
postProcessBeforeInitialization)。 - 初始化:
@PostConstruct→ InitializingBean 的afterPropertiesSet→ 自定义 init-method。 - BeanPostProcessor 后置处理(
postProcessAfterInitialization,AOP 代理在这步生成)。 - 使用。
- 销毁:
@PreDestroy→ DisposableBean 的destroy。
拓展:
- "AOP 代理在哪步?"——后置处理器里,所以拿到的 Bean 其实是代理对象,这也解释了为什么自调用 AOP 会失效。
- "BeanPostProcessor 和 BeanFactoryPostProcessor 区别?"——前者处理 Bean 实例、后者处理 Bean 定义(元数据)。
- 循环依赖的解决(三级缓存)就发生在实例化和属性注入之间。
往项目引 ⭐:"我项目用 @PostConstruct 在 Bean 初始化后预加载字典、配置到本地缓存,省去每次查库。理解生命周期让我知道初始化逻辑该放哪个钩子。"
3. 🟢 Bean 的作用域有哪些?单例 Bean 线程安全吗?
标准答:作用域——singleton(默认,容器内唯一)、prototype(每次获取新建)、request、session、application(Web 相关)。 单例 Bean 不一定线程安全:如果有可变的成员变量,多线程并发访问会有问题;如果是无状态的(不存可变状态),就是安全的。
拓展:
- "Spring 怎么保证单例线程安全?"——Spring 不负责保证,是否安全取决于你有没有可变状态。
- "需要线程内状态怎么办?"——用 ThreadLocal,或把单例改成方法局部变量(栈封闭)。
- "单例 Bean 里注入 prototype Bean 会怎样?"——注入的还是同一个,要每次拿新的得用
@Lookup或 ObjectProvider。
往项目引 ⭐:"我项目 Service 都设计成无状态的(不放可变成员变量),需要请求级状态就用 ThreadLocal 存(如当前用户),所以单例 Bean 没有并发安全问题。"
4. 🔴 Spring 怎么解决循环依赖?三级缓存分别是什么?
标准答:用三级缓存解决单例 + 属性注入的循环依赖:
- 一级缓存(singletonObjects):完整的成品 Bean。
- 二级缓存(earlySingletonObjects):实例化了但还没注入属性的半成品。
- 三级缓存(singletonFactories):对象工厂,用于在需要时提前生成代理对象。 A 依赖 B、B 依赖 A 时:A 实例化后把自己的工厂放三级缓存 → A 注入 B 时去创建 B → B 注入 A 时从三级缓存拿到 A 的早期引用(提前暴露)→ B 创建完,A 继续完成,打破循环。
拓展:
- "为什么需要三级而不是二级?"——三级缓存的工厂是为了在有 AOP 时提前暴露的是代理对象而不是原始对象,保证注入的是同一个代理。
- "哪种循环依赖解决不了?"——构造器注入的循环依赖(对象还没实例化就需要对方,无法提前暴露);prototype 的也不行。
- 解决不了时报
BeanCurrentlyInCreationException。
往项目引 ⭐:"我项目遇到过两个 Service 互相依赖、启动报循环依赖错——其实是用了构造器注入。改成字段注入或加 @Lazy 就好了。理解三级缓存让我一眼定位是注入方式的问题。"
5. 🟢 @Autowired 和 @Resource 的区别?
标准答:
@Autowired:Spring 提供,**默认按类型(byType)**注入,配合@Qualifier指定名字。@Resource:JDK(JSR-250)提供,**默认按名字(byName)**注入,找不到再按类型。
拓展:
- "按类型注入遇到多个实现怎么办?"——会报
NoUniqueBeanDefinitionException,用@Qualifier("名字")或在某个实现上加@Primary。 - "字段注入的缺点?"——不利于单元测试(不能在 new 时传 mock)、可能掩盖循环依赖,官方更推荐构造器注入。
往项目引 ⭐:"我项目有微信、支付宝多个支付实现,注入时用 @Qualifier 指定,或者干脆用一个 Map<String, PayService> 让 Spring 把所有实现按名字注进来,再按渠道 key 取对应的——这是策略模式 + Spring 注入的经典配合。"
6. 🟢 AOP 的实现原理?JDK 动态代理和 CGLIB 的区别?
标准答:AOP 靠动态代理在目标方法前后织入逻辑:
- JDK 动态代理:要求目标类实现了接口,基于接口生成代理类(
Proxy+InvocationHandler),反射调用。 - CGLIB:目标类没接口也行,通过生成子类字节码来代理,重写父类方法。
SpringBoot 默认全用 CGLIB(
spring.aop.proxy-target-class=true)。
拓展:
- "CGLIB 的限制?"——不能代理 final 类和 final 方法(因为靠继承重写)。
- "为什么自调用 AOP 失效?"——
this.方法()调的是原始对象不是代理对象,绕过了切面。解决:注入自己、或用AopContext.currentProxy()。 - 切面执行顺序、多个切面用
@Order控制。
往项目引 ⭐:"我项目用 AOP + 自定义注解 @Idempotent、@RateLimit 做接口幂等和限流——原理就是动态代理在方法执行前先做校验。理解了代理机制,也知道为什么这些注解不能用在同类自调用的方法上。"
7. 🟢 Spring 事务的传播行为有哪些?
标准答:定义"方法 A 调方法 B 时,B 的事务怎么处理"。常用:
- REQUIRED(默认):有事务就加入、没有就新建。
- REQUIRES_NEW:挂起当前事务、自己开一个新的(互不影响)。
- NESTED:嵌套事务,基于 savepoint,外层回滚带着它回滚,但它能单独回滚到 savepoint。
- SUPPORTS / NOT_SUPPORTED / MANDATORY / NEVER:按需。
拓展:
- 典型场景:主流程失败要回滚,但"记操作日志"要独立提交——用 REQUIRES_NEW。
- "NESTED 和 REQUIRES_NEW 区别?"——NESTED 依赖外层(外层回滚它也回),REQUIRES_NEW 完全独立。
- 传播行为是事务失效的一个隐藏点(用错导致没回滚或没生效)。
往项目引 ⭐:"我项目下单失败要整体回滚,但失败原因日志必须留下来,所以记日志的方法用 REQUIRES_NEW 开独立事务,主事务回滚不影响日志落库——这是传播行为最实用的场景。"
8. 🟢 Spring 事务在哪些情况下会失效?(高频实战)
标准答:
- 方法不是 public(动态代理拦不到)。
- 自调用:
this.方法()绕过代理。 - 异常被 catch 没抛出,或抛了但没触发回滚。
- 抛的是受检异常但没配 rollbackFor(默认只回滚 RuntimeException 和 Error)。
- 类没被 Spring 管理(没加
@Service等)。 - 数据库引擎不支持事务(MyISAM)。
- 多线程里——事务是绑在当前线程的,新线程里的操作不在事务内。
拓展:
- "怎么让受检异常也回滚?"——
@Transactional(rollbackFor = Exception.class)。 - "自调用怎么解决?"——注入自己(
@Autowired private XxxService self;)调self.方法(),或拆到别的 Bean。 - 这题几乎必问,能列全 + 说出解决办法很加分。
往项目引 ⭐:"我项目踩过自调用失效的坑——一个方法里调本类另一个带 @Transactional 的方法,事务没生效。后来注入自身代理调用解决。从那以后我写事务方法会特别注意调用方式。"
9. 🟢 SpringMVC 的完整执行流程?
标准答:
- 请求到 DispatcherServlet(前端控制器,统一入口)。
- 它查 HandlerMapping 找到能处理的 Controller 方法。
- 通过 HandlerAdapter 调用 Controller。
- Controller 返回 ModelAndView(或
@ResponseBody返回对象)。 - ViewResolver 解析视图并渲染(前后端分离下用 HttpMessageConverter 把对象转 JSON)。
- 返回响应。
拓展:
- "前后端分离怎么返回 JSON?"——
@ResponseBody/@RestController+ HttpMessageConverter(Jackson)。 - "参数怎么绑定的?"——HandlerMethodArgumentResolver 解析
@RequestParam、@RequestBody、@PathVariable。 - DispatcherServlet 是核心调度者,体现了"前端控制器"模式。
往项目引 ⭐:"我项目是前后端分离,Controller 全用 @RestController 返回统一的 Result<T> 包装,再配全局异常处理器兜底返回错误码——前端只需按统一结构处理成功和失败。"
10. 🔴 SpringBoot 自动装配的原理?
标准答:核心是 @SpringBootApplication 里的 @EnableAutoConfiguration:
- 它通过
@Import引入AutoConfigurationImportSelector。 - selector 去扫描所有 jar 包的
META-INF/spring.factories(SpringBoot 2.7+ 是META-INF/spring/...AutoConfiguration.imports)里登记的自动配置类。 - 每个自动配置类上有
@ConditionalOnXxx条件注解,满足条件才生效(如@ConditionalOnClass有这个类才配、@ConditionalOnMissingBean用户没自定义才用默认)。
拓展:
- "约定优于配置"靠的就是
@ConditionalOnMissingBean——你不配就用默认、你配了就用你的。 - "怎么自定义 starter?"——写自动配置类 + 条件注解 +
@ConfigurationProperties绑定配置 + 在 imports 文件登记。 - 想看哪些自动配置生效了,启动加
--debug看 Conditions 报告。
往项目引 ⭐:"我项目封装过公司内部 starter——比如统一的日志、链路追踪、异常处理打成一个 starter,各业务线引依赖就自动生效、零配置。这就是自动装配 + 条件注解的实战,让公共能力复用。"
11. 🟢 @SpringBootApplication 注解做了什么?
标准答:它是组合注解,包含三个:
@SpringBootConfiguration:标识这是配置类(本质是@Configuration)。@EnableAutoConfiguration:开启自动装配。@ComponentScan:扫描启动类所在包及其子包的组件(@Component/@Service等)。
拓展:
- "为什么启动类要放最外层包?"——
@ComponentScan默认扫描启动类所在包及子包,放外层才能扫到所有业务包。 - 可以用
@ComponentScan(basePackages=...)自定义扫描范围。
往项目引 ⭐:"我项目早期出过 Bean 注入失败——就是启动类放错了包、有些子包没被扫到。知道 @ComponentScan 的默认范围后,我们规范了包结构,启动类一定放在根包。"
12. 🟢 拦截器(Interceptor)和过滤器(Filter)的区别?
标准答:
- Filter:Servlet 规范的,在 DispatcherServlet 之前,能拿到原始 request/response,但拿不到 Spring 容器里的 Handler 信息。
- Interceptor:SpringMVC 的,在 Controller 前后,能拿到 HandlerMethod(方法上的注解)、能注入 Spring Bean。 执行顺序:Filter → Interceptor → Controller → Interceptor → Filter。
拓展:
- "什么时候用哪个?"——需要操作 Spring Bean、拿方法注解(如权限注解)用拦截器;处理编码、跨域、底层请求包装用过滤器。
- 拦截器有 preHandle/postHandle/afterCompletion 三个时机。
- 还有 AOP 也能做类似切面,但粒度更细(方法级)。
往项目引 ⭐:"我项目用过滤器做请求日志和跨域(底层、所有请求都过),用拦截器做登录校验和权限(能拿到方法上的 @RequiresPermission 注解判断)——按'要不要 Spring 上下文和方法信息'来选。"
13. 🟢 SpringBoot 怎么做统一异常处理和统一返回?
标准答:
- 统一异常:用
@RestControllerAdvice+@ExceptionHandler,集中捕获业务异常、参数校验异常、系统异常,返回统一格式。 - 统一返回:定义
Result<T>(code/message/data),所有接口返回它,或用 ResponseBodyAdvice 自动包装。
拓展:
- 配合自定义业务异常(BusinessException)+ 错误码枚举,业务里直接
throw,由全局处理器转成友好响应。 - 参数校验异常(
MethodArgumentNotValidException)也在这里统一处理。 - 系统异常要兜底(记日志 + 报警 + 返回通用错误),不能把堆栈暴露给前端。
往项目引 ⭐:"我项目所有接口异常都走全局处理器:业务异常返回友好提示、参数错误返回字段信息、系统异常记日志报警并返回通用错误码。前端拿到的永远是统一结构,开发体验好、也不泄露内部信息。"
14. 🔴 BeanFactory 和 ApplicationContext 的区别?
标准答:
- BeanFactory:最底层的容器接口,懒加载(getBean 时才创建),功能基础。
- ApplicationContext:BeanFactory 的子接口,启动时就预实例化所有单例 Bean,并扩展了国际化、事件发布、资源加载、AOP 集成等企业级功能。 实际开发用 ApplicationContext。
拓展:
- "懒加载 vs 预加载的取舍?"——预加载启动慢但能提前暴露配置错误(启动就报错),生产更安全。
- ApplicationContext 常见实现:ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、SpringBoot 的 Web 容器。
往项目引 ⭐:"我项目用的就是 ApplicationContext,启动时单例都初始化好,万一有 Bean 配错、依赖缺失,启动阶段就报错,而不是等到运行时某个请求才暴露——这对线上稳定很重要。"
15. 🟢 MyBatis 里 #{} 和 ${} 的区别?
标准答:
#{}:预编译占位符,MyBatis 会把它替换成?用 PreparedStatement 设参数,能防 SQL 注入。${}:字符串直接拼接到 SQL 里,有注入风险,且不能享受预编译缓存。 能用#{}就别用${}。
拓展:
- "
${}什么时候不得不用?"——动态表名、动态列名、order by字段这种没法用占位符的地方,但要自己做白名单校验防注入。 - "为什么
#{}能防注入?"——参数是作为值传给数据库的,不会被当 SQL 语法解析。
往项目引 ⭐:"我项目里查询条件全用 #{};只有一个动态排序字段是前端传的、必须用 ${} 拼,我先做了字段白名单校验(只允许指定几个列名),从根上防 SQL 注入。"
16. 🟢 MyBatis 的一级缓存和二级缓存?
标准答:
- 一级缓存:SqlSession 级别,默认开启。同一个会话里查相同 SQL 走缓存。增删改、提交、关闭会话后失效。
- 二级缓存:Mapper(namespace)级别,跨 SqlSession,需手动配置开启。
拓展:
- "二级缓存为什么实际项目少用?"——分布式多实例下,各实例的二级缓存不共享、容易脏,所以一般用 Redis 统一做缓存。
- 一级缓存在不同会话间不共享,所以并发下也可能读到旧数据,要注意。
往项目引 ⭐:"我项目没用 MyBatis 二级缓存,统一用 Redis 做缓存——因为服务是多实例部署,二级缓存在节点间不一致,Redis 才能保证所有实例看到同一份。"
17. 🟢 怎么解决跨域问题?
标准答:跨域是浏览器同源策略(协议+域名+端口任一不同)的限制,本质是后端返回 CORS 响应头放行。方式:
@CrossOrigin注解(方法/类级)。- 全局配置
WebMvcConfigurer.addCorsMappings。 - 在网关/Nginx 层统一加 CORS 头(推荐,省得每个服务配)。
拓展:
- 关键响应头:
Access-Control-Allow-Origin、-Methods、-Headers、-Credentials。 - 复杂请求会先发 OPTIONS 预检请求。
- 带 cookie 的跨域,Allow-Origin 不能是
*,要指定具体域名。
往项目引 ⭐:"我项目前后端分离,统一在网关层配 CORS,比每个微服务各配一遍省事、也好维护——改一处所有服务生效。"
18. 🔴 BeanPostProcessor 有什么用?
标准答:是 Bean 初始化前后的扩展点,能对每个 Bean做增强处理(postProcessBeforeInitialization / AfterInitialization)。Spring 的很多功能靠它实现——AOP 代理生成、@Autowired/@Value 注入(AutowiredAnnotationBeanPostProcessor)。
拓展:
- "和 BeanFactoryPostProcessor 区别?"——后者在 Bean 实例化之前改 Bean 定义(元数据,如 PropertyPlaceholderConfigurer 替换占位符),前者在 Bean 实例化之后改实例。
- 自定义 BeanPostProcessor 可以给特定 Bean 统一做初始化。
往项目引 ⭐:"我项目自定义过 BeanPostProcessor——给带某个自定义注解的 Bean 统一注册到本地事件总线,不用每个 Bean 自己写注册逻辑。理解扩展点让我能优雅地做这种横切初始化。"
19. 🟢 Spring 里用到了哪些设计模式?
标准答:
- 工厂:BeanFactory/ApplicationContext 生产 Bean。
- 单例:Bean 默认单例。
- 代理:AOP 动态代理。
- 模板方法:JdbcTemplate、RestTemplate。
- 观察者:事件机制(ApplicationEvent/Listener)。
- 适配器:HandlerAdapter 适配不同类型的 Controller。
- 装饰器:BeanWrapper。
拓展:
- 能结合具体类说,比只报名词强。
- 这题常引到"你项目用了什么设计模式"。
往项目引 ⭐:"我项目自己也用策略 + 工厂做多渠道支付——思路就是跟 Spring 学的:面向接口 + 容器管理实现 + 按 key 路由。新增渠道只加实现类、不改原有代码,符合开闭原则。"
20. 🔴 Spring 有哪些常用扩展点?(框架集成必问)
标准答:
- BeanFactoryPostProcessor:改 Bean 定义。
- BeanPostProcessor:改 Bean 实例。
- InitializingBean / @PostConstruct:初始化回调。
- ApplicationListener / @EventListener:监听容器事件。
- ApplicationContextAware / 各种 Aware:拿容器和环境。
- ImportSelector / @Import:批量导入配置(自动装配就用它)。
- FactoryBean:自定义复杂 Bean 的创建逻辑(MyBatis 的 Mapper 就是 FactoryBean 造的)。
拓展:
- 中间件集成 Spring(MyBatis、Dubbo、各种 starter)基本都靠这些扩展点。
SmartInitializingSingleton在所有单例初始化完后回调,适合做"全部就绪后"的动作。
往项目引 ⭐:"我项目用 ApplicationListener 监听容器启动完成事件,做服务预热和向注册中心上报的初始化动作;MyBatis 的 Mapper 接口能注入也是靠 FactoryBean——理解扩展点让我看得懂这些'魔法'。"
你能答到第几层?
- 三段都能答、还能往项目引:Spring 这块你稳了。
- 标准答 + 拓展能成体系答:知识够,差接到项目说出来。
- 标准答都磕巴:Spring 大但有主线(IoC/AOP → Bean 生命周期 → 事务 → SpringBoot 自动装配 → 扩展点),跟着学一遍就通。
这是面试专题的「Spring 全家桶篇」,网站上还有并发、MySQL、Redis、微服务、项目场景等系统整理。 🌐 更多真实面试专题与资料:smallredtech.com 💬 想系统学 / 简历与辅导咨询,加微信:Ahongbb666(备注「面试题」)