Skip to content

SpringBoot2核心功能面试题库 ​

生成时间:2026-05-21 21:49


【面试题】1. SpringBoot自动配置原理是什么?@EnableAutoConfiguration如何工作? ​

【标准答案】 自动配置是SpringBoot的核心,原理有三步:

第一步:@SpringBootApplication是个组合注解,包含了@SpringBootConfiguration(就是个@Configuration)、@ComponentScan(包扫描)和@EnableAutoConfiguration(开启自动配置)。

第二步:@EnableAutoConfiguration通过@Import导入AutoConfigurationImportSelector,这个选择器会读取META-INF/spring.factories(SpringBoot 2.7之前)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7之后),加载所有自动配置类的全限定名。

第三步:条件注解过滤。每个自动配置类上都有一堆@ConditionalOnXxx注解,比如@ConditionalOnClass(类路径下有某个类才生效)、@ConditionalOnMissingBean(容器里没有才创建)、@ConditionalOnProperty(配置项满足条件才生效)。只有满足条件的配置类才会真正注册Bean。

总结: 自动配置 = 读取配置类 + 条件过滤 + 按需装配。这就解释了为什么引入starter就能自动配置好一切,因为starter引入了依赖(满足@ConditionalOnClass),自动配置类就会生效。


【面试题】2. SpringBoot的配置文件加载顺序和优先级覆盖规则是怎样的? ​

【标准答案】 SpringBoot的配置加载有个核心原则:外层配置覆盖内层配置,后加载覆盖先加载。

加载顺序(从高到低优先级):

  1. 命令行参数(--server.port=8081)
  2. JAR包外的application.properties/yml(config子目录 > 当前目录)
  3. JAR包内的application.properties/yml(config子目录 > 当前目录)
  4. @PropertySource注解指定的配置
  5. 默认属性(SpringApplication.setDefaultProperties)

具体到application.yml/properties:

  • ./config/application.yml(项目根目录的config文件夹,优先级最高)
  • ./application.yml(项目根目录)
  • classpath:/config/application.yml(resources/config下)
  • classpath:/application.yml(resources下,优先级最低)

Profile多环境: application-dev.yml、application-prod.yml会覆盖application.yml中的同名配置。激活方式:spring.profiles.active=dev。

记忆口诀: 外层盖内层,命令行最霸道,config目录比平级高,Profile最后叠加。


【面试题】3. Spring Bean的完整生命周期是怎样的?BeanPostProcessor在哪里介入? ​

【标准答案】 Bean生命周期分四个阶段,记成口诀:实例化 → 属性赋值 → 初始化 → 销毁。

详细流程:

  1. 实例化:调用构造方法创建对象实例
  2. 属性赋值:依赖注入(@Autowired、@Value)
  3. 初始化前:BeanPostProcessor.postProcessBeforeInitialization()
  4. 初始化:依次执行 @PostConstruct → InitializingBean.afterPropertiesSet() → 自定义init-method
  5. 初始化后:BeanPostProcessor.postProcessAfterInitialization()(AOP代理就是在这步生成的)
  6. 使用中:Bean就绪,可以调用
  7. 销毁前:@PreDestroy → DisposableBean.destroy() → 自定义destroy-method

关键点:

  • BeanPostProcessor是Spring留给开发者的扩展点,在初始化前后介入。最经典应用就是AOP:Spring会在postProcessAfterInitialization里把原始对象包装成代理对象。
  • Aware接口(如ApplicationContextAware)会在初始化前调用,让Bean拿到容器的引用。

面试加分: 如果问到BeanFactoryPostProcessor,它更早,在Bean实例化之前执行,可以修改BeanDefinition(比如PropertyPlaceholderConfigurer解析占位符)。


【面试题】4. @Import注解有哪三种使用方式?分别在什么场景下用? ​

【标准答案】 @Import有三种用法,都可以把类导入到Spring容器:

第一种:直接导入普通类

java
@Import(DataSourceConfig.class)

直接把DataSourceConfig注册为Bean,相当于@Bean的效果。适用场景:导入第三方类,无法加@Component等注解时。

第二种:导入ImportSelector实现类

java
@Import(MyImportSelector.class)
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata metadata) {
        return new String[]{"com.example.ConfigA", "com.example.ConfigB"};
    }
}

动态选择要导入的配置类全限定名。适用场景:条件装配,比如@EnableXXX注解背后就是用ImportSelector。

第三种:导入ImportBeanDefinitionRegistrar实现类

SpringBoot
@Import(MyRegistrar.class)
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 手动注册BeanDefinition
        BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(MyBean.class).getBeanDefinition();
        registry.registerBeanDefinition("myBean", bd);
    }
}

最灵活,可以手动注册BeanDefinition。适用场景:动态注册Bean,比如MyBatis的@MapperScan就是用这种方式批量注册Mapper。

对比: 第一种最简单,第二种可批量+动态选择,第三种最强大可完全控制注册过程。


【面试题】5. Spring AOP的代理方式是什么?JDK动态代理和CGLIB有什么区别? ​

【标准答案】 Spring AOP的代理方式选择有个明确规则:目标类实现了接口,默认用JDK动态代理;目标类没实现接口,用CGLIB代理。可以通过spring.aop.proxy-target-class=true强制全部使用CGLIB。

JDK动态代理:

  • 基于接口,只能代理接口方法
  • 通过反射生成代理类,代理类实现了目标接口
  • 性能:调用时需要反射,但生成代理快
  • 限制:被代理类必须实现至少一个接口

CGLIB代理:

  • 基于继承,代理类继承目标类
  • 通过字节码增强生成代理类的子类
  • 性能:调用快(直接调用),但生成代理慢
  • 限制:无法代理final类和final方法

SpringBoot 2.x后的变化: SpringBoot 2.x默认使用CGLIB,因为spring.aop.proxy-target-class默认为true。Spring 5.x后JDK动态代理性能优化,差距已不明显。

面试陷阱: @Transactional注解生效的关键——代理对象必须通过容器获取,自己new出来的对象不走代理,事务不会生效。类内部方法调用(this.method())也不会走代理,需要用AopContext.currentProxy()获取代理对象调用自己的方法。


【面试题】6. Spring事件机制是怎么工作的?有哪些核心内置事件? ​

【标准答案】 Spring事件机制是基于观察者模式的解耦方案,核心三要素:事件、监听器、发布者。

工作流程:

  1. 定义事件:继承ApplicationEvent
  2. 发布事件:通过ApplicationEventPublisher.publishEvent()
  3. 监听事件:实现ApplicationListener接口 或 使用@EventListener注解

两种监听方式:

java
// 方式一:实现接口
public class MyListener implements ApplicationListener<MyEvent> {
    public void onApplicationEvent(MyEvent event) { }
}

// 方式二:注解(推荐,更简洁)
@EventListener
public void handleMyEvent(MyEvent event) { }

// 异步事件:加@Async注解
@EventListener
@Async
public void handleAsync(MyEvent event) { }

核心内置事件(按执行顺序):

  1. ContextRefreshedEvent:容器刷新完成,所有Bean初始化完毕
  2. ContextStartedEvent:容器启动(调用ConfigurableApplicationContext.start())
  3. ContextClosedEvent:容器关闭
  4. ApplicationReadyEvent:SpringBoot应用完全就绪(比ContextRefreshedEvent更晚,actuator端点已可用)

应用场景:

  • ContextRefreshedEvent常用于应用启动后执行初始化逻辑(比如加载缓存)
  • 自定义事件用于业务解耦(比如订单创建后发送通知,不同模块监听同一事件各自处理)

【面试题】7. SpringBoot如何切换嵌入式容器?Tomcat、Undertow、Jetty有什么区别? ​

【标准答案】 SpringBoot默认使用Tomcat,切换方式很简单:排除Tomcat依赖,引入其他容器starter。

切换到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>

三大容器对比:

容器特点适用场景
Tomcat成熟稳定,生态最完善,资源占用中等传统企业应用,兼容性要求高
Undertow轻量高性能,非阻塞IO,内存占用低高并发、微服务、资源受限环境
Jetty轻量级,嵌入式友好,启动快长连接、WebSocket、快速启动场景

性能调优(以Tomcat为例):

yaml
server:
  tomcat:
    threads:
      max: 200  # 最大工作线程
      min-spare: 10  # 最小空闲线程
    max-connections: 10000  # 最大连接数
    accept-count: 100  # 等待队列长度
  port: 8080

面试建议: 选Tomcat求稳,选Undertow求性能。Undertow在高并发场景下内存占用更低,吞吐量更高。


【面试题】8. @Transactional事务注解在什么情况下会失效?如何避免? ​

【标准答案】 @Transactional失效的常见原因有七种,面试必问:

1. 方法不是public

  • Spring AOP只能代理public方法,private/protected/包级私有都不生效

2. 同类内部调用

  • this.method()不走代理,事务无效。解决:注入自己(@Autowired Self self)或用AopContext.currentProxy()

3. 异常被catch吞掉

  • 异常没抛出,事务管理器感知不到,无法回滚。解决:catch后手动抛RuntimeException或调用setRollbackOnly()

4. 异常类型不对

  • 默认只对RuntimeException和Error回滚,检查型异常(如IOException)不回滚。解决:@Transactional(rollbackFor = Exception.class)

5. 数据库引擎不支持事务

  • MySQL的MyISAM不支持事务,要用InnoDB

6. 多线程调用

  • 事务线程绑定(ThreadLocal),新线程无法共享事务上下文

7. 传播行为配置错误

  • @Transactional(propagation = Propagation.NOT_SUPPORTED)会以非事务方式运行

最佳实践:

  • 统一加 rollbackFor = Exception.class
  • 事务方法只做数据库操作,不要夹杂远程调用(RPC超时会导致数据库连接池耗尽)
  • 大事务拆小事务,避免长事务锁表

【面试题】9. Spring缓存抽象@Cacheable的原理是什么?如何集成Redis? ​

【标准答案】 Spring缓存抽象是基于AOP实现的,核心原理:拦截方法调用,先查缓存,命中直接返回;未命中执行方法,结果写入缓存。

@Cacheable执行流程:

  1. 生成缓存Key:key属性的SpEL表达式(默认是方法参数组合)
  2. 查缓存:用CacheManager.get(key)
  3. 命中:直接返回缓存值
  4. 未命中:执行方法,将返回值写入缓存(CacheManager.put)

集成Redis步骤:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml
spring:
  cache:
    type: redis
  redis:
    host: localhost
    port: 6379

常用注解:

java
@Cacheable(value = "user", key = "#id")  // 查询缓存
public User getUser(Long id) { }

@CachePut(value = "user", key = "#user.id")  // 更新缓存
public User updateUser(User user) { }

@CacheEvict(value = "user", key = "#id")  // 删除缓存
public void deleteUser(Long id) { }

@CacheEvict(value = "user", allEntries = true)  // 清空整个缓存空间
public void clearAllUsers() { }

注意事项:

  • 缓存的方法所在类必须是Spring Bean(才能被AOP代理)
  • 返回对象必须可序列化(Redis要求)
  • 设置过期时间:@Cacheable(value = "user", key = "#id", cacheManager = "redisCacheManager")

【面试题】10. SpringBoot Actuator有哪些核心端点?如何自定义HealthIndicator? ​

【标准答案】 Actuator是SpringBoot的生产级监控工具,提供了多个健康检查和指标端点。

核心端点:

端点作用默认暴露
/health应用健康状态(DB、Redis、磁盘等)是
/info应用信息(版本、名称)是
/metrics性能指标(内存、线程、HTTP请求)否
/env环境变量和配置属性否
/beans容器中的所有Bean否
/mappingsURL映射关系否
/threaddump线程转储否

自定义HealthIndicator:

java
@Component
public class MyServiceHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        // 自定义健康检查逻辑
        boolean isHealthy = checkMyService();
        if (isHealthy) {
            return Health.up()
                .withDetail("service", "my-service")
                .withDetail("status", "running")
                .build();
        } else {
            return Health.down()
                .withDetail("error", "Service unavailable")
                .build();
        }
    }
    
    private boolean checkMyService() {
        // 实际检查逻辑(如调用第三方服务、检查资源)
        return true;
    }
}

暴露配置:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics  # 暴露指定端点
        # include: "*"  # 暴露所有端点(生产环境不推荐)
  endpoint:
    health:
      show-details: always  # 显示详细健康信息

安全建议:

  • 生产环境必须加安全认证(Spring Security)
  • 敏感端点(/env、/beans)不建议暴露
  • 推荐用Prometheus + Grafana可视化监控:引入micrometer-registry-prometheus依赖,暴露/prometheus端点

本套面试题覆盖考点:

  • 自动配置原理(题目1)
  • 配置管理(题目2)
  • Bean生命周期(题目3)
  • 注解驱动开发(题目4)
  • AOP切面编程(题目5)
  • 事件机制(题目6)
  • Web开发-嵌入式容器(题目7)
  • 数据访问-事务管理(题目8)
  • 缓存抽象(题目9)
  • 安全与监控-Actuator(题目10)

题目类型分布: 原理深挖(5题)、场景实操(3题)、性能优化(1题)、面试情景问答(1题)