本轮 SpringBoot2核心功能面试题库(带答案)
【面试题】1. SpringBoot中@Import注解有哪三种用法?分别适用于什么场景?
【标准答案】三种用法:
- 直接导入类:
@Import(TargetClass.class),直接把普通类注册为Bean,不需要加@Component等注解。适合导入第三方jar包里的类,因为你改不了人家源码加不了注解。 - 导入ImportSelector实现类:返回String数组,数组里是要注册的Bean的全类名。可以根据条件动态决定注册哪些Bean,SpringBoot自动配置大量用了这种方式——AutoConfigurationImportSelector就是典型。
- 导入ImportBeanDefinitionRegistrar实现类:直接操作BeanDefinitionRegistry手动注册BeanDefinition,是最底层最灵活的方式。MyBatis的@MapperScan就是用这种方式,因为需要扫描指定包下所有接口逐一注册。
面试关键点:三种方式从简到繁、从声明式到编程式,越往后灵活度越高但复杂度也越高。SpringBoot自动配置核心走的是ImportSelector,框架整合走的是Registrar。
【面试题】2. SpringBoot的@ConditionalOnProperty是怎么工作的?如果配置项不存在会怎样?
【标准答案】@ConditionalOnProperty通过匹配application配置文件中的属性值来决定是否装配Bean。核心属性有三个:
prefix/value:属性前缀或完整keyhavingValue:期望的属性值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需要哪些步骤?有哪些必须注意的点?
【标准答案】核心步骤:
- 创建两个模块:
xxx-spring-boot-autoconfigure(自动配置模块)和xxx-spring-boot-starter(依赖聚合模块,只做pom依赖引用) - 编写自动配置类:用@Configuration + 条件注解(@ConditionalOnClass、@ConditionalOnMissingBean等)声明Bean
- 编写属性类:@ConfigurationProperties(prefix = "xxx")绑定配置,@Validated加校验
- 注册自动配置:在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)或META-INF/spring.factories(2.7以下)中写入配置类全路径 - 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?
【标准答案】从高到低(高优先级覆盖低优先级):
- 命令行参数(
--server.port=8081) - JNDI属性
- Java系统属性(
System.getProperties(),-Dserver.port=8081) - 操作系统环境变量(
SERVER_PORT=8081) - jar包外application-{profile}.yml
- jar包内application-{profile}.yml
- jar包外application.yml
- jar包内application.yml
- @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)。原因:
- 大部分场景类没实现接口,JDK代理用不了
- 统一代理方式减少踩坑(有些Bean有接口有些没有,混用容易出问题)
- 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()执行完毕之后。
典型应用场景:
- 应用启动后初始化:在所有Bean就绪后执行一些预加载操作,比如预热缓存、建立长连接、加载基础数据
- 检测重复Bean注册:在容器完全就绪后做一次校验
- 多上下文场景:SpringMVC中会有Root和Servlet两个上下文,ContextRefreshedEvent可能触发两次,需要用
if(event.getApplicationContext().getParent() == null)过滤
注意事项:
- 不要在监听器里做耗时操作,会阻塞启动
- 如果需要异步处理,用@EventListener + @Async
- ContextStartedEvent和ContextRefreshedEvent不同,Started需要显式调用start()才触发,Refreshed是自动的
【面试题】8. @Transactional的传播行为有哪些?实际开发中最常用哪几种?
【标准答案】7种传播行为:
- REQUIRED(默认):有事务就加入,没有就新建。最常用。
- SUPPORTS:有事务就加入,没有就以非事务执行。
- MANDATORY:必须在事务中调用,否则抛异常。
- REQUIRES_NEW:总是新建事务,挂起当前事务。用于独立提交的场景。
- NOT_SUPPORTED:以非事务执行,挂起当前事务。
- NEVER:以非事务执行,如果当前有事务则抛异常。
- NESTED:在当前事务中创建保存点,嵌套事务。外层回滚内层也回滚,内层回滚外层不受影响。
实际开发常用:
- REQUIRED:90%场景用这个,方法间调用共享同一事务
- REQUIRES_NEW:日志记录、消息发送等"不管外层成败都要独立提交"的场景
- NESTED:部分业务允许独立回滚但不允许独立提交的场景
面试踩坑:
- 同类方法调用事务不生效(绕过代理)
- checked异常默认不回滚,要
@Transactional(rollbackFor = Exception.class) - 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步骤:
- 实现HealthIndicator接口,重写health()方法
- 返回Health.up().withDetail("key", "value").build()表示健康
- 返回Health.down().withDetail("error", "msg").build()表示不健康
- 类上加@Component,自动注册
@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+原生支持优雅停机。配置方式:
server:
shutdown: graceful # 开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最大等待时间底层原理:
- 收到SIGTERM信号后,SpringBoot不再接受新请求(Web容器停止接受新连接)
- 已在处理的请求继续执行,等待完成
- 超过timeout-per-shutdown-phase后强制关闭
- 触发ContextClosedEvent,执行所有@PreDestroy和DisposableBean的destroy()方法
- 关闭连接池、清理资源
关键源码: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接口手动处理。