SpringBoot相关面试题
GQ SpringBoot
说说 Springboot 的启动流程
一句话总览
main → SpringApplication.run → 准备环境 → 创建并准备 ApplicationContext → 加载并注册 Bean 定义(含自动装配)→ 刷新容器(创建单例、启动内置服务器)→ 调用 Runner → 发布就绪事件。
途中会伴随一串生命周期事件,失败会发失败事件。
分步骤说
-
入口 & 构建 SpringApplication
- 从
main()调SpringApplication.run()开始。 - 构造
SpringApplication时会推断应用类型(Servlet/Reactive/None),加载ApplicationListener、ApplicationContextInitializer等(Boot 2.x 从spring.factories,Boot 3.x 从META-INF/spring/*.imports)。
- 从
-
准备 Environment(environmentPrepared)
- 创建并配置
ConfigurableEnvironment,读取配置(application.yml/properties、命令行、环境变量等),确定激活的 profile。 - 这一阶段会发布
ApplicationEnvironmentPreparedEvent,并打印 Banner。
- 创建并配置
-
创建 & 准备 ApplicationContext(contextPrepared)
- 根据应用类型创建相应的
ApplicationContext(如ServletWebServerApplicationContext)。 - 设置基础属性(资源加载器、BeanName 生成器等),然后调用所有
ApplicationContextInitializer对上下文做预初始化。
- 根据应用类型创建相应的
-
加载 Bean 定义 & 自动装配(contextLoaded)
BeanDefinitionLoader把启动类及其扫描包里的组件注册进容器。- 进入工厂后置处理:触发
BeanFactoryPostProcessor(特别是ConfigurationClassPostProcessor),解析@Configuration/@ComponentScan/@Import。 - 关键:
@EnableAutoConfiguration(来自@SpringBootApplication复合注解)会通过AutoConfigurationImportSelector
从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(或旧版的spring.factories)读取自动配置类并注册 → 这就是“开箱即用”的来源(图右下红框部分)。
-
刷新容器 refresh(started)
-
标准的
context.refresh():- 注册
BeanPostProcessor; - 初始化
MessageSource、ApplicationEventMulticaster; - onRefresh:如果是 Web 应用,创建并启动内嵌服务器(Tomcat/Jetty/Undertow);
- 注册监听器;
- finishBeanFactoryInitialization:实例化所有单例 Bean,触发依赖注入、
@PostConstruct、InitializingBean等生命周期回调; - finishRefresh:发布
ContextRefreshedEvent。
- 注册
-
到这里容器“可用了”,会发布
ApplicationStartedEvent。
-
-
调用 Runner(ready)
- 如果你定义了
ApplicationRunner/CommandLineRunner,此时会被调用,做业务启动收尾初始化。 - 一切正常后发布
ApplicationReadyEvent,表示服务已就绪;若中途异常,则发布ApplicationFailedEvent。
- 如果你定义了
-
运行阶段(running)
- Web 容器已启动、端口已监听,Bean 都是单例常驻,应用进入稳定运行期。之后所有的请求、定时任务、消息消费等都在这阶段工作。
事件与分层的直观对照
- 引导:
starting → environmentPrepared(准备环境) - 启动:
contextPrepared → contextLoaded → started → ready(容器准备、加载、刷新、就绪) - 运行:
running(对外提供服务)
每一阶段都有相应ApplicationEvent广播,方便你在合适的时机做扩展。

精炼版
Spring Boot 启动从 SpringApplication.run 开始:先准备 Environment 读取配置和 profile,然后创建合适的 ApplicationContext 并应用 Initializer,对上下文做预初始化,接着加载 Bean 定义并通过 @EnableAutoConfiguration 把自动配置从 *.imports/spring.factories 导入;之后 refresh():注册后置处理器、创建单例 Bean、Web 应用会启动内嵌 Tomcat;容器刷完再执行 Runner,最后发布 ApplicationReadyEvent,应用就绪对外提供服务。中途有一系列生命周期事件,方便做扩展,失败会发布失败事件。
追问“自动配置怎么生效/怎么排错”:
记住@EnableAutoConfiguration+AutoConfiguration.imports,配合debug=true或--debug能看到 Auto-Configuration Report,按条件装配(@Conditional*)判断哪些生效、哪些没生效,定位问题很快。
什么是 Spring Boot?
Spring Boot 是一个简化 Spring 应用开发的框架,它的目标是减少配置、降低开发复杂度,让我们能更快地构建、测试和部署 Spring 应用。
它通过几项核心特性来做到这一点:
1 自动化配置 : 根据依赖自动帮你配置 Spring 环境,省去繁琐的 XML。
2 内嵌服务器 : 自带 Tomcat 或 Jetty,直接运行,无需外部容器。
3 快速开发 : 提供开箱即用的项目结构和依赖管理。
4 独立运行 :应用可以打包成一个可执行的 Jar,一行命令就能启动。
Spring Boot 就是帮我们“更快启动、更少配置、即开即用”的 Spring 框架。
Spring Boot 的核心特性有哪些?
1 自动化配置 : 根据依赖自动帮你配置 Spring 环境,省去繁琐的 XML。
2 内嵌服务器 : 自带 Tomcat 或 Jetty,直接运行,无需外部容器。
3 快速开发 : 提供开箱即用的项目结构和依赖管理。
4 独立运行 :应用可以打包成一个可执行的 Jar,一行命令就能启动。
Spring Boot 中 application.properties 和 application.yml 的区别是什么?
书写格式不一样。 优先级:application.properties > application.yml
Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
SpringBoot 打成的Jar 包,不仅包含应用程序的源代码和依赖库,还包含程序运行的配置、脚本以及服务依赖,只需要有个jdk就可以运行这个jar包了。
而普通jar需要启动tomcat才能运行这个jar包
Spring Boot 是否可以使用 XML 配置 ?
支持,可以用@ImportResource注解导入XML文件
SpringBoot 默认同时可以处理的最大连接数是多少?
最大连接数=默认最大连接数+默认等待数 = 8192+100 最大请求数 线程池配置默认核心线程是10 最大是200
如何理解 Spring Boot 中的 starter?
什么是 Spring Boot 的 Starter?
- 一句话:Starter 就是一组“开箱即用的依赖集合”。它把某个功能模块所需的常用依赖、默认配置、自动配置类打包好,你只需在
pom.xml里引一个 starter 依赖,Spring Boot 在启动时就会按需装配对应的 Bean,避免你手写大量配置。 - 好处:少依赖、少配置、少样板代码;按条件装配、默认即用、可覆盖可禁用。
自动生效的大致流程(看到 Starter 时,Spring Boot 做了什么)
-
你在
pom.xml引入了某个spring-boot-starter-xxx。 -
这个 starter 里会依赖一个 自动配置模块(通常形如
xxx-autoconfigure)。 -
自动配置模块里通过
@EnableAutoConfiguration机制,配合:- Spring Boot 2.x:
META-INF/spring.factories列出自动配置类; - Spring Boot 3.x:改为
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports;
由框架在启动时加载这些*AutoConfiguration类。
- Spring Boot 2.x:
-
自动配置类里使用大量 条件注解(
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty……)按需注册 Bean。 -
配置属性通过
@ConfigurationProperties与你的application.properties/yaml自动绑定,默认值可用,想调再覆写。
看图可以把它想象成:导入 starter → 发现 & 激活自动配置类 → 读取配置属性 → 条件成立就注册 Bean → 场景就绪。
该怎么使用(开发者视角)
- 需要 Web?
spring-boot-starter-web - 需要 JPA?
spring-boot-starter-data-jpa - 需要缓存?
spring-boot-starter-cache - 需要监控?
spring-boot-starter-actuator
默认即可跑,想自定义再在
application.yml/properties覆盖配置或提供自定义 Bean。
如何禁用/定制
- 局部禁用:
application.yml中设置spring.autoconfigure.exclude=xxx.AutoConfiguration - 全局禁用:
@SpringBootApplication(exclude = XxxAutoConfiguration.class) - 覆盖默认:自己提供同名/同类型 Bean(配合
@ConditionalOnMissingBean,你的 Bean 会生效,默认的不会再装配)
自定义一个 Starter(简化流程版)
-
创建
xxx-spring-boot-starter(只写依赖坐标,不含自动配置逻辑)。 -
创建
xxx-spring-boot-autoconfigure:- 写业务组件与
@ConfigurationProperties; - 写
XxxAutoConfiguration(使用条件注解注册 Bean); - Spring Boot 2.x:在
META-INF/spring.factories中声明自动配置类;
Spring Boot 3.x:在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中声明。
- 写业务组件与
-
starter 依赖自动配置模块;业务项目只需引入 starter 即可生效。
一句话比喻
Starter 就像“功能套餐”:你点了 Web 套餐,服务员(Spring Boot)就把 MVC、默认 JSON、内嵌容器、基本配置都端上来;默认能吃,想加料/换口味自己配就行。
Spring Boot 如何处理跨域请求(CORS)?
问:Spring Boot 怎么处理跨域(CORS)?
核心点:跨域要让浏览器的预检请求(OPTIONS) 和后续请求都能“过关”,并且在响应头里带上允许跨域的配置。Spring Boot 有三种常用做法:
1) 局部开启:在 Controller/方法上加 @CrossOrigin
-
适合某几个接口临时放开跨域。
-
用法简单:
1
2
3
public class DemoController { ... } -
可以精确控制
origins / methods / headers / maxAge / allowCredentials等。
2) 全局开启:实现 WebMvcConfigurer#addCorsMappings
-
适合整个应用统一管理跨域策略。
-
推荐做法(轻量、无侵入):
1
2
3
4
5
6
7
8
9
10
11
12
public class WebMvcCorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET","POST","PUT","DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
} -
注意:Spring Security 开启时要同步
http.cors()(否则被安全链挡住)。
3) 过滤器方式:注册 CorsFilter Bean
-
走 Filter 链,在 进入 Spring MVC 之前 统一处理跨域。
-
适合你想在 MVC 之外的过滤器 场景里也放行跨域(如静态资源、某些网关场景)。
1
2
3
4
5
6
7
8
9
10
11
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://example.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
addCorsMappings vs CorsFilter:什么时候用哪个?
addCorsMappings(WebMvcConfigurer):在 Spring MVC 层处理,进入DispatcherServlet之后才生效。CorsFilter:在 Filter 链中最先执行,进入 MVC 之前就把跨域处理好了。- 实践选择:能用 全局配置(addCorsMappings) 就用它;如果你有自定义 Filter/网关等 MVC 之外的场景需要跨域,就用 CorsFilter。
常见坑 & 排查思路
-
预检请求被拦截
- 现象:OPTIONS 直接 401/403/404。
- 处理:确保拦截器/安全过滤器对 OPTIONS 放行;Spring Security 里加
http.cors()。
-
allowCredentials(true)与*冲突- 有
allowCredentials(true)时 不能 用allowedOrigins("*"),要写具体域名。
- 有
-
顺序问题
- 如果你加了自定义
Filter/Interceptor做鉴权,务必让 跨域先处理,否则浏览器拿不到允许跨域的响应头。
- 如果你加了自定义
-
Spring Security
-
开启全局 CORS 后,还要在 Security 里:
1
http.cors().and().csrf().disable();
-
否则安全链会先把请求挡住。
-
一句话总结
Spring Boot 处理跨域的最佳实践是:全局用
addCorsMappings统一配置,安全框架里启用http.cors();若需要在 MVC 之外生效,再上CorsFilter。记得放行 OPTIONS,并处理好 allowCredentials 与 * 的冲突。
SpringBoot 中如何实现定时任务 ?
单体:启动类加@EnableSchedual注解,定义定时任务类在任务上使用@Schedualed注解。
分布式:xxl-job
Spring Boot 3.x 与 2.x 版本有哪些主要的改进和区别?
1、从javaee迁移到Jakarta
2、JDK最低支持17
3、微服务下 启动更快,毫秒级别
SpringBoot(Spring)中为什么不推荐使用 @Autowired ?
为什么 Spring / Spring Boot 不推荐使用 @Autowired?
其实 Spring 官方并不是“禁止” @Autowired,只是不推荐 字段注入(Field Injection) 的用法,推荐使用 构造器注入(Constructor Injection)。
原因主要有以下几点,一条条展开说:
1 不利于单元测试(强烈原因)
字段注入的时候:
1 |
|
要写单元测试时,你无法直接构造对象,因为没有无参构造器 + 没有办法给字段赋值,只能靠 Spring 容器启动,非常麻烦。
而构造器注入:
1 | public UserController(UserService userService) { |
单元测试时可以直接 new
也可以 mock,完全不需要启动 Spring 容器。
这是 Spring 官方最推荐构造器注入的核心原因。
2 字段注入会隐藏依赖关系,不利于维护
字段注入时:
1 |
|
看起来简单,但你根本看不出一个类到底依赖了哪些组件。
构造器注入就很清晰:
1 | public MyService(AService a, BService b, CService c) { ... } |
依赖一眼可见
构造方法参数过多的时候,你还能反思是否违反“单一职责”
3 字段注入容易造成循环依赖难排查
字段注入的生命周期顺序不受你控制,Spring 会在对象创建后再进行注入,所以:
- 容易产生循环依赖
- 报错信息也不直观
- 排查起来非常痛苦
构造器注入则更明确,Spring 会在构建对象前检查依赖关系,一旦循环依赖直接报错。
4 不利于不可变对象设计(违背 OOP 最佳实践)
字段注入要求:
- 字段必须是非 final
- 必须允许被修改(反射注入)
这违背了“依赖尽量不可变、对象设计尽量稳定”的理念。
构造器注入可以让字段使用 final:
1 | private final UserService userService; |
线程安全
更符合面向对象设计
5 @Autowired 不是标准,@Resource 更标准
@Autowired是 Spring 自己的@Resource是 JSR-250 标准(Java 标准规范)
如果以后换成其他 IoC 框架:
@Resource大部分都能用@Autowired不一定兼容
不过这里 大部分面试官不太关注这一点,但可以当加分项说明。
总结
我们不推荐字段注入的 @Autowired,而是推荐构造器注入。主要原因有四个:
1)可测试性差:字段注入必须依赖 Spring 容器,单元测试不好写;构造器注入能直接 new 或 mock。
2)依赖不透明:字段注入隐藏依赖,构造器注入一眼能看出类依赖了什么。
3)风险更高:字段注入更容易引发循环依赖且不易排查,构造器注入在创建阶段就能发现问题。
4)不利于不可变设计:字段注入不能用 final,构造器注入可以保证依赖不可变、线程更安全。
如果一定要用字段注入,也建议用标准的 @Resource,但整体最佳实践是构造器注入。
说说 MyBatis 的缓存机制?
MyBatis 里面有两级缓存:一级缓存和二级缓存。一级缓存默认开启,二级缓存需要手动配置。
一级缓存(SqlSession 级别)
可以理解成“当前会话里的一个临时缓存区”。
-
作用范围:同一个 SqlSession 内有效。只要你在同一个 SqlSession 中重复查相同数据,MyBatis 不会再查数据库,而是直接从缓存返回
→ 文档中称一级缓存为会话级缓存 -
生命周期:跟 SqlSession 一致。
-
什么时候失效?
- 执行增删改操作(insert/update/delete)
- 手动清缓存(clearCache)
- SqlSession 关闭 / commit / rollback
→ 文档也明确提到这些会导致一级缓存清空
一句话总结
一级缓存是 SqlSession 级别的、默认开启的“最近查询结果”缓存,只在当前会话里有效。
二级缓存(Mapper 映射级别)
可以理解成“mapper 级别的共享缓存”,跨多个 SqlSession 都能用。
-
作用范围:同一个 Mapper(命名空间)内的所有 SqlSession 共享
→ 文档称其为命名空间级缓存,作用范围比一级缓存大 -
需要手动开启,在 mapper.xml 中加:
1
<cache/>
-
生命周期:和 SqlSessionFactory 一致。
-
何时失效?
- 对应 Mapper 执行任何增删改操作
→ 文档明确指出更新、插入、删除会让缓存失效
- 对应 Mapper 执行任何增删改操作
一句话总结
二级缓存是 Mapper 级的跨会话缓存,需要手动开启,可以让不同 SqlSession 共享查询结果。
总结:
MyBatis 有一级、二级两个缓存。
一级缓存默认开启,是 SqlSession 级别的,只在当前会话里生效,重复查询不会再查数据库。执行增删改、提交事务或清缓存时会失效。
二级缓存需要显式开启,是基于 Mapper 的缓存,可以被多个 SqlSession 共享。新增、修改、删除数据会让对应 Mapper 的二级缓存失效。
两级缓存一起配合,提升了查询性能,但缓存一致性依然由 MyBatis 自动维护。
MyBatis
说说MyBatis的缓存机制
说说 MyBatis 的缓存机制?
MyBatis 里面有两级缓存:一级缓存和二级缓存。一级缓存默认开启,二级缓存需要手动配置。
一级缓存(SqlSession 级别)
可以理解成“当前会话里的一个临时缓存区”。
-
作用范围:同一个 SqlSession 内有效。只要你在同一个 SqlSession 中重复查相同数据,MyBatis 不会再查数据库,而是直接从缓存返回
→ 文档中称一级缓存为会话级缓存 -
生命周期:跟 SqlSession 一致。
-
什么时候失效?
- 执行增删改操作(insert/update/delete)
- 手动清缓存(clearCache)
- SqlSession 关闭 / commit / rollback
→ 文档也明确提到这些会导致一级缓存清空
一句话总结
一级缓存是 SqlSession 级别的、默认开启的“最近查询结果”缓存,只在当前会话里有效。
二级缓存(Mapper 映射级别)
可以理解成“mapper 级别的共享缓存”,跨多个 SqlSession 都能用。
-
作用范围:同一个 Mapper(命名空间)内的所有 SqlSession 共享
→ 文档称其为命名空间级缓存,作用范围比一级缓存大 -
需要手动开启,在 mapper.xml 中加:
1
<cache/>
-
生命周期:和 SqlSessionFactory 一致。
-
何时失效?
- 对应 Mapper 执行任何增删改操作
→ 文档明确指出更新、插入、删除会让缓存失效
- 对应 Mapper 执行任何增删改操作
一句话总结
二级缓存是 Mapper 级的跨会话缓存,需要手动开启,可以让不同 SqlSession 共享查询结果。
总结:
MyBatis 有一级、二级两个缓存。
一级缓存默认开启,是 SqlSession 级别的,只在当前会话里生效,重复查询不会再查数据库。执行增删改、提交事务或清缓存时会失效。
二级缓存需要显式开启,是基于 Mapper 的缓存,可以被多个 SqlSession 共享。新增、修改、删除数据会让对应 Mapper 的二级缓存失效。
两级缓存一起配合,提升了查询性能,但缓存一致性依然由 MyBatis 自动维护。
MyBatis 中 #{} 和 ${} 的区别是什么?
在 MyBatis 里,#{} 和 ${} 都是用来传参的,但它们的处理方式完全不一样。
{}:预编译,占位符(安全、推荐使用)
#{} 更像 JDBC 里的 PreparedStatement,占位符会先变成 “?”,然后 MyBatis 再安全地把参数绑定进去。
- #{} 会在预处理阶段使用 ? 代替参数,能防止 SQL 注入(因为不会直接拼接 SQL)
- 传值时自动加引号、自动类型转换
1 | SELECT * FROM user WHERE id = #{id} |
${}:字符串拼接(不安全,需要谨慎)
${} 是直接把参数原样拼到 SQL 里,相当于字符串替换。
- 更像简单的字符串拼接
- 存在 SQL 注入风险(文档明确提到)
- 一般用户输入不能直接使用它
示例:
1 | SELECT * FROM ${tableName} |
那什么时候必须用 ${}?
主要用于不能使用占位符的场景,比如数据库对象名、关键字动态拼接。
动态排序
ORDER BY ${column} ${order}
动态表名
SELECT * FROM ${tableName}
这类位置如果用 #{} 会报错,因为 SQL 关键字或字段名不能当作值绑定。(某些场景必须用 ${}(如 order by、group by 字段))
小结
MyBatis 中 #{} 是安全的占位符,相当于 PreparedStatement,会把参数先替换成 “?” 再绑定,因此能 防止 SQL 注入,绝大多数场景都推荐用 #{}。
${} 是字符串拼接,会把参数直接拼到 SQL 里,可能带来 SQL 注入风险,所以一般不用,只有在 动态表名、排序字段、group by / order by 字段 这些不能作为参数绑定的位置,才必须用 ${}。
一句话:# 安全传值,$ 字符串拼接;能用 # 一律用 #,必须动态 SQL 的地方才用 $。
什么是 MyBatis-Plus?它有什么作用?它和 MyBatis 有哪些区别?
什么是 MyBatis-Plus?它有什么作用?和 MyBatis 有什么区别?
MyBatis-Plus 本质上是 MyBatis 的增强框架,它不改变 MyBatis 的核心,只是在原有基础上做增强,让开发更简单、高效。
1. MyBatis 是什么?
MyBatis 是一个半 ORM 框架,核心是 手写 SQL + 映射关系,开发者要写 SQL、写 XML、写 Mapper 方法,灵活但工作量大。
2. MyBatis-Plus 是什么?
文档中说得很清楚:MyBatis-Plus 就像 “MyBatis 的加强版弟弟”,对 MyBatis 做 二次封装,不做改变,只做增强 。
它主要解决 MyBatis 写 SQL 多、CRUD 重复度高的问题,让开发更轻松。
3. MyBatis-Plus 的作用(优势)
总结为三大类:
① 内置通用 CRUD(无需写 SQL)
只要让 Mapper 继承 BaseMapper,就自动获得增删改查:
1 | public interface UserMapper extends BaseMapper<User> {} |
不用写 XML,直接能用。
② 内置很多实用功能
- 分页插件(不用自己写 LIMIT 语句)
- 条件构造器(LambdaQueryWrapper,让代码更优雅)
- 性能分析、SQL 注入器等扩展能力
③ 自动代码生成器
根据数据库表自动生成:
- Entity
- Mapper / XML
- Service
大幅减少重复劳动。
4. MyBatis-Plus vs MyBatis(核心区别)
| 对比点 | MyBatis | MyBatis-Plus |
|---|---|---|
| CRUD | 手写 SQL | 内置通用 CRUD |
| 分页 | 手写分页 SQL | 自带分页插件 |
| 代码量 | 多 | 少 |
| 上手难度 | SQL 灵活但繁琐 | 更简单,更少样板代码 |
| 本质 | 半 ORM | MyBatis 的增强工具 |
一句话总结:
MyBatis 需要你自己写 SQL;MyBatis-Plus 帮你自动写一大部分,让你把精力放在业务逻辑上。
面试一句话总结
MyBatis-Plus 是 MyBatis 的增强框架,提供通用 CRUD、分页、自动生成代码、Lambda 条件构造器等功能,大幅减少 SQL 和模板代码。相比 MyBatis,更省代码、更高效率,但底层还是 MyBatis,不会改变原有机制。
Spring AOP默认用的是什么动态代理,两者的区别?
Spring默认使用的是JDK动态代理,SpringBoot2.x版本默认改为CGLIB动态代理了。
- JDK动态代理需要基于接口实现,通过反射生成代理类
- CGLIB动态代理基于类继承实现,通过字节码生成技术,生成目标类的子类,来实现对目标方法的代理
