Skip to content

SpringBoot2核心功能面试题库 ​

生成时间:2026-05-21 21:41 | 题目数量:10


【面试题】1. SpringBoot自动配置的"条件化装配"底层是怎么实现的?@ConditionalOnClass在类路径不存在时如何做到不报错? ​

【标准答案】条件化装配的核心是Spring 4.0引入的@Conditional机制。SpringBoot在此基础上封装了一堆@ConditionalOnXxx注解,它们本质上都是@Conditional的元注解,每个都绑定了一个Condition实现类。

拿@ConditionalOnClass来说,它绑定的实现类是OnClassCondition。关键点在于——Spring在注册BeanDefinition之前,会先调用Condition.matches()方法做判断。OnClassCondition通过ClassLoader尝试加载目标类,如果类路径不存在,matches()返回false,这个BeanDefinition就不会被注册,自然也就不会进入实例化阶段,所以不会报ClassNotFoundException。

底层调用链:AutoConfigurationImportSelector → 读取spring.factories/AutoConfiguration.imports → 过滤出候选配置类 → 对每个配置类及其内部的@Bean方法逐一执行Condition评估 → 不满足的直接跳过。

面试加分点:可以提到ConditionEvaluator是执行入口,以及SpringBoot用了SpringBootCondition基类统一做日志输出(这就是你在启动日志里看到"XxxAutoConfiguration did not match"的来源)。


【面试题】2. @ConfigurationProperties和@Value都能读取配置,生产环境怎么选?有什么本质区别? ​

【标准答案】两者有三个本质区别:

第一,绑定机制不同。 @Value是SpEL逐个注入,一次只绑一个属性;@ConfigurationProperties是批量绑定,把整个前缀下的属性映射到一个POJO,支持松散绑定(my-prop/myProp/MY_PROP都能匹配)、嵌套对象、List/Map等复杂结构。@Value不支持松散绑定,属性名必须完全一致。

第二,校验能力不同。 @ConfigurationProperties配合@Validated可以用JSR-303注解做校验(@NotNull/@Size/@Min等),启动时校验失败直接报错,防止脏配置上线。@Value没有原生校验支持,只能自己写逻辑。

第三,刷新支持不同。 @ConfigurationProperties标注的Bean在Spring Cloud环境下配合@RefreshScope可以动态刷新;@Value的值在Bean创建时就确定了,默认不刷新。

选型建议: 属性少且简单(1-2个)用@Value方便;属性多、有层级结构、需要校验的,一律用@ConfigurationProperties。生产环境强烈推荐后者,维护性和安全性都更好。


【面试题】3. BeanFactory和ApplicationContext到底什么关系?为什么说ApplicationContext是BeanFactory的超集? ​

【标准答案】从继承体系看,ApplicationContext继承自BeanFactory,所以它本身就是个BeanFactory,但远不止于此。

BeanFactory是Spring最底层的IoC容器接口,只提供Bean的获取和基础管理功能(getBean、containsBean、isSingleton等),它是懒加载的——Bean在第一次getBean时才创建。

ApplicationContext在BeanFactory基础上扩展了五大能力:

  1. 国际化(MessageSource):支持i18n消息解析
  2. 资源访问(ResourceLoader):统一加载classpath、文件系统、URL资源
  3. 事件发布(ApplicationEventPublisher):内建事件机制
  4. AOP集成:自动注册BeanPostProcessor,开箱支持切面
  5. 环境抽象(Environment):Profile和PropertySource统一管理

另一个关键区别:ApplicationContext在启动时就会预实例化所有单例Bean(Eager Loading),而BeanFactory是Lazy Loading。

SpringBoot默认用的AnnotationConfigServletWebServerApplicationContext,它就是ApplicationContext的实现,启动时就把所有单例Bean准备好,这样能在启动阶段就发现配置问题,而不是等到运行时才暴露。


【面试题】4. @Import的三种用法分别是什么?自定义Starter时哪种最常用? ​

【标准答案】@Import有三种用法:

1. 直接导入配置类: @Import(XxxConfiguration.class),效果等同于在配置类上加@Configuration,被导入的类甚至不需要加@Configuration注解。Spring会识别并处理其中的@Bean方法。

2. 导入ImportSelector实现类: @Import(XxxSelector.class),ImportSelector的selectImports()方法返回String数组(全限定类名),Spring会把这些类当作配置类加载。好处是可以根据条件动态决定导入哪些类,SpringBoot的@EnableAutoConfiguration就是这个机制——通过AutoConfigurationImportSelector读取spring.factories返回所有自动配置类。

3. 导入ImportBeanDefinitionRegistrar实现类: @Import(XxxRegistrar.class),直接操作BeanDefinitionRegistry手动注册BeanDefinition,粒度最细,可以动态注册、条件注册、批量注册,不依赖@Bean方法。MyBatis的@MapperScan就是用这种方式通过MapperScannerRegistrar批量注册Mapper接口的BeanDefinition。

自定义Starter最常用的是第二种(ImportSelector),因为自动配置类通常有多个,而且需要条件化选择加载,用Selector返回类名数组最灵活。如果只是导入一两个固定配置类,第一种就够了。需要直接操作BeanDefinition的才用第三种。


【面试题】5. SpringBoot的AOP默认用JDK动态代理还是CGLIB?什么情况下会切换?多个切面执行顺序怎么控制? ​

【标准答案】SpringBoot 2.x默认使用CGLIB代理。这一点和Spring 5.x之前不一样——Spring框架默认是JDK代理(有接口用JDK,没接口用CGLIB),但SpringBoot 2.x把spring.aop.proxy-target-class默认值改成了true,强制走CGLIB。

切换方式:在application.yml中设置spring.aop.proxy-target-class=false,并且目标类实现了接口,就会用JDK动态代理。如果没实现接口,无论怎么设置都是CGLIB。

多个切面的执行顺序用@Order或@Priority控制,数值越小优先级越高。执行规则是"洋葱模型":优先级高的切面,Before通知先执行,After/Around的后半段后执行。比如两个切面Order分别为1和2,执行顺序是:A1-Before → A2-Before → 目标方法 → A2-After → A1-After。

面试加分: @Order只控制切面间的顺序,同一个切面内的通知类型顺序由Spring固定:Around → Before → 方法执行 → After → AfterReturning/AfterThrowing。Spring 5.2.7+修复了一个历史bug,现在同一个切面内Around和Before的执行顺序是确定的。


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

【标准答案】ContextRefreshedEvent在ApplicationContext刷新完成时触发,具体时机是:所有BeanDefinition注册完毕、所有单例Bean实例化完成、所有BeanPostProcessor执行完毕、所有Lifecycle组件start之后。调用栈是finishRefresh() → publishEvent(new ContextRefreshedEvent(this))。

一个关键细节: 如果是父子容器(比如Spring MVC的WebApplicationContext),ContextRefreshedEvent会触发两次——子容器刷新时触发一次,且事件中的ApplicationContext就是子容器本身。监听时要注意判断来源,避免重复执行。

典型应用场景:

  1. 启动后预热: 监听该事件,在容器完全就绪后执行缓存预热、连接池初始化等操作,确保所有Bean可用
  2. 服务注册: 向注册中心(Nacos/Eureka)注册服务实例,因为需要等所有Bean准备好
  3. 启动状态标记: 将应用标记为"已就绪",健康检查接口据此返回UP

编码方式: 两种——实现ApplicationListener<ContextRefreshedEvent>接口,或者用@EventListener注解标注方法。推荐后者,代码更简洁,且支持条件过滤和异步执行(配合@Async)。


【面试题】7. SpringBoot如何切换嵌入式容器?Tomcat、Undertow、Jetty各自适合什么场景? ​

【标准答案】切换方式很简单——排除默认的Tomcat依赖,引入目标容器的starter:

xml
<!-- 排除Tomcat -->
<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>
<!-- 引入Undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

底层原理:ServletWebServerApplicationContext启动时在classpath中查找ServletWebServerFactory实现类,哪个容器的jar在classpath就用哪个。EmbeddedTomcat/EmbeddedUndertow/EmbeddedJetty都是通过@ConditionalOnClass做条件判断的自动配置类。

场景选择:

  • Tomcat: 生态最成熟,文档最多,默认选择,适合大多数业务场景。缺点是内存占用偏高,高并发下线程模型不如Undertow
  • Undertow: 基于NIO,轻量高并发,内存占用小,适合I/O密集型微服务、高并发短连接场景。缺点是社区生态不如Tomcat
  • Jetty: 长连接支持好(WebSocket/HTTP长轮询),架构灵活可嵌入,适合聊天、推送类应用。缺点是性能略逊于Undertow

【面试题】8. @Transactional的传播行为PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW有什么区别?什么场景下必须用REQUIRES_NEW? ​

【标准答案】REQUIRED(默认): 当前有事务就加入,没有就新建。内外方法共享同一个事务,内部方法回滚会导致整个事务回滚。

REQUIRES_NEW: 无论当前有没有事务,都新建一个独立事务。新事务和原事务互不影响——新事务回滚不影响外层,外层回滚也不影响已提交的新事务。

关键区别在于事务边界: REQUIRED是同一个事务,一荣俱荣一损俱损;REQUIRES_NEW是两个独立事务,各自提交/回滚。

必须用REQUIRES_NEW的典型场景:

  1. 操作日志记录: 业务方法执行失败回滚了,但日志必须持久化不能丢。日志写入方法用REQUIRES_NEW,即使外层事务回滚,日志事务已独立提交
  2. 审计/积分: 用户操作失败,但积分变动记录需要保留
  3. 并行任务: 批量处理中某条记录失败不应影响其他记录,每条记录的处理方法独立事务

踩坑提醒: 同一个类内部方法调用,REQUIRES_NEW不生效!因为Spring AOP基于代理,内部调用走的是this而不是代理对象。解决方案:注入自身(@Lazy避免循环依赖)、用AopContext.currentProxy()、或拆到不同类。


【面试题】9. SpringBoot整合Redis缓存时,@Cacheable的key和cacheManager怎么配置才能避免key冲突和序列化问题? ​

【标准答案】Key冲突问题: 默认key生成策略是SimpleKeyGenerator,按方法参数拼接。如果不同方法有相同参数值,key会冲突。解决方案:

  1. 自定义key表达式:@Cacheable(key = "'user:' + #id"),用业务前缀区分不同缓存域
  2. 全局配置KeyGenerator:@Bean public KeyGenerator cacheKeyGenerator(),统一命名规范
  3. 用CacheName做第一层隔离:@Cacheable(cacheNames = "userCache"),Redis中key格式是cacheName::key

序列化问题: 默认用JdkSerializationRedisSerializer,存的是二进制,Redis客户端看不懂、跨语言不兼容、还有序列化漏洞风险。解决方案:

java
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .serializeKeysWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new StringRedisSerializer()))
        .serializeValuesWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()))
        .entryTtl(Duration.ofMinutes(30)); // 统一过期时间
    return RedisCacheManager.builder(factory)
        .cacheDefaults(config)
        .build();
}

生产环境推荐: Key用StringRedisSerializer,Value用GenericJackson2JsonRedisSerializer(会存@class类型信息,反序列化不丢类型)或Jackson2JsonRedisSerializer(需要手动指定类型)。同时一定要配置TTL,防止缓存永不过期导致内存溢出。


【面试题】10. SpringBoot Actuator的/health端点如何自定义?生产环境暴露策略怎么设计? ​

【标准答案】自定义HealthIndicator: 实现HealthIndicator接口或继承AbstractHealthIndicator:

java
@Component
public class MyServiceHealthIndicator extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        // 检查外部依赖(如Redis、MQ、第三方API)
        if (checkPass) {
            builder.up().withDetail("responseTime", "50ms");
        } else {
            builder.down().withDetail("error", "connection timeout");
        }
    }
}

/health端点会自动聚合所有HealthIndicator的结果,整体状态取最差的(任一DOWN则整体DOWN)。

生产环境暴露策略:

  1. 端点分类暴露:

    • 对外(Nginx/负载均衡):只暴露health和info,management.endpoints.web.exposure.include=health,info
    • 对内(运维/监控系统):暴露全部业务端点,通过网络隔离限制访问
    • 绝对不暴露:env、beans、configprops等敏感端点,或在暴露时加management.endpoint.env.show-values=never
  2. 安全加固:

    • 独立端口:management.server.port=8081,与业务端口分离,网络策略只允许监控系统访问
    • 开启认证:配合Spring Security,对Actuator端点加角色校验
    • 关闭HTTP:生产环境用JMX暴露而非HTTP
  3. Prometheus集成: 引入micrometer-registry-prometheus依赖,暴露/actuator/prometheus端点,Grafana通过Prometheus抓取指标做可视化。核心指标包括JVM内存、线程池、HTTP请求延迟、HikariCP连接数等。