Skip to content

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

【面试题】1. SpringBoot中@Import注解有哪三种用法?分别适用于什么场景? ​

【标准答案】三种用法:

  1. 直接导入类:@Import(TargetClass.class),直接把普通类注册为Bean,不需要加@Component等注解。适合导入第三方jar包里的类,因为你改不了人家源码加不了注解。
  2. 导入ImportSelector实现类:返回String数组,数组里是要注册的Bean的全类名。可以根据条件动态决定注册哪些Bean,SpringBoot自动配置大量用了这种方式——AutoConfigurationImportSelector就是典型。
  3. 导入ImportBeanDefinitionRegistrar实现类:直接操作BeanDefinitionRegistry手动注册BeanDefinition,是最底层最灵活的方式。MyBatis的@MapperScan就是用这种方式,因为需要扫描指定包下所有接口逐一注册。

面试关键点:三种方式从简到繁、从声明式到编程式,越往后灵活度越高但复杂度也越高。SpringBoot自动配置核心走的是ImportSelector,框架整合走的是Registrar。

【面试题】2. SpringBoot的@ConditionalOnProperty是怎么工作的?如果配置项不存在会怎样? ​

【标准答案】@ConditionalOnProperty通过匹配application配置文件中的属性值来决定是否装配Bean。核心属性有三个:

  • prefix/value:属性前缀或完整key
  • havingValue:期望的属性值
  • matchIfMissing:当属性不存在时是否匹配,默认false

工作机制:Spring在注册BeanDefinition前,读取Environment中对应属性值,与havingValue比较。如果属性不存在且matchIfMissing=false(默认),条件不满足,Bean不注册;如果matchIfMissing=true,属性不存在时视为匹配成功。

典型应用:@ConditionalOnProperty(prefix = "spring.autoconfigure", name = "enable", havingValue = "true", matchIfMissing = true)——属性没配时默认开启。很多开关型配置用这个模式,比如spring.mvc.throw-exception-if-no-handler-found。

面试踩坑:havingValue不是"包含"而是"等于",不是contains是equals。而且属性值为空字符串和属性不存在是两码事,空字符串不等于不存在。

【面试题】3. BeanFactory和ApplicationContext有什么区别?为什么SpringBoot用ApplicationContext? ​

【标准答案】BeanFactory是Spring最底层的容器接口,只提供Bean的获取和管理基础功能,懒加载模式——Bean在第一次getBean时才创建。

ApplicationContext是BeanFactory的子接口(严格说是通过HierarchicalBeanFactory间接继承),在BeanFactory基础上扩展了:

  • 国际化支持(MessageSource)
  • 事件发布机制(ApplicationEventPublisher)
  • 资源加载(ResourceLoader)
  • AOP集成
  • 环境抽象(Environment)
  • Bean生命周期管理(自动注册BeanPostProcessor等)

SpringBoot用ApplicationContext的原因:一是预加载模式,启动时就把所有单例Bean创建好,问题早暴露;二是需要事件机制来发布ContextRefreshedEvent等内置事件;三是需要Environment来管理多环境配置;四是自动配置依赖BeanPostProcessor来处理@Conditional等条件注解。

一句话:BeanFactory是能用,ApplicationContext是好用。生产环境没理由用BeanFactory。

【面试题】4. 自定义一个SpringBoot Starter需要哪些步骤?有哪些必须注意的点? ​

【标准答案】核心步骤:

  1. 创建两个模块:xxx-spring-boot-autoconfigure(自动配置模块)和xxx-spring-boot-starter(依赖聚合模块,只做pom依赖引用)
  2. 编写自动配置类:用@Configuration + 条件注解(@ConditionalOnClass、@ConditionalOnMissingBean等)声明Bean
  3. 编写属性类:@ConfigurationProperties(prefix = "xxx")绑定配置,@Validated加校验
  4. 注册自动配置:在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)或META-INF/spring.factories(2.7以下)中写入配置类全路径
  5. Starter模块只做依赖聚合:pom里引autoconfigure模块和必要的第三方依赖

必须注意的点:

  • @ConditionalOnMissingBean必须加:让用户可以覆盖默认Bean
  • 属性类用@ConfigurationProperties而非@Value:支持松散绑定、校验、批量绑定
  • 配置类不要包扫描:避免扫到不该扫的类,靠imports文件注册即可
  • Starter命名规范:官方spring-boot-starter-xxx,第三方xxx-spring-boot-starter
  • 2.7版本双写注册:同时写imports文件和spring.factories,兼容新旧版本

【面试题】5. SpringBoot的配置加载优先级是怎样的?哪些配置源会覆盖application.yml? ​

【标准答案】从高到低(高优先级覆盖低优先级):

  1. 命令行参数(--server.port=8081)
  2. JNDI属性
  3. Java系统属性(System.getProperties(),-Dserver.port=8081)
  4. 操作系统环境变量(SERVER_PORT=8081)
  5. jar包外application-{profile}.yml
  6. jar包内application-{profile}.yml
  7. jar包外application.yml
  8. jar包内application.yml
  9. @ConfigurationProperties的默认值

关键规则:

  • 高优先级覆盖低优先级,不是合并,是直接覆盖
  • jar包外 > jar包内:运维改配置不用重新打包
  • 带profile > 不带profile:多环境优先
  • 命令行参数优先级最高,运维调整最方便

实战场景:生产环境用jar外application-prod.yml覆盖默认配置,紧急调整用命令行参数--xxx临时覆盖,不改文件不重启。

面试加分点:SpringBoot 2.4+对spring.config.import引入了可选配置源(optional:file:...),配置文件不存在也不报错。

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

【标准答案】JDK动态代理:基于接口,目标类必须实现接口,通过Proxy.newProxyInstance生成代理对象,代理类和目标类实现相同接口。

CGLIB代理:基于继承,通过字节码技术生成目标类的子类,目标类不能是final类,目标方法不能是final方法。

SpringBoot 2.x默认使用CGLIB代理(spring.aop.proxy-target-class=true)。原因:

  1. 大部分场景类没实现接口,JDK代理用不了
  2. 统一代理方式减少踩坑(有些Bean有接口有些没有,混用容易出问题)
  3. SpringBoot 2.x起默认开启了proxy-target-class

什么时候用JDK代理?显式设置spring.aop.proxy-target-class=false,且目标类实现了接口。但不推荐,除非你有明确的接口隔离需求。

面试踩坑:说"Spring AOP默认用JDK代理"是旧版(Spring 5.x之前)的说法,SpringBoot 2.x默认是CGLIB。另外,同类内部方法调用不走代理(this调用绕过代理),这是AOP失效的经典坑。

【面试题】7. SpringBoot事件机制中,ContextRefreshedEvent在什么时候触发?有什么典型应用场景? ​

【标准答案】ContextRefreshedEvent在ApplicationContext刷新完成时触发,即所有Bean定义加载完毕、所有单例Bean初始化完成后发布。具体是在AbstractApplicationContext的finishRefresh()方法中触发的。

触发时机:所有BeanPostProcessor执行完毕、所有单例Bean的@PostConstruct和afterPropertiesSet()执行完毕之后。

典型应用场景:

  1. 应用启动后初始化:在所有Bean就绪后执行一些预加载操作,比如预热缓存、建立长连接、加载基础数据
  2. 检测重复Bean注册:在容器完全就绪后做一次校验
  3. 多上下文场景:SpringMVC中会有Root和Servlet两个上下文,ContextRefreshedEvent可能触发两次,需要用if(event.getApplicationContext().getParent() == null)过滤

注意事项:

  • 不要在监听器里做耗时操作,会阻塞启动
  • 如果需要异步处理,用@EventListener + @Async
  • ContextStartedEvent和ContextRefreshedEvent不同,Started需要显式调用start()才触发,Refreshed是自动的

【面试题】8. @Transactional的传播行为有哪些?实际开发中最常用哪几种? ​

【标准答案】7种传播行为:

  1. REQUIRED(默认):有事务就加入,没有就新建。最常用。
  2. SUPPORTS:有事务就加入,没有就以非事务执行。
  3. MANDATORY:必须在事务中调用,否则抛异常。
  4. REQUIRES_NEW:总是新建事务,挂起当前事务。用于独立提交的场景。
  5. NOT_SUPPORTED:以非事务执行,挂起当前事务。
  6. NEVER:以非事务执行,如果当前有事务则抛异常。
  7. NESTED:在当前事务中创建保存点,嵌套事务。外层回滚内层也回滚,内层回滚外层不受影响。

实际开发常用:

  • REQUIRED:90%场景用这个,方法间调用共享同一事务
  • REQUIRES_NEW:日志记录、消息发送等"不管外层成败都要独立提交"的场景
  • NESTED:部分业务允许独立回滚但不允许独立提交的场景

面试踩坑:

  1. 同类方法调用事务不生效(绕过代理)
  2. checked异常默认不回滚,要@Transactional(rollbackFor = Exception.class)
  3. REQUIRES_NEW内层事务提交后,外层回滚不会撤回内层已提交的数据

【面试题】9. SpringBoot Actuator的端点安全暴露策略是怎样的?如何自定义HealthIndicator? ​

【标准答案】端点安全策略:

  • 默认只暴露/actuator/health和/actuator/info
  • 其他端点需要显式配置暴露:management.endpoints.web.exposure.include=health,info,metrics,prometheus
  • 可以用*暴露所有:management.endpoints.web.exposure.include=*
  • 排除特定端点:management.endpoints.web.exposure.exclude=env,beans
  • 端点安全应配合Spring Security,对敏感端点(env、beans、configprops)做访问控制

自定义HealthIndicator步骤:

  1. 实现HealthIndicator接口,重写health()方法
  2. 返回Health.up().withDetail("key", "value").build()表示健康
  3. 返回Health.down().withDetail("error", "msg").build()表示不健康
  4. 类上加@Component,自动注册
java
@Component
public class RedisHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        try {
            // 检查Redis连接
            redisTemplate.ping();
            return Health.up().withDetail("redis", "连接正常").build();
        } catch (Exception e) {
            return Health.down().withDetail("redis", "连接失败: " + e.getMessage()).build();
        }
    }
}

访问/actuator/health时会自动聚合所有HealthIndicator的结果。

Prometheus集成:加micrometer-registry-prometheus依赖,暴露/actuator/prometheus端点,Grafana直接对接。

【面试题】10. SpringBoot如何实现优雅停机?底层原理是什么? ​

【标准答案】SpringBoot 2.3+原生支持优雅停机。配置方式:

yaml
server:
  shutdown: graceful  # 开启优雅停机
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 最大等待时间

底层原理:

  1. 收到SIGTERM信号后,SpringBoot不再接受新请求(Web容器停止接受新连接)
  2. 已在处理的请求继续执行,等待完成
  3. 超过timeout-per-shutdown-phase后强制关闭
  4. 触发ContextClosedEvent,执行所有@PreDestroy和DisposableBean的destroy()方法
  5. 关闭连接池、清理资源

关键源码:SmartLifecycle接口的stop()方法,WebServerGracefulShutdownLifecycle负责Web容器的优雅关闭,它先停止接受新请求,等待活跃请求处理完,再关闭容器。

Docker/K8s场景:

  • Docker的docker stop默认发SIGTERM,等10秒后SIGKILL
  • K8s的terminationGracePeriodSeconds要大于SpringBoot的shutdown timeout
  • 建议K8s配置:preStop hook加sleep 5,让Service摘除节点后再停机,避免请求打到正在关闭的实例

面试加分:老版本(2.3之前)没有原生支持,需要自己注册Runtime.getRuntime().addShutdownHook()或者实现SmartLifecycle接口手动处理。