Skip to content

本轮 SpringBoot2核心功能面试题库(带答案) ​

生成时间:2026-05-21 22:47


【面试题】1. SpringBoot自动配置的"条件装配"是如何实现的?请结合@ConditionalOnClass举例说明。 ​

【标准答案】 SpringBoot的条件装配核心是@Conditional系列注解,配合Condition接口实现。

原理层面:

  1. @EnableAutoConfiguration导入AutoConfigurationImportSelector
  2. 该Selector读取META-INF/spring.factories或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  3. 对每个候选自动配置类,评估@Conditional条件
  4. 条件通过则注册Bean,不通过则跳过

@ConditionalOnClass示例:

java
@Configuration
@ConditionalOnClass(DataSource.class)  // classpath存在DataSource才生效
public class DataSourceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

底层实现:

  • @ConditionalOnClass注解被OnClassCondition类处理
  • 通过ClassLoader尝试加载指定类,成功则条件成立
  • 不需要实例化Bean,仅检查类是否存在

面试加分点:

  • 条件注解在Bean实例化前执行,性能高效
  • 可以组合多个条件:@ConditionalOnClass + @ConditionalOnProperty等
  • 自动配置的"按需加载"避免不必要的Bean创建

【面试题】2. SpringBoot配置文件的加载顺序是怎样的?优先级高的配置如何覆盖低的? ​

【标准答案】 SpringBoot配置加载遵循"外层覆盖内层、后加载覆盖先加载"原则:

优先级从高到低:

  1. 命令行参数 --server.port=8081
  2. JAR包外部的application-{profile}.properties/yml
  3. JAR包内部的application-{profile}.properties/yml
  4. JAR包外部的application.properties/yml
  5. JAR包内部的application.properties/yml
  6. @PropertySource注解指定的配置
  7. 默认属性SpringApplication.setDefaultProperties

覆盖机制举例:

# 内部application.yml
server:
  port: 8080

# 外部application.yml(与JAR同目录)
server:
  port: 9090

启动后端口为9090,外部配置覆盖内部。

关键源码: ConfigFileApplicationListener按优先级加载配置,后者覆盖前者同名属性。

面试加分点:

  • "外部"指JAR所在目录,生产环境可通过外部配置实现运维解耦
  • spring.config.location可自定义配置文件路径
  • spring.config.additional-location追加配置源而非覆盖

【面试题】3. 解释Spring Bean的完整生命周期,包括容器启动到Bean销毁的全过程。 ​

【标准答案】 Bean生命周期分为实例化→属性赋值→初始化→使用→销毁五个阶段:

详细流程:

1. 实例化
   - InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
   - 构造函数创建Bean实例

2. 属性赋值
   - InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
   - @Autowired/@Value依赖注入
   - InstantiationAwareBeanPostProcessor.postProcessPropertyValues()

3. 初始化(核心)
   - Aware接口回调(BeanNameAware/ApplicationContextAware等)
   - BeanPostProcessor.postProcessBeforeInitialization()
   - @PostConstruct注解方法
   - InitializingBean.afterPropertiesSet()
   - 自定义init-method
   - BeanPostProcessor.postProcessAfterInitialization() ← AOP代理在此创建

4. 使用
   - Bean正常提供服务

5. 销毁
   - @PreDestroy注解方法
   - DisposableBean.destroy()
   - 自定义destroy-method

关键点:

  • BeanPostProcessor是扩展核心,AOP、事务注解在此生效
  • InitializingBean比@PostConstruct执行早?错!实际顺序:@PostConstruct → InitializingBean → init-method

面试加分点:

  • FactoryBean与普通Bean区别:FactoryBean通过getObject()返回对象,可用于创建复杂Bean
  • BeanFactoryPostProcessor在Bean实例化前执行,可修改BeanDefinition

【面试题】4. SpringBoot中@Transactional事务传播行为REQUIRED和REQUIRES_NEW有什么区别?各适用什么场景? ​

【标准答案】 REQUIRED(默认):

  • 存在事务则加入,不存在则新建
  • 多个方法共享同一事务,任一方法回滚则整体回滚

REQUIRES_NEW:

  • 总是新建独立事务,已存在事务被挂起
  • 新事务独立提交/回滚,不影响外层事务

代码示例:

java
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderDao.insert(order);
        logService.saveLog(order);  // 即使logService失败,order也不会回滚
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(Order order) {
        // 独立事务,失败不影响外层
        logDao.insert(new Log(order));
    }
}

适用场景:

  • REQUIRED:常规业务操作,需要原子性
  • REQUIRES_NEW:日志记录、发送通知等"必须执行但不能影响主业务"的场景

面试加分点:

  • REQUIRES_NEW存在事务挂起开销,性能影响需考虑
  • 同类方法调用@Transactional会失效(绕过代理),需通过AopContext或注入自身解决
  • 事务传播仅对RuntimeException回滚,checked异常需配置rollbackFor

【面试题】5. Spring AOP中JDK动态代理和CGLIB代理有什么区别?SpringBoot默认使用哪种? ​

【标准答案】 JDK动态代理:

  • 基于接口,代理类实现与目标类相同的接口
  • 目标类必须实现至少一个接口
  • 生成的代理类调用InvocationHandler.invoke()

CGLIB代理:

  • 基于继承,生成目标类的子类
  • 不要求接口,可代理普通类
  • 通过方法重写实现增强,final方法无法代理

SpringBoot默认策略:

properties
# SpringBoot 2.x 默认配置
spring.aop.proxy-target-class=true  # 默认使用CGLIB

源码逻辑: DefaultAopProxyFactory.createAopProxy():

java
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    return new JdkDynamicAopProxy(config);  // 接口用JDK
}
return new ObjenesisCglibAopProxy(config);  // 类用CGLIB

面试加分点:

  • Spring 5+ CGLIB不再依赖外部库,内置了CGLIB
  • @EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB
  • 性能差异:JDK代理调用稍快,CGLIB创建稍慢(现代框架已优化差距很小)
  • CGLIB无法代理private和final方法

【面试题】6. SpringBoot如何实现自定义Starter?请描述核心步骤和注意事项。 ​

【标准答案】 自定义Starter三要素:

1. 创建autoconfigure模块:

my-spring-boot-starter-autoconfigure/
├── src/main/java/
│   └── com/example/
│       ├── MyAutoConfiguration.java
│       ├── MyProperties.java
│       └── MyService.java
└── src/main/resources/
    └── META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

2. 核心代码:

java
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties properties) {
        return new MyService(properties.getPrefix());
    }
}

@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    private String prefix = "default";
    // getter/setter
}

3. 注册自动配置: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.MyAutoConfiguration

注意事项:

  • 命名规范:xxx-spring-boot-starter(官方)vs spring-boot-starter-xxx(第三方)
  • 必须提供spring-configuration-metadata.json支持IDE提示
  • 使用@ConditionalOnMissingBean允许用户覆盖默认Bean
  • 添加@AutoConfigureAfter/@AutoConfigureBefore控制加载顺序

面试加分点:

  • SpringBoot 2.7+推荐使用.imports文件替代spring.factories
  • 可通过spring.factories指定EnableAutoConfiguration实现兼容旧版本

【面试题】7. SpringBoot事件机制的原理是什么?如何在业务中使用ApplicationEvent? ​

【标准答案】 事件机制三要素:

  1. ApplicationEvent - 事件对象
  2. ApplicationEventPublisher - 事件发布者
  3. ApplicationListener / @EventListener - 事件监听者

自定义事件示例:

java
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
}

// 2. 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void createOrder(Order order) {
        orderDao.save(order);
        publisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

// 3. 监听事件(两种方式)
@Component
public class OrderEventListener {
    // 方式一:@EventListener注解
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("订单创建:" + event.getOrder());
    }
    
    // 方式二:实现ApplicationListener接口
    // public class OrderEventListener implements ApplicationListener<OrderCreatedEvent>
}

异步事件:

java
@EventListener
@Async  // 配合@EnableAsync
public void onOrderCreated(OrderCreatedEvent event) {
    // 异步处理,不阻塞主线程
}

内置核心事件:

  • ContextRefreshedEvent - 容器刷新完成
  • ContextStartedEvent - 容器启动
  • ContextClosedEvent - 容器关闭
  • ApplicationReadyEvent - 应用就绪

面试加分点:

  • 事件默认同步执行,@Async实现异步
  • @EventListener(condition = "...")支持SpEL条件过滤
  • @TransactionalEventListener可绑定事务阶段(提交后/回滚后)

【面试题】8. SpringBoot Actuator如何实现自定义健康检查端点? ​

【标准答案】 内置端点:

  • /actuator/health - 应用健康状态
  • /actuator/info - 应用信息
  • /actuator/metrics - 指标数据

自定义HealthIndicator:

java
@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        // 检查逻辑
        boolean healthy = checkExternalService();
        
        if (healthy) {
            return Health.up()
                .withDetail("service", "External API")
                .withDetail("responseTime", "120ms")
                .build();
        } else {
            return Health.down()
                .withDetail("service", "External API")
                .withDetail("error", "Connection timeout")
                .build();
        }
    }
    
    private boolean checkExternalService() {
        // 检查外部服务是否可用
        return true;
    }
}

访问结果:

json
{
  "status": "UP",
  "components": {
    "custom": {
      "status": "UP",
      "details": {
        "service": "External API",
        "responseTime": "120ms"
      }
    },
    "db": { "status": "UP" }
  }
}

暴露配置:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics  # 或 "*" 暴露全部
  endpoint:
    health:
      show-details: always  # 显示详细信息

面试加分点:

  • 健康状态:UP/DOWN/OUT_OF_SERVICE/UNKNOWN
  • 可组合多个健康检查:HealthAggregator决定最终状态
  • 生产环境需配合Spring Security保护端点

【面试题】9. SpringBoot整合MyBatis时,@MapperScan和@Mapper有什么区别? ​

【标准答案】 @MapperScan(推荐):

  • 配置在启动类或配置类上
  • 批量扫描指定包下所有Mapper接口
  • 无需每个Mapper都加注解

@Mapper:

  • 配置在每个Mapper接口上
  • 标记该接口为MyBatis Mapper
  • 需要每个接口单独添加

代码对比:

java
// 方式一:@MapperScan(推荐)
@SpringBootApplication
@MapperScan("com.example.mapper")  // 批量扫描
public class Application { }

// 方式二:@Mapper(每个接口单独标记)
@Mapper
public interface UserMapper {
    User findById(Long id);
}

@Mapper
public interface OrderMapper {
    Order findById(Long id);
}

底层原理:

  • @MapperScan通过@Import(MapperScannerRegistrar.class)导入注册器
  • MapperScannerRegistrar注册MapperScannerConfigurer
  • MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor
  • 扫描包并将接口注册为MapperFactoryBean

面试加分点:

  • @MapperScan可配置多个包:@MapperScan({"com.example.mapper", "com.example.dao"})
  • @MapperScan支持自定义sqlSessionFactoryRef和sqlSessionTemplateRef
  • MyBatis-Plus无需@MapperScan,使用@MappersScan(注意拼写)
  • @Mapper和@MapperScan可同时使用,不冲突

【面试题】10. SpringBoot如何实现优雅停机?生产环境如何配置? ​

【标准答案】 优雅停机核心:

  1. 停止接收新请求
  2. 等待现有请求处理完成
  3. 释放资源、关闭连接
  4. 退出应用

配置方式:

yaml
server:
  shutdown: graceful  # 启用优雅停机

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 等待超时

内置机制:

  • SpringBoot 2.3+ 内置GracefulShutdown
  • 接收到SIGTERM信号后进入优雅停机
  • SmartLifecycle实现类可自定义关闭逻辑

自定义关闭钩子:

java
@Component
public class GracefulShutdown implements DisposableBean, SmartLifecycle {
    
    private volatile boolean running = false;
    
    @Override
    public void start() {
        running = true;
    }
    
    @Override
    public void stop() {
        // 清理资源
        System.out.println("正在关闭,等待请求完成...");
        running = false;
    }
    
    @Override
    public boolean isRunning() {
        return running;
    }
    
    @Override
    public int getPhase() {
        return Integer.MAX_VALUE;  // 最后关闭
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("资源已释放");
    }
}

Kubernetes配置:

yaml
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 10"]  # 等待Service剔除

面试加分点:

  • getPhase()返回值越大越晚关闭,越小越早关闭
  • Actuator的/actuator/shutdown端点可触发优雅停机(需手动开启)
  • 线程池需自行处理:executor.shutdown() + awaitTermination()
  • 数据库连接池Druid/HikariCP已内置优雅关闭