Skip to content

五、Spring Boot(10 题)

5.1 ★★★★Spring Boot 的自动配置原理是什么?@EnableAutoConfiguration 的实现原理?spring.factories 文件的作用?

📖 点击查看答案

核心原理:基于约定优于配置,通过 @Conditional 条件注解 + SPI 机制,根据类路径中的依赖自动装配 Bean。

@EnableAutoConfiguration 实现原理

  1. 该注解通过 @Import(AutoConfigurationImportSelector.class) 导入选择器
  2. AutoConfigurationImportSelector#selectImports 调用 SpringFactoriesLoader.loadFactoryNames()
  3. 扫描所有依赖 jar 包下 META-INF/spring.factories 文件(Spring Boot 2.7+ 同时支持 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  4. 将其中 EnableAutoConfiguration key 对应的全类名作为候选配置类加载
  5. 通过 @ConditionalOnXXX 条件过滤,只装配满足条件的配置类

spring.factories 文件作用

  • SPI 配置文件,key=接口/注解全限定名,value=实现类全限定名(可多个,逗号分隔)
  • Spring Boot 启动时扫描该文件,完成自动配置类、监听器、初始化器等组件的注册
java
// 自动配置类示例
@Configuration
@ConditionalOnClass(DataSource.class)  // 类路径存在 DataSource 才生效
@ConditionalOnProperty(prefix = "spring.datasource", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(DataSourceProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() { return new MyService(); }
}

spring.factories 示例

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration,\
com.example.OtherAutoConfiguration

关键点

  • @ConditionalOnClass:类路径存在指定类时生效
  • @ConditionalOnMissingBean:容器中不存在该 Bean 时才创建,允许用户覆盖默认配置
  • @ConditionalOnProperty:配置文件满足条件时生效
  • @ConditionalOnBean / @ConditionalOnWebApplication 等多种条件注解
  • Spring Boot 3.x 推荐使用 AutoConfiguration.imports 文件替代 spring.factories

5.2 ★★@SpringBootApplication 注解底层做了什么?包含哪三个注解?

📖 点击查看答案

三个核心注解

  1. @SpringBootConfiguration:本质是 @Configuration,标记主配置类,允许通过 @Bean 注册 Bean
  2. @EnableAutoConfiguration:开启自动配置机制,加载 spring.factories 中的配置类
  3. @ComponentScan:自动扫描主类所在包及其子包下的 @Component / @Service / @Controller 等组件
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration   // 1. 配置类
@EnableAutoConfiguration    // 2. 自动配置
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})                          // 3. 组件扫描
public @interface SpringBootApplication {
    // 排除特定自动配置类
    Class<?>[] exclude() default {};
    // 排除特定自动配置类名
    String[] excludeName() default {};
    // 扫描包路径
    String[] scanBasePackages() default {};
    // 扫描的基类
    Class<?>[] scanBasePackageClasses() default {};
}

关键点

  • @ComponentScan 默认扫描主类所在包及子包,这也是为什么主类要放在根包下
  • excludeFilters 排除自动配置类,避免重复
  • 可通过 scanBasePackages 自定义扫描路径
  • @SpringBootConfiguration@Configuration 等价,但语义更明确

5.3 ★★★Spring Boot 应用的启动过程是怎样?SpringApplication.run() 内部做了哪些事?

📖 点击查看答案

启动流程SpringApplication.run() 核心步骤):

  1. 创建 SpringApplication 实例

    • 推断应用类型(SERVLET / REACTIVE / NONE),根据 classpath 是否存在相关类
    • META-INF/spring.factories 加载 ApplicationContextInitializerApplicationListener
    • 推断主类(通过堆栈找到 main 方法所在类)
  2. run() 方法执行

    • 获取并启动 SpringApplicationRunListener(默认 EventPublishingRunListener),发布 ApplicationStartingEvent
    • 准备环境(Environment):加载 application.yml / application.properties、命令行参数、系统属性等,发布 ApplicationEnvironmentPreparedEvent
    • 打印 Banner
    • 创建 ApplicationContext(根据应用类型选择 AnnotationConfigServletWebServerApplicationContext 等)
    • 准备上下文:设置环境、执行 ApplicationContextInitializer、注册主配置类,发布 ApplicationContextInitializedEventApplicationPreparedEvent
    • refresh 上下文:核心,解析配置类、扫描 Bean、创建 Bean、启动内嵌 Web 容器(Tomcat)
    • afterRefresh:注册 ApplicationRunner / CommandLineRunner 回调
    • 发布 ApplicationReadyEvent / ApplicationFailedEvent
java
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);   // 核心:IOC 容器初始化 + Web 容器启动
        afterRefresh(context, applicationArguments);
        listeners.started(context);
        callRunners(context, applicationArguments);  // 执行 ApplicationRunner
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
    }
    return context;
}

关键扩展点

  • SpringApplicationRunListener:监听启动各阶段事件
  • ApplicationContextInitializer:在 context refresh 前初始化上下文
  • ApplicationListener:响应各种启动事件
  • ApplicationRunner / CommandLineRunner:容器启动完成后执行自定义逻辑
  • Banner 接口:自定义启动 Banner

5.4 ★★★★Spring Boot 配置文件加载顺序是怎样的?多环境配置(Profile)如何实现?配置优先级顺序是怎样的?

📖 点击查看答案

配置文件加载位置(优先级从高到低,高优先级覆盖低优先级):

  1. 命令行参数(--server.port=8080
  2. SPRING_APPLICATION_JSON 环境变量中的 JSON
  3. ServletConfig / ServletContext 初始化参数
  4. JNDI 属性
  5. Java 系统属性(System.getProperties()
  6. 操作系统环境变量
  7. RandomValuePropertySourcerandom.*
  8. jar 包外的 application-{profile}.yml/properties
  9. jar 包内的 application-{profile}.yml/properties
  10. jar 包外的 application.yml/properties
  11. jar 包内的 application.yml/properties
  12. @PropertySource 注解指定的配置
  13. SpringApplication.setDefaultProperties 设置的默认值

Profile 多环境配置

  • 文件命名:application-{profile}.yml,如 application-dev.ymlapplication-prod.yml
  • 激活方式:
    • 配置文件中:spring.profiles.active=dev
    • 命令行:--spring.profiles.active=prod
    • 环境变量:SPRING_PROFILES_ACTIVE=prod
    • 默认配置文件 application-default.yml 在未指定时生效
yaml
# application.yml
spring:
  profiles:
    active: dev   # 激活 dev 配置
    group:        # Spring Boot 2.4+ 支持组
      "prod": ["prod-db", "prod-mq"]
    include: common  # 始终包含的配置

Spring Boot 2.4+ 配置变更

  • 配置文件不再默认支持多文档覆盖(需使用 spring.config.activate.on-profile
  • spring.profiles.include 只能在主文档中使用
  • 引入 spring.config.import 支持导入其他配置源(如 Nacos、Consul)

优先级总结:命令行 > 环境变量 > 外部 jar 同目录配置 > 内部配置 > 默认值。


5.3 ★★★Spring Boot 条件注解 @ConditionalOnXXX 系列有哪些?各自的使用场景和工作原理是什么?

📖 点击查看答案

条件注解分类

类条件

  • @ConditionalOnClass:类路径存在指定类时生效,用于自动配置类是否生效
  • @ConditionalOnMissingClass:类路径不存在指定类时生效

Bean 条件

  • @ConditionalOnBean:容器中存在指定 Bean 时生效
  • @ConditionalOnMissingBean:容器中不存在指定 Bean 时生效(最常用,允许用户覆盖默认 Bean)

属性条件

  • @ConditionalOnProperty:配置文件满足条件时生效
    java
    @ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true", matchIfMissing = false)

资源条件

  • @ConditionalOnResource:类路径存在指定资源文件时生效
    java
    @ConditionalOnResource(resources = "classpath:custom.properties")

Web 应用条件

  • @ConditionalOnWebApplication:是 Web 应用时生效(SERVLET / REACTIVE)
  • @ConditionalOnNotWebApplication:非 Web 应用时生效

其他条件

  • @ConditionalOnExpression:SpEL 表达式为 true 时生效
  • @ConditionalOnJava:JDK 版本满足条件时生效
  • @ConditionalOnSingleCandidate:容器中指定类型的 Bean 唯一或存在首选 Bean 时生效
  • @ConditionalOnDeploymentID:部署 ID 匹配时生效

工作原理

  1. 所有条件注解都基于 @Conditional 元注解,指向 Condition 接口实现
  2. Spring 在注册 Bean 时调用 Condition#matches() 判断是否满足条件
  3. 配置类的条件在配置类解析阶段判断,Bean 的条件在 Bean 注册阶段判断
  4. @ConditionalOnClass 通过 ASM 读取类字节码判断,避免直接 Class.forName 触发 ClassNotFoundException
java
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class DataSourceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean   // 用户未自定义时才创建默认 DataSource
    public DataSource dataSource() { /* ... */ }
}

自定义条件

java
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("app.enabled").equals("true");
    }
}
@Conditional(MyCondition.class)

5.6 ★★★Spring Boot 的 Starter 是什么?工作原理是什么?如何自定义一个 Starter?

📖 点击查看答案

Starter 是什么

  • 一组约定的依赖集合 + 自动配置类,引入即可开箱即用
  • 命名约定:官方 spring-boot-starter-xxx,第三方 xxx-spring-boot-starter
  • 解决"依赖冲突 + 配置繁琐"问题,提供合理的默认配置

工作原理

  1. 引入 Starter 依赖,jar 包通过 META-INF/spring.factories(或 3.x 的 AutoConfiguration.imports)注册自动配置类
  2. Spring Boot 启动时扫描 spring.factories,加载自动配置类
  3. 自动配置类通过 @ConditionalOnXXX 条件判断决定是否生效
  4. 通过 @EnableConfigurationProperties 绑定配置文件属性到 XxxProperties

自定义 Starter 步骤

  1. 创建 XxxProperties 配置属性类:
java
@ConfigurationProperties(prefix = "hello")
@Data
public class HelloProperties {
    private String name = "World";
    private int times = 1;
}
  1. 创建核心服务类(非配置类):
java
public class HelloService {
    private HelloProperties properties;
    public String sayHello() {
        return "Hello, " + properties.getName();
    }
}
  1. 创建自动配置类:
java
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnProperty(prefix = "hello", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HelloAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public HelloService helloService(HelloProperties properties) {
        return new HelloService(properties);
    }
}
  1. 注册自动配置类:

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.hello.HelloAutoConfiguration

Spring Boot 3.x 使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.hello.HelloAutoConfiguration
  1. 使用:引入依赖后在 application.yml 配置 hello.name=Spring 即可使用。

5.7 ★★★★Spring Boot Actuator 提供了哪些监控端点?如何自定义健康检查?生产环境如何做访问控制?

📖 点击查看答案

常用端点(默认 /actuator/{endpoint}):

  • /health:应用健康状态(数据库、Redis、磁盘等)
  • /info:应用基本信息
  • /metrics:JVM、Tomcat、HTTP 等指标
  • /env:环境变量、配置属性
  • /loggers:动态调整日志级别
  • /threaddump:线程栈信息
  • /heapdump:堆转储文件
  • /beans:所有 Bean 列表
  • /mappings:所有 @RequestMapping 映射
  • /configprops:配置属性
  • /shutdown:优雅关闭应用(默认关闭)
  • /prometheus:Prometheus 监控格式(需引入 micrometer-registry-prometheus)

端点暴露配置

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus   # 暴露的端点
        exclude: env,beans                        # 排除的端点
  endpoint:
    health:
      show-details: always    # 显示健康详情(默认 never)
    shutdown:
      enabled: true           # 开启优雅关闭
  server:
    port: 8081                # 独立管理端口(推荐)

自定义健康检查

java
@Component
public class MyHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        int errorCode = check();
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().withDetail("status", "OK").build();
    }
}

// 或自定义端点
@Component
@Endpoint(id = "custom")
public class CustomEndpoint {
    @ReadOperation
    public Map<String, Object> info() {
        Map<String, Object> map = new HashMap<>();
        map.put("version", "1.0");
        return map;
    }
    @WriteOperation
    public void update(String name) { /* ... */ }
}

生产环境访问控制

  1. 独立管理端口management.server.port=8081,不对外暴露
  2. Spring Security 集成:Actuator 端点自动纳入 Security 过滤链
    yaml
    spring.security.user.name=admin
    spring.security.user.password=xxx
  3. IP 白名单:通过 Nginx 或防火墙限制管理端口的访问 IP
  4. 敏感信息脱敏/env 默认对密码类属性脱敏
  5. 禁用危险端点/shutdown/heapdump 等在生产环境谨慎开放

5.8 ★★Spring Boot 中如何实现统一异常处理?@ControllerAdvice@ExceptionHandler 的原理?

📖 点击查看答案

统一异常处理实现

java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(f -> f.getField() + ": " + f.getDefaultMessage())
            .collect(Collectors.joining("; "));
        return Result.fail(400, msg);
    }

    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.fail(500, "系统繁忙");
    }
}

@ControllerAdvice 原理

  1. 本质是 @Component,会被 Spring 扫描注册
  2. ExceptionHandlerExceptionResolver 初始化时,initExceptionHandlerAdviceCache() 扫描所有 @ControllerAdvice 类,建立 @ExceptionHandler 方法缓存
  3. 拦截到异常时,ExceptionHandlerExceptionResolver#doResolveHandlerMethodException 在缓存中查找匹配的 @ExceptionHandler 方法
  4. 找到则通过反射调用并返回结果,找不到则交给下一个 HandlerExceptionResolver 处理
  5. @ResponseBody / @RestControllerAdvice 通过 RequestResponseBodyMethodProcessor 将返回值序列化为 JSON

@ExceptionHandler 匹配规则

  • 优先匹配异常类型最精确的 @ExceptionHandler 方法
  • 支持 Throwable 及其子类,按深度匹配
  • @ResponseStatus 注解可指定 HTTP 状态码

@RestControllerAdvice = @ControllerAdvice + @ResponseBody,返回值直接序列化为 JSON。


5.9 ★★Spring Boot 内嵌了哪些 Web 容器?如何切换为 Undertow 或 Jetty?如何定制参数?

📖 点击查看答案

内嵌容器

  • Tomcat:默认容器,spring-boot-starter-web 默认引入
  • Jetty:轻量级,适合长连接场景
  • Undertow:高性能,Servlet 3.1+,内存占用少
  • Reactor Nettyspring-boot-starter-webflux 默认

切换为 Undertow

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

切换为 Jetty:将 spring-boot-starter-tomcat 替换为 spring-boot-starter-jetty

定制参数(以 Tomcat 为例):

yaml
server:
  port: 8080
  tomcat:
    max-connections: 8192          # 最大连接数
    threads:
      max: 200                    # 最大工作线程数
      min-spare: 10               # 最小空闲线程数
    accept-count: 100             # 等待队列长度
    max-http-form-post-size: 2MB  # 表单最大大小
    connection-timeout: 20s       # 连接超时
    uri-encoding: UTF-8
    max-swallow-size: 2MB         # 异常请求最大吞入字节数
  compression:
    enabled: true                 # 开启 Gzip
    mime-types: application/json,text/html
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: xxx
    key-store-type: PKCS12

编程式定制

java
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizer() {
    return factory -> {
        factory.setPort(8081);
        factory.addConnectorCustomizers(connector -> {
            connector.setProperty("maxThreads", "300");
        });
    };
}

5.10 ★★Spring Boot 如何实现优雅停机(Graceful Shutdown)?为什么需要优雅停机?

📖 点击查看答案

为什么需要优雅停机

  • 避免请求处理到一半被强制中断,导致数据不一致
  • 让正在处理的请求完成,提升用户体验
  • 释放资源(连接池、文件句柄、消息队列消费者)
  • 通知注册中心下线,避免流量继续打到该实例

Spring Boot 2.3+ 内置优雅停机

yaml
server:
  shutdown: graceful          # 开启优雅停机(默认 immediate)
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s   # 优雅停机超时时间

原理

  1. JVM 收到 SIGTERM 信号(kill -15)或 SIGINT 信号(Ctrl+C)
  2. Spring 调用 SpringApplication 注册的 shutdown hook
  3. WebServerGracefulShutdownLifecycle 拒绝接收新请求,等待已有请求处理完成
  4. 超时后强制关闭
  5. 依次关闭 Lifecycle Bean(如 @PreDestroy 方法)
  6. 关闭 Web 容器、IOC 容器

配合 actuator shutdown 端点

yaml
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

@PreDestroy 资源释放

java
@Component
public class MyService {
    @PreDestroy
    public void cleanup() {
        // 释放线程池、连接等资源
        executor.shutdown();
        if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
            executor.shutdownNow();
        }
    }
}

K8s 场景完整方案

  1. PreStop 钩子:先调用注册中心下线接口(如 Eureka /service-registry/instance-status/DOWN)或 actuator/shutdown
  2. terminationGracePeriodSeconds: 45(大于优雅停机超时)
  3. 等待流量摘除(服务消费者刷新注册表)
  4. 容器收到 SIGTERM,执行 Spring 优雅停机

基于 4 份主流面试题库整合精选,共 200 题