SpringBoot面试题

什么是Spring Boot?有哪些优点?

Spring Boot 是基于 Spring 框架的快速开发脚手架,它的核心目标是 “简化 Spring 应用的开发、配置和部署流程”。它并非替代 Spring,而是通过 “约定大于配置” 的设计理念,整合 Spring 生态的底层能力(如 IOC、AOP),解决传统 Spring 开发中 “配置繁琐、依赖冲突、部署复杂” 等痛点,让开发者能更专注于业务逻辑,快速搭建可直接运行的生产级应用。

  • 自动配置,减少冗余配置

    传统 Spring 开发需要手动写大量 XML 或注解配置(比如配置数据源、MVC 映射等),而 Spring Boot 会根据引入的依赖(如引入spring-boot-starter-web)自动推断并配置相关组件(如 Tomcat、DispatcherServlet),无需手动干预,大幅减少配置工作量。

  • 起步依赖,解决依赖管理难题

    传统开发中,需要手动引入多个相关依赖(如 Web 开发需手动加spring-webtomcat等),还可能因版本不兼容导致冲突。Spring Boot 将常用依赖打包成 “starter”(如starter-webstarter-jpa),引入一个 starter 即可自动获取所有相关依赖及适配版本,避免版本冲突。

  • 嵌入式服务器,简化部署流程

    默认集成 Tomcat、Jetty 等嵌入式服务器,无需单独安装和配置外部服务器,应用可直接打包成 Jar 包,通过java -jar命令启动,部署流程从 “部署 war 包到服务器” 简化为 “一行命令启动”,极大降低部署成本。

  • 开发效率高,支持快速迭代

    内置热部署(如spring-boot-devtools),修改代码后无需重启应用即可生效;同时提供自动配置的监控(如 Actuator)、日志等基础能力,开发者无需从零搭建,能快速聚焦业务开发。

  • 无缝兼容 Spring 生态,学习成本低

    完全继承 Spring 的核心功能(IOC、AOP 等),熟悉 Spring 的开发者能快速上手;同时兼容 Spring Cloud 等微服务组件,适合微服务架构开发,无需额外学习新框架。

  • 简化测试,支持开箱即用的测试工具

    集成spring-boot-starter-test,内置 JUnit、Mockito 等测试工具,无需手动配置测试环境,可直接编写单元测试、集成测试,降低测试门槛。

说说Spring Boot的启动流程

sequenceDiagram
    participant Main as main()
    participant SA as SpringApplication
    participant AC as ApplicationContext
    participant RS as Runner
    
    Main->>SA: SpringApplication.run()
    SA->>SA: 初始化阶段
    SA->>AC: 创建应用上下文
    AC->>AC: 准备阶段
    AC->>AC: 刷新阶段(核心)
    AC->>RS: 执行Runner
    AC-->>Main: 返回ApplicationContext
  1. 启动入口:SpringApplication.run()

    一切从main方法调用SpringApplication.run()开始:

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class MyApp {
    public static void main(String[] args) {
    SpringApplication.run(MyApp.class, args);
    }
    }

    这个静态方法会:

    • 创建SpringApplication实例
    • 调用其run()方法开始启动流程
  2. SpringApplication实例化(初始化准备)

    SpringApplication.run()方法首先会创建SpringApplication实例,此过程主要完成基础配置初始化,具体包括:

    • 判断应用类型:根据类路径(classpath)是否存在ServletReactive相关类,确定应用类型(如SERVLETREACTIVE),后续将创建对应类型的ApplicationContext
    • 加载初始化器(Initializer):从META-INF/spring.factories中加载所有ApplicationContextInitializer(上下文初始化器),用于在上下文刷新前对其进行自定义配置(如设置环境变量、注册 Bean 等)。
    • 加载监听器(Listener):同样从META-INF/spring.factories中加载所有ApplicationListener(应用监听器),用于监听启动过程中的各种事件(如环境准备完成、上下文刷新完成等),实现扩展逻辑。
    • 确定主应用类:通过main方法所在的类,定位主应用类(即标注@SpringBootApplication的类)。
  3. run()方法执行(容器启动核心流程)

    SpringApplication实例创建后,调用其run(args)方法,开始容器启动,核心步骤如下:

    • 步骤 1:启动计时器与发布启动事件

      • 启动计时器(StopWatch),用于记录启动耗时。
      • 发布ApplicationStartingEvent事件(启动开始),所有监听器可响应此事件(如初始化日志系统)。
    • 步骤 2:准备环境(Environment)

      • 创建并配置 Environment(环境对象),包含:

        • 整合命令行参数(args)、系统变量、环境变量、配置文件(application.yml/properties)等配置源。
        • 激活指定的配置文件(如spring.profiles.active=dev)。
      • 发布ApplicationEnvironmentPreparedEvent事件(环境准备完成),监听器可在此阶段修改环境配置(如动态添加配置)。

    • 步骤 3:打印 Banner(可选)

      • 读取banner.txt(默认在classpath下),打印启动图案(可通过spring.banner.location自定义,或关闭spring.main.banner-mode=off)。
    • 步骤 4:创建 ApplicationContext(应用上下文)

      • 根据之前判断的应用类型,创建对应的ApplicationContext(如 Servlet 应用对应AnnotationConfigServletWebServerApplicationContext)。
      • 为上下文设置环境(Environment)、注册主应用类为配置类。
    • 步骤 5:准备 ApplicationContext(上下文预处理)

      • 应用初始化器(ApplicationContextInitializer):调用所有初始化器的initialize(context)方法,对上下文进行预处理(如注册 BeanDefinition、设置上下文属性)。
      • 发布ApplicationContextInitializedEvent事件(上下文初始化完成)。
      • 注册特殊 Bean:如将命令行参数args封装为CommandLineArgs对象并注册到容器。
    • 步骤 6:刷新 ApplicationContext(核心中的核心)

      此步骤复用了 Spring Framework 的AbstractApplicationContext.refresh()逻辑,是容器初始化的核心,主要包括:

      • BeanFactory 初始化:创建DefaultListableBeanFactory作为 Bean 容器。
      • 加载 Bean 定义:扫描主类所在包及子包(基于@ComponentScan),解析@Component@Service等注解的类,将其注册为BeanDefinition(Bean 定义)。
      • 执行自动配置:通过@EnableAutoConfiguration触发,从META-INF/spring.factories加载AutoConfiguration类,根据条件(@Conditional)注册符合条件的 Bean(如数据源、Web 服务器等)。
      • 初始化非懒加载单例 Bean:实例化并初始化所有非懒加载的单例 Bean(依赖注入在此阶段完成)。
      • 启动嵌入式服务器:对于 Web 应用,会触发ServletWebServerFactory(如TomcatServletWebServerFactory)的 Bean,创建并启动嵌入式服务器(Tomcat/Netty 等)。
      • 发布ContextRefreshedEvent事件(上下文刷新完成)。
    • 步骤 7:刷新后处理

      • 执行 ApplicationRunnerCommandLineRunner :容器刷新完成后,自动调用所有实现这两个接口的 Bean 的run()方法,用于执行启动后的初始化逻辑(如数据加载、缓存预热)。

        • 区别:ApplicationRunner接收ApplicationArguments(包装后的参数),CommandLineRunner接收原始String[] args
    • 步骤 8:启动完成

      • 发布ApplicationStartedEvent(容器启动完成)和ApplicationReadyEvent(应用可接收请求)事件。
      • 停止计时器,打印启动耗时。
      • 返回创建好的ApplicationContext,应用正式启动完成。

讲述一下 SpringBoot 自动装配原理

自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

SpringBoot 自动装配的核心是:

  • 基于 SPI 机制:通过spring.factories发现候选配置类。
  • 条件化加载:使用@Conditional系列注解动态决定配置类是否生效。
  • 可覆盖设计:默认配置可被用户自定义配置轻松替换。

一、核心设计思想

传统 Spring 开发需要手动配置大量 Bean(如数据源、WebMvc 配置),而 SpringBoot 通过自动装配实现:

  • 无配置启动:只需添加 starter 依赖(如spring-boot-starter-web),无需 XML 或 JavaConfig,应用即可自动配置并启动。
  • 条件化装配:根据类路径(Classpath)中存在的类、配置属性等条件,动态决定是否加载某个配置。
  • 可定制化:自动配置并非强制,用户可通过自定义 Bean 或配置文件覆盖默认配置。

二、实现机制:基于 SPI 的自动发现

SpringBoot 的自动装配基于 Java 的服务发现机制(SPI, Service Provider Interface),核心流程如下:

1. 启动入口:@SpringBootApplication注解

主类上的@SpringBootApplication是自动装配的起点,它是一个组合注解:

1
2
3
4
5
6
7
@SpringBootApplication
public class DemoApplication { ... }

// 等价于以下三个注解
@SpringBootConfiguration // 等价于@Configuration
@EnableAutoConfiguration // 开启自动装配核心
@ComponentScan // 扫描@Component及其子注解(@Service、@Repository等)

2. 核心注解:@EnableAutoConfiguration

这个注解导入了AutoConfigurationImportSelector类,负责加载自动配置类:

1
2
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { ... }

3. 自动配置类加载:spring.factories

AutoConfigurationImportSelector会从META-INF/spring.factories(Spring Boot 2.7+ 以后从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)文件中读取所有自动配置类:

1
2
3
4
5
6
# spring-boot-autoconfigure-*.jar/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
...

每个自动配置类(如WebMvcAutoConfiguration)都是一个完整的 JavaConfig,包含多个 Bean 定义。

三、条件化装配:@Conditional注解

自动配置类并非一定会生效,而是通过@Conditional及其衍生注解进行条件判断:

注解 作用场景
@ConditionalOnClass 当类路径中存在指定类时生效
@ConditionalOnMissingClass 当类路径中不存在指定类时生效
@ConditionalOnBean 当容器中存在指定 Bean 时生效
@ConditionalOnMissingBean 当容器中不存在指定 Bean 时生效
@ConditionalOnProperty 当配置文件中存在指定属性且值符合条件时生效
@ConditionalOnWebApplication 当应用是 Web 应用时生效

示例:DataSourceAutoConfiguration中的条件装配

1
2
3
4
5
6
7
8
9
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url", matchIfMissing = false)
public class DataSourceAutoConfiguration {
// 仅当类路径存在DataSource和EmbeddedDatabaseType
// 且容器中没有手动配置DataSource Bean
// 且配置文件中存在spring.datasource.url属性时,此配置才生效
}

四、自定义与覆盖机制

自动配置提供默认实现,但用户可通过以下方式覆盖:

1. 自定义 Bean

@Configuration类中手动定义 Bean,会优先于自动配置中的 Bean:

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig {
@Bean
public DataSource dataSource() {
// 自定义数据源,覆盖自动配置的数据源
return new MyDataSource();
}
}

2. 配置文件属性

通过application.propertiesapplication.yml修改默认配置:

1
2
3
4
5
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb # 覆盖自动配置的数据库连接URL
username: root
password: secret

3. 排除特定自动配置类

@SpringBootApplication中排除不需要的自动配置:

1
2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DemoApplication { ... }

五、自动配置执行流程

  1. 启动入口SpringApplication.run()触发自动装配流程。
  2. 加载自动配置类AutoConfigurationImportSelectorspring.factories读取候选配置类。
  3. 筛选有效配置类:通过@Conditional注解过滤不符合条件的配置类。
  4. 加载有效配置类:将符合条件的配置类注册到 Spring 容器,完成 Bean 的自动装配。

如何自定义一个 SpringBoot Srarter?

一、Starter 的核心组成

一个完整的 Starter 通常包含 3 部分:

  1. 自动配置模块:包含@Configuration配置类,负责自动装配 Bean。
  2. 配置属性类:通过@ConfigurationProperties绑定用户配置(如application.properties中的属性)。
  3. META-INF/spring.factories:注册自动配置类,让 SpringBoot 启动时能扫描到。

二、自定义 Starter 的步骤(以 “demo-starter” 为例)

步骤 1:创建 Starter 项目(Maven/Gradle)

推荐使用 Maven,项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
demo-spring-boot-starter/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── demo/
│ │ │ ├── config/ // 自动配置类
│ │ │ └── properties/ // 配置属性类
│ │ └── resources/
│ │ └── META-INF/
│ │ ├── spring.factories // 注册自动配置
│ │ └── spring-configuration-metadata.json // 配置元数据(可选,用于IDE提示)
│ └── test/
└── pom.xml

步骤 2:配置 pom.xml 依赖

核心依赖包括:

  • spring-boot-autoconfigure:提供自动配置基础能力。
  • spring-boot-configuration-processor:可选,用于生成配置元数据(IDE 会提示配置项)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependencies>
<!-- 自动配置核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.0</version> <!-- 与目标SpringBoot版本一致 -->
</dependency>

<!-- 配置元数据生成器(可选,提升用户体验) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.0</version>
<optional>true</optional> <!-- 避免传递依赖 -->
</dependency>

<!-- starter需要的核心功能依赖(例如,假设我们的starter提供一个简单的服务) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<optional>true</optional> <!-- 若用户可能已引入,标记为可选 -->
</dependency>
</dependencies>

注意

  • 避免引入不必要的依赖,防止版本冲突(使用optional=true控制传递)。
  • Starter 命名规范:官方建议第三方 Starter 命名为xxx-spring-boot-starter(区分官方的spring-boot-starter-xxx)。

步骤 3:定义配置属性类(绑定用户配置)

通过@ConfigurationProperties将用户在application.properties中的配置(如demo.prefix)绑定到 Java 类,供自动配置类使用。

1
2
3
4
5
6
7
8
// com.demo.properties.DemoProperties.java
@Data
@ConfigurationProperties(prefix = "demo") // 配置前缀
public class DemoProperties {
// 默认值:若用户未配置,使用该值
private String prefix = "defaultPrefix";
private boolean enabled = true; // 是否启用功能
}

用户使用时可在配置文件中自定义:

1
2
3
# application.properties
demo.prefix=myPrefix
demo.enabled=true

步骤 4:编写自动配置类(核心逻辑)

自动配置类负责根据条件(如@Conditional)装配 Bean,依赖步骤 3 的配置属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// com.demo.config.DemoAutoConfiguration.java
@Configuration
@EnableConfigurationProperties(DemoProperties.class) // 启用配置属性类
@ConditionalOnProperty(prefix = "demo", name = "enabled", havingValue = "true", matchIfMissing = true)
// 条件:当demo.enabled=true时生效(默认生效)
public class DemoAutoConfiguration {

private final DemoProperties properties;

// 注入配置属性
public DemoAutoConfiguration(DemoProperties properties) {
this.properties = properties;
}

// 自动装配核心Bean:仅当容器中没有该Bean时才自动配置(允许用户自定义覆盖)
@Bean
@ConditionalOnMissingBean
public DemoService demoService() {
return new DemoService(properties.getPrefix());
}
}

// 核心功能类
public class DemoService {
private final String prefix;

public DemoService(String prefix) {
this.prefix = prefix;
}

public String doSomething(String input) {
return prefix + ":" + input;
}
}

关键注解说明

  • @Configuration:标记为配置类。
  • @EnableConfigurationProperties:使DemoProperties生效,可被注入。
  • @ConditionalOnProperty:根据配置属性决定是否启用配置。
  • @ConditionalOnMissingBean:若用户手动定义了DemoService,则不使用自动配置的 Bean(允许覆盖)。

步骤 5:注册自动配置类(spring.factories)

SpringBoot 启动时会通过 SPI 机制扫描META-INF/spring.factories,因此需要在此文件中注册自动配置类。

创建src/main/resources/META-INF/spring.factories

1
2
3
# 格式:key为EnableAutoConfiguration的全路径,value为自动配置类的全路径(多个用逗号分隔)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.config.DemoAutoConfiguration

步骤 6:生成配置元数据(提升用户体验)

若引入了spring-boot-configuration-processor,编译时会自动生成META-INF/spring-configuration-metadata.json,IDE(如 IDEA)会根据该文件提示配置项(如demo.prefix的说明)。

如需自定义元数据(如描述),可创建src/main/resources/META-INF/additional-spring-configuration-metadata.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"properties": [
{
"name": "demo.prefix",
"type": "java.lang.String",
"description": "功能前缀,用于拼接输出内容。",
"defaultValue": "defaultPrefix"
},
{
"name": "demo.enabled",
"type": "java.lang.Boolean",
"description": "是否启用demo功能。",
"defaultValue": true
}
]
}

步骤 7:打包发布

执行mvn clean install,将 Starter 安装到本地仓库(或发布到私服),供其他项目引入。

三、测试自定义 Starter

创建一个测试项目, pom.xml 文件中引入自定义 Starter 依赖,验证自动配置是否生效。

1. pom.xml 文件中引入依赖

1
2
3
4
5
<dependency>
<groupId>com.demo</groupId>
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>

2. 编写测试代码

application.properties 配置文件中配置:

1
2
demo.prefix=myPrefix
demo.enabled=true

测试类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
public class DemoStarterTest {

@Autowired
private DemoService demoService; // 自动注入Starter中的Bean

@Test
public void testDoSomething() {
String result = demoService.doSomething("test");
//用户在 中配置了demo.prefix=myPrefix,结果应为"myPrefix:test"
//未配置 则输出 "defaultPrefix:test"
System.out.println(result);
}
}

3. 验证覆盖机制

手动定义DemoService,验证是否覆盖自动配置:

1
2
3
4
5
6
7
8
@Configuration
public class TestConfig {
// 若用户手动定义了`DemoService`,则不使用自动配置的 Bean(允许覆盖)。
@Bean
public DemoService customDemoService() {
return new DemoService("customPrefix"); // 自定义Bean
}
}

测试结果应为customPrefix:test,说明覆盖生效。

四、总结

自定义 Starter 的核心流程是:

  1. 定义配置属性:通过@ConfigurationProperties绑定用户配置。
  2. 编写自动配置类:用@Conditional控制 Bean 的装配条件,支持用户覆盖。
  3. 注册配置类:通过spring.factories让 SpringBoot 扫描到。