本轮 SpringBoot2核心功能面试题库(带答案)
生成时间:2026-05-22 00:19
【面试题】1. SpringBoot自动配置的"约定优于配置"体现在哪些方面?举例说明@Conditional条件装配的典型应用场景。
【标准答案】
SpringBoot的"约定优于配置"主要体现在三个方面:
一是默认值约定。比如嵌入式容器默认Tomcat、默认端口8080、默认数据库连接池HikariCP,这些都有合理默认值,开发者零配置即可启动。
二是自动装配约定。classpath下存在某类就自动配置对应Bean,比如有spring-webmvc就自动配置DispatcherServlet,有DataSource相关类就自动配置数据源。
三是结构约定。约定优于配置不等于不能配置,而是配置有优先级:命令行参数 > 系统属性 > 外部配置文件 > 默认值。
条件装配的典型应用场景:
| 条件注解 | 典型应用 |
|---|---|
@ConditionalOnClass | classpath存在某类才装配,如DataSourceAutoConfiguration条件是DataSource类存在 |
@ConditionalOnMissingBean | 容器无此Bean才装配,方便用户自定义覆盖默认配置 |
@ConditionalOnProperty | 配置属性满足条件才装配,如@ConditionalOnProperty(name="cache.type", havingValue="redis") |
@ConditionalOnWebApplication | 只有Web应用才装配,如WebMvcAutoConfiguration |
实际案例:RedisAutoConfiguration中,@ConditionalOnMissingBean确保用户自定义RedisTemplate覆盖默认配置;@ConditionalOnClass(RedisOperations.class)确保只有引入Redis依赖才生效。这种设计既保证开箱即用,又支持灵活扩展。
【面试题】2. application.yml和application.properties加载顺序是怎样的?相同属性在不同配置源如何覆盖?
【标准答案】
加载顺序(优先级从高到低):
- 命令行参数:
--server.port=8081 - Java系统属性:
-Dserver.port=8081 - 操作系统环境变量:
SERVER_PORT=8081 - application.yml/properties(jar包外):
./config/application.yml>./application.yml - application.yml/properties(jar包内):
classpath:/config/application.yml>classpath:/application.yml - @PropertySource注解:只能用于@Configuration类,不能用于application配置
- 默认属性:SpringApplication.setDefaultProperties()
yml与properties并存时:同一目录下同时存在application.yml和application.properties,properties优先级高于yml(SpringBoot 2.4+统一加载逻辑后),但实际开发中强烈建议只用一种。
覆盖规则核心:位置相同看优先级,属性不同则合并,属性相同高优先级覆盖低优先级。
实际场景:
- 开发环境用application-dev.yml,生产环境用application-prod.yml
- 打包后jar外放application.yml覆盖jar内默认值,实现不重新打包改配置
- 敏感配置用环境变量(数据库密码),避免写入配置文件
注意:SpringBoot 2.4+引入spring.config.import机制,配置源导入顺序可精确控制,spring.config.activate.on-profile替代旧版spring.profiles避免混淆。
【面试题】3. 自定义Starter的开发流程和命名规范是什么? Starter内部如何实现条件装配?
【标准答案】
开发流程四步走:
my-spring-boot-starter/
├── pom.xml (artifactId命名规范: xxx-spring-boot-starter)
├── src/main/java/
│ └── com/example/
│ ├── MyAutoConfiguration.java # 自动配置类
│ ├── MyProperties.java # 配置属性类
│ └── MyService.java # 核心功能类
└── src/main/resources/
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports关键步骤:
配置属性类:
@ConfigurationProperties(prefix = "my")+@Validated实现属性绑定和校验自动配置类:
@Configuration
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties props) {
return new MyService(props);
}
}- 注册配置类:SpringBoot 2.7+在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports写入全限定类名(旧版用spring.factories)
命名规范:
| 场景 | 命名规范 | 示例 |
|---|---|---|
| 官方Starter | spring-boot-starter-xxx | spring-boot-starter-web |
| 第三方Starter | xxx-spring-boot-starter | mybatis-spring-boot-starter |
条件装配策略:
@ConditionalOnClass:依赖类存在才生效@ConditionalOnMissingBean:允许用户覆盖默认实现@ConditionalOnProperty:配置开关控制是否启用@AutoConfigureBefore/After:控制配置类加载顺序,解决依赖关系
生产建议:自定义Starter配置类加@ConditionalOnProperty(name="my.enabled", havingValue="true", matchIfMissing=true)提供开关,默认启用但用户可关闭。
【面试题】4. SpringBoot的Bean生命周期回调有哪些?执行顺序是怎样的?@PostConstruct、InitializingBean、init-method三者的优先级?
【标准答案】
Bean生命周期完整流程:
实例化 → 属性注入 → BeanNameAware → BeanFactoryAware →
ApplicationContextAware → @PostConstruct → InitializingBean.afterPropertiesSet() →
init-method → BeanPostProcessor.postProcessBeforeInitialization →
[Bean就绪] → BeanPostProcessor.postProcessAfterInitialization →
[销毁阶段] → @PreDestroy → DisposableBean.destroy() → destroy-method三种初始化回调优先级(从高到低):
- @PostConstruct:JSR-250标准注解,最先执行
- InitializingBean.afterPropertiesSet():Spring接口,第二执行
- init-method:XML/注解配置的方法,最后执行
代码验证:
@Component
public class MyBean implements InitializingBean {
@PostConstruct
public void postConstruct() { System.out.println("1. @PostConstruct"); }
@Override
public void afterPropertiesSet() { System.out.println("2. InitializingBean"); }
@Bean(initMethod = "initMethod")
public void initMethod() { System.out.println("3. init-method"); }
}实际应用建议:
@PostConstruct:代码简洁,推荐使用,依赖注入完成后立即初始化InitializingBean:需要访问BeanFactory时使用,耦合Spring APIinit-method:遗留XML配置迁移场景,或方法名需要外部配置时
销毁回调同理:@PreDestroy → DisposableBean.destroy() → destroy-method
关键点:BeanPostProcessor的postProcessBeforeInitialization在@PostConstruct之前,postProcessAfterInitialization在init-method之后,这是AOP代理创建的时机点。
【面试题】5. @Import三种使用方式分别是什么?各自的应用场景?
【标准答案】
@Import三种用法:
| 方式 | 语法 | 应用场景 |
|---|---|---|
| 直接导入配置类 | @Import(MyConfig.class) | 导入@Configuration类,替代@ComponentScan精确控制 |
| ImportSelector接口 | @Import(MySelector.class) | 根据条件动态选择导入哪些配置类 |
| ImportBeanDefinitionRegistrar | @Import(MyRegistrar.class) | 手动注册BeanDefinition,完全自定义注册逻辑 |
方式一:直接导入配置类
@Import({RedisConfig.class, DataSourceConfig.class})
@SpringBootApplication
public class Application { }场景:模块化配置,避免@ComponentScan全包扫描性能损耗
方式二:ImportSelector动态选择
public class CacheSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableCache.class.getName());
String type = (String) attrs.get("type");
return "redis".equals(type)
? new String[]{RedisCacheConfig.class.getName()}
: new String[]{LocalCacheConfig.class.getName()};
}
}场景:@EnableXXX注解根据属性值选择不同实现,如@EnableCaching、@EnableAsync
方式三:ImportBeanDefinitionRegistrar手动注册
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
RootBeanDefinition bd = new RootBeanDefinition(MyService.class);
bd.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
registry.registerBeanDefinition("myService", bd);
}
}场景:MyBatis的MapperScannerRegistrar扫描接口并注册为Bean、动态代理Bean注册
实际开发选择:
- 简单场景用方式一
- 需要条件判断用方式二
- 需要完全控制Bean定义用方式三
SpringBoot的@EnableAutoConfiguration就是ImportSelector的经典实现,通过AutoConfigurationImportSelector从spring.factories动态加载配置类。
【面试题】6. AOP切面编程中,同一个连接点有多个切面时执行顺序如何控制?Around、Before、After、AfterReturning、AfterThrowing的执行流程?
【标准答案】
多切面执行顺序控制:
- @Order注解:
@Order(1)、@Order(2),数值越小优先级越高 - 实现Ordered接口:
getOrder()返回值决定顺序 - 不指定顺序:无法保证执行顺序
执行顺序规则:
切面A (@Order(1)) → 切面B (@Order(2))
正常执行流程:
A.@Around前 → A.@Before → B.@Around前 → B.@Before → [目标方法] →
B.@AfterReturning → B.@After → B.@Around后 → A.@AfterReturning → A.@After → A.@Around后
异常执行流程:
A.@Around前 → A.@Before → B.@Around前 → B.@Before → [目标方法抛异常] →
B.@AfterThrowing → B.@After → A.@AfterThrowing → A.@After五种通知执行时机:
| 通知类型 | 执行时机 | 能否获取返回值 | 能否阻止目标方法 |
|---|---|---|---|
| @Before | 目标方法前 | 否 | 否 |
| @Around | 完全包围目标方法 | 是(通过ProceedingJoinPoint.proceed()) | 是(不调用proceed) |
| @AfterReturning | 目标方法正常返回后 | 是 | 否 |
| @AfterThrowing | 目标方法抛异常后 | 否(获取异常对象) | 否 |
| @After | 目标方法后(无论成功异常) | 否 | 否 |
代码示例:
@Aspect
@Component
@Order(1)
public class LogAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 必须调用,否则目标方法不执行
long cost = System.currentTimeMillis() - start;
log.info("方法{}执行耗时{}ms", pjp.getSignature(), cost);
return result; // 可修改返回值
}
}生产建议:
- Around最强大,但谨慎使用,忘记proceed()会导致方法不执行
- 业务日志用@Before/@AfterReturning,性能监控用@Around
- 多切面协作时明确@Order,避免顺序混乱
【面试题】7. Spring事件机制中,同步事件和异步事件的区别?@Async注解失效的常见原因?
【标准答案】
同步vs异步事件:
| 特性 | 同步事件 | 异步事件 |
|---|---|---|
| 执行方式 | 发布线程同步执行监听器 | 线程池异步执行监听器 |
| 异常传播 | 监听器异常会中断发布线程 | 异常不影响发布线程 |
| 事务 | 在发布者事务内执行 | 独立事务 |
| 适用场景 | 需要监听器结果、事务一致性 | 解耦、耗时操作、通知类 |
实现方式:
// 同步监听器
@EventListener
public void onEvent(UserCreatedEvent event) { }
// 异步监听器
@Async
@EventListener
public void onEventAsync(UserCreatedEvent event) { }
// 启用异步支持
@EnableAsync
@SpringBootApplication
public class Application { }@Async失效的常见原因:
- 未启用异步支持:缺少
@EnableAsync注解 - 同类内部调用:@Async基于AOP代理,同类方法调用绕过代理java
// 错误示例 public void methodA() { this.methodB(); } // methodB的@Async失效 // 正确做法:注入自身或使用AopContext @Autowired private ApplicationContext context; public void methodA() { context.getBean(getClass()).methodB(); } - 方法非public:@Async要求方法是public
- 异步方法在private类中:代理无法访问
- 线程池配置问题:自定义线程池配置错误导致任务无法提交
自定义异步线程池:
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
@Async("asyncExecutor") // 指定线程池
public void asyncMethod() { }事务边界注意:异步监听器不在发布者事务内,如需事务需在监听器方法上加@Transactional开启新事务。
【面试题】8. SpringBoot集成MyBatis-Plus时,@MapperScan和@Mapper有什么区别?多数据源如何配置?
【标准答案】
@MapperScan vs @Mapper:
| 注解 | 作用范围 | 使用位置 | 优缺点 |
|---|---|---|---|
| @MapperScan("com.example.mapper") | 批量扫描包下所有接口 | 启动类或配置类 | 推荐,减少重复代码 |
| @Mapper | 单个接口 | Mapper接口上 | 需要每个接口都加 |
推荐用法:
@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application { }多数据源配置核心步骤:
// 1. 配置文件定义两个数据源
spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1
spring.datasource.primary.username=root
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2
spring.datasource.secondary.username=root
// 2. 数据源配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
@Primary // 主数据源标记
public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); }
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); }
}
// 3. MyBatis配置 - 主数据源
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(DataSource primaryDataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(primaryDataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factory.getObject();
}
}
// 4. MyBatis配置 - 从数据源(类似结构,去掉@Primary)关键点:
@Primary指定默认数据源,注入DataSource时不指定名称用默认值- 不同数据源的Mapper接口分开放不同包
- 事务管理器需要为每个数据源单独配置:
@Transactional(transactionManager = "primaryTransactionManager")
MyBatis-Plus简化方案:
@Bean
@Primary
public MybatisPlusInterceptor primaryMybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
return interceptor;
}【面试题】9. SpringBoot Actuator健康检查如何自定义?如何实现优雅停机?
【标准答案】
自定义HealthIndicator:
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 自定义检查逻辑
boolean dbConnected = checkDatabase();
boolean redisConnected = checkRedis();
if (dbConnected && redisConnected) {
return Health.up()
.withDetail("database", "MySQL")
.withDetail("redis", "connected")
.build();
} else {
return Health.down()
.withDetail("database", dbConnected ? "up" : "down")
.withDetail("redis", redisConnected ? "up" : "down")
.build();
}
}
}健康状态类型:UP、DOWN、OUT_OF_SERVICE、UNKNOWN
端点暴露配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus # 暴露指定端点
# include: "*" # 暴露所有端点(生产环境谨慎)
endpoint:
health:
show-details: always # 显示详细健康信息优雅停机实现:
# application.yml
server:
shutdown: graceful # 启用优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最多等待30秒处理完成请求停机流程:
- 接收SIGTERM信号
- 停止接收新请求(如果使用负载均衡,先从注册中心下线)
- 等待已接收请求处理完成
- 超时后强制关闭
代码层面处理:
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
public class GracefulShutdown implements DisposableBean {
@Override
public void destroy() throws Exception {
// 清理资源、关闭连接池、保存状态
log.info("应用正在优雅关闭...");
}
}Kubernetes环境:
- 使用
preStophook确保Pod下线前完成请求处理 - 结合Readiness探针避免流量打到正在关闭的实例
Actuator端点安全:
- 生产环境务必加安全控制(Spring Security)
- 敏感端点(/env、/heapdump)限制访问
- 使用management.server.port指定独立端口,内网访问
【面试题】10. SpringBoot打包成可执行JAR的原理是什么?分层JAR如何优化Docker镜像构建?
【标准答案】
可执行JAR原理:
SpringBoot使用spring-boot-maven-plugin打包成Fat JAR(又称Uber JAR),结构如下:
app.jar
├── META-INF/
│ └── MANIFEST.MF # Start-Class: com.example.Application
├── BOOT-INF/
│ ├── classes/ # 应用代码
│ └── lib/ # 依赖JAR包
└── org/springframework/boot/loader/
└── JarLauncher.class # 启动器,创建自定义类加载器启动流程:
java -jar app.jar执行JarLauncher- JarLauncher创建
LaunchedURLClassLoader - 自定义类加载器加载BOOT-INF/classes和BOOT-INF/lib/*.jar
- 反射调用
Start-Class指定的Application.main()
分层JAR优化Docker构建:
传统Fat JAR问题:每次代码变更,整个JAR的Docker层都重建,即使依赖没变也要重新下载。
分层JAR原理:按变更频率分层打包
# 生成分层JAR
java -Djarmode=layertools -jar app.jar list
# 输出:
# dependencies
# spring-boot-loader
# snapshot-dependencies
# applicationDockerfile优化:
FROM eclipse-temurin:17-jre as builder
WORKDIR /builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:17-jre
WORKDIR /app
# 按层级COPY,变更频率低的先复制
COPY --from=builder /builder/dependencies/ ./
COPY --from=builder /builder/spring-boot-loader/ ./
COPY --from=builder /builder/snapshot-dependencies/ ./
COPY --from=builder /builder/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]优化效果:
- dependencies层:变更频率最低,依赖不变则Docker缓存命中
- application层:代码频繁变更,每次重建
- CI/CD中构建时间从分钟级降到秒级
Maven配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>生产实践:结合多阶段构建、Alpine基础镜像、JVM参数调优(-XX:+UseContainerSupport),镜像体积可控制在150MB以内。