GQ SpringBoot

说说 Springboot 的启动流程

一句话总览

main → SpringApplication.run → 准备环境 → 创建并准备 ApplicationContext → 加载并注册 Bean 定义(含自动装配)→ 刷新容器(创建单例、启动内置服务器)→ 调用 Runner → 发布就绪事件
途中会伴随一串生命周期事件,失败会发失败事件。

分步骤说

  1. 入口 & 构建 SpringApplication

    • main()SpringApplication.run() 开始。
    • 构造 SpringApplication 时会推断应用类型(Servlet/Reactive/None),加载 ApplicationListenerApplicationContextInitializer 等(Boot 2.x 从 spring.factories,Boot 3.x 从 META-INF/spring/*.imports)。
  2. 准备 Environment(environmentPrepared)

    • 创建并配置 ConfigurableEnvironment读取配置application.yml/properties、命令行、环境变量等),确定激活的 profile
    • 这一阶段会发布 ApplicationEnvironmentPreparedEvent,并打印 Banner
  3. 创建 & 准备 ApplicationContext(contextPrepared)

    • 根据应用类型创建相应的 ApplicationContext(如 ServletWebServerApplicationContext)。
    • 设置基础属性(资源加载器、BeanName 生成器等),然后调用所有 ApplicationContextInitializer 对上下文做预初始化。
  4. 加载 Bean 定义 & 自动装配(contextLoaded)

    • BeanDefinitionLoader 把启动类及其扫描包里的组件注册进容器。
    • 进入工厂后置处理:触发 BeanFactoryPostProcessor(特别是 ConfigurationClassPostProcessor),解析 @Configuration/@ComponentScan/@Import
    • 关键:@EnableAutoConfiguration(来自 @SpringBootApplication 复合注解)会通过 AutoConfigurationImportSelector
      META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(或旧版的 spring.factories)读取自动配置类并注册 → 这就是“开箱即用”的来源(图右下红框部分)。
  5. 刷新容器 refresh(started)

    • 标准的 context.refresh()

      • 注册 BeanPostProcessor
      • 初始化 MessageSourceApplicationEventMulticaster
      • onRefresh:如果是 Web 应用,创建并启动内嵌服务器(Tomcat/Jetty/Undertow);
      • 注册监听器;
      • finishBeanFactoryInitialization:实例化所有单例 Bean,触发依赖注入、@PostConstructInitializingBean 等生命周期回调;
      • finishRefresh:发布 ContextRefreshedEvent
    • 到这里容器“可用了”,会发布 ApplicationStartedEvent

  6. 调用 Runner(ready)

    • 如果你定义了 ApplicationRunner/CommandLineRunner,此时会被调用,做业务启动收尾初始化。
    • 一切正常后发布 ApplicationReadyEvent,表示服务已就绪;若中途异常,则发布 ApplicationFailedEvent
  7. 运行阶段(running)

    • Web 容器已启动、端口已监听,Bean 都是单例常驻,应用进入稳定运行期。之后所有的请求、定时任务、消息消费等都在这阶段工作。

事件与分层的直观对照

  • 引导starting → environmentPrepared(准备环境)
  • 启动contextPrepared → contextLoaded → started → ready(容器准备、加载、刷新、就绪)
  • 运行running(对外提供服务)
    每一阶段都有相应 ApplicationEvent 广播,方便你在合适的时机做扩展。

img

精炼版

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 做了什么)

  1. 你在 pom.xml 引入了某个 spring-boot-starter-xxx

  2. 这个 starter 里会依赖一个 自动配置模块(通常形如 xxx-autoconfigure)。

  3. 自动配置模块里通过 @EnableAutoConfiguration 机制,配合:

    • Spring Boot 2.x:META-INF/spring.factories 列出自动配置类;
    • Spring Boot 3.x:改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
      由框架在启动时加载这些 *AutoConfiguration 类。
  4. 自动配置类里使用大量 条件注解@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty……)按需注册 Bean

  5. 配置属性通过 @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(简化流程版)

  1. 创建 xxx-spring-boot-starter(只写依赖坐标,不含自动配置逻辑)。

  2. 创建 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 中声明。
  3. starter 依赖自动配置模块;业务项目只需引入 starter 即可生效。

一句话比喻

Starter 就像“功能套餐”:你点了 Web 套餐,服务员(Spring Boot)就把 MVC、默认 JSON、内嵌容器、基本配置都端上来;默认能吃,想加料/换口味自己配就行。

Spring Boot 如何处理跨域请求(CORS)?

问:Spring Boot 怎么处理跨域(CORS)?

核心点:跨域要让浏览器的预检请求(OPTIONS) 和后续请求都能“过关”,并且在响应头里带上允许跨域的配置。Spring Boot 有三种常用做法:


1) 局部开启:在 Controller/方法上加 @CrossOrigin

  • 适合某几个接口临时放开跨域。

  • 用法简单:

    1
    2
    3
    @RestController
    @CrossOrigin(origins = "https://example.com", allowCredentials = "true")
    public class DemoController { ... }
  • 可以精确控制 origins / methods / headers / maxAge / allowCredentials 等。


2) 全局开启:实现 WebMvcConfigurer#addCorsMappings

  • 适合整个应用统一管理跨域策略。

  • 推荐做法(轻量、无侵入):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class WebMvcCorsConfig implements WebMvcConfigurer {
    @Override
    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
    @Bean
    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

常见坑 & 排查思路

  1. 预检请求被拦截

    • 现象:OPTIONS 直接 401/403/404。
    • 处理:确保拦截器/安全过滤器对 OPTIONS 放行;Spring Security 里加 http.cors()
  2. allowCredentials(true)* 冲突

    • allowCredentials(true)不能allowedOrigins("*"),要写具体域名。
  3. 顺序问题

    • 如果你加了自定义 Filter/Interceptor 做鉴权,务必让 跨域先处理,否则浏览器拿不到允许跨域的响应头。
  4. 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
2
@Autowired
private UserService userService;

要写单元测试时,你无法直接构造对象,因为没有无参构造器 + 没有办法给字段赋值,只能靠 Spring 容器启动,非常麻烦。

而构造器注入:

1
2
3
public UserController(UserService userService) {
this.userService = userService;
}

单元测试时可以直接 new
也可以 mock,完全不需要启动 Spring 容器。

这是 Spring 官方最推荐构造器注入的核心原因。


2 字段注入会隐藏依赖关系,不利于维护

字段注入时:

1
2
@Autowired
private OrderService orderService;

看起来简单,但你根本看不出一个类到底依赖了哪些组件。

构造器注入就很清晰:

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 级的跨会话缓存,需要手动开启,可以让不同 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 级的跨会话缓存,需要手动开启,可以让不同 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动态代理基于类继承实现,通过字节码生成技术,生成目标类的子类,来实现对目标方法的代理