Skip to content

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

生成时间:2026-05-22 00:19


【面试题】1. SpringBoot自动配置的"约定优于配置"体现在哪些方面?举例说明@Conditional条件装配的典型应用场景。 ​

【标准答案】

SpringBoot的"约定优于配置"主要体现在三个方面:

一是默认值约定。比如嵌入式容器默认Tomcat、默认端口8080、默认数据库连接池HikariCP,这些都有合理默认值,开发者零配置即可启动。

二是自动装配约定。classpath下存在某类就自动配置对应Bean,比如有spring-webmvc就自动配置DispatcherServlet,有DataSource相关类就自动配置数据源。

三是结构约定。约定优于配置不等于不能配置,而是配置有优先级:命令行参数 > 系统属性 > 外部配置文件 > 默认值。

条件装配的典型应用场景:

条件注解典型应用
@ConditionalOnClassclasspath存在某类才装配,如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加载顺序是怎样的?相同属性在不同配置源如何覆盖? ​

【标准答案】

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

  1. 命令行参数:--server.port=8081
  2. Java系统属性:-Dserver.port=8081
  3. 操作系统环境变量:SERVER_PORT=8081
  4. application.yml/properties(jar包外):./config/application.yml > ./application.yml
  5. application.yml/properties(jar包内):classpath:/config/application.yml > classpath:/application.yml
  6. @PropertySource注解:只能用于@Configuration类,不能用于application配置
  7. 默认属性: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

关键步骤:

  1. 配置属性类:@ConfigurationProperties(prefix = "my") + @Validated实现属性绑定和校验

  2. 自动配置类:

java
@Configuration
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties props) {
        return new MyService(props);
    }
}
  1. 注册配置类:SpringBoot 2.7+在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports写入全限定类名(旧版用spring.factories)

命名规范:

场景命名规范示例
官方Starterspring-boot-starter-xxxspring-boot-starter-web
第三方Starterxxx-spring-boot-startermybatis-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

三种初始化回调优先级(从高到低):

  1. @PostConstruct:JSR-250标准注解,最先执行
  2. InitializingBean.afterPropertiesSet():Spring接口,第二执行
  3. init-method:XML/注解配置的方法,最后执行

代码验证:

java
@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 API
  • init-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,完全自定义注册逻辑

方式一:直接导入配置类

java
@Import({RedisConfig.class, DataSourceConfig.class})
@SpringBootApplication
public class Application { }

场景:模块化配置,避免@ComponentScan全包扫描性能损耗

方式二:ImportSelector动态选择

java
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手动注册

java
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的执行流程? ​

【标准答案】

多切面执行顺序控制:

  1. @Order注解:@Order(1)、@Order(2),数值越小优先级越高
  2. 实现Ordered接口:getOrder()返回值决定顺序
  3. 不指定顺序:无法保证执行顺序

执行顺序规则:

切面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目标方法后(无论成功异常)否否

代码示例:

java
@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异步事件:

特性同步事件异步事件
执行方式发布线程同步执行监听器线程池异步执行监听器
异常传播监听器异常会中断发布线程异常不影响发布线程
事务在发布者事务内执行独立事务
适用场景需要监听器结果、事务一致性解耦、耗时操作、通知类

实现方式:

java
// 同步监听器
@EventListener
public void onEvent(UserCreatedEvent event) { }

// 异步监听器
@Async
@EventListener
public void onEventAsync(UserCreatedEvent event) { }

// 启用异步支持
@EnableAsync
@SpringBootApplication
public class Application { }

@Async失效的常见原因:

  1. 未启用异步支持:缺少@EnableAsync注解
  2. 同类内部调用:@Async基于AOP代理,同类方法调用绕过代理
    java
    // 错误示例
    public void methodA() { this.methodB(); } // methodB的@Async失效
    
    // 正确做法:注入自身或使用AopContext
    @Autowired private ApplicationContext context;
    public void methodA() { context.getBean(getClass()).methodB(); }
  3. 方法非public:@Async要求方法是public
  4. 异步方法在private类中:代理无法访问
  5. 线程池配置问题:自定义线程池配置错误导致任务无法提交

自定义异步线程池:

java
@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接口上需要每个接口都加

推荐用法:

java
@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application { }

多数据源配置核心步骤:

java
// 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简化方案:

java
@Bean
@Primary
public MybatisPlusInterceptor primaryMybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件
    return interceptor;
}

【面试题】9. SpringBoot Actuator健康检查如何自定义?如何实现优雅停机? ​

【标准答案】

自定义HealthIndicator:

java
@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

端点暴露配置:

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

优雅停机实现:

yaml
# application.yml
server:
  shutdown: graceful  # 启用优雅停机

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 最多等待30秒处理完成请求

停机流程:

  1. 接收SIGTERM信号
  2. 停止接收新请求(如果使用负载均衡,先从注册中心下线)
  3. 等待已接收请求处理完成
  4. 超时后强制关闭

代码层面处理:

java
@Bean
public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
}

public class GracefulShutdown implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        // 清理资源、关闭连接池、保存状态
        log.info("应用正在优雅关闭...");
    }
}

Kubernetes环境:

  • 使用preStop hook确保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  # 启动器,创建自定义类加载器

启动流程:

  1. java -jar app.jar执行JarLauncher
  2. JarLauncher创建LaunchedURLClassLoader
  3. 自定义类加载器加载BOOT-INF/classes和BOOT-INF/lib/*.jar
  4. 反射调用Start-Class指定的Application.main()

分层JAR优化Docker构建:

传统Fat JAR问题:每次代码变更,整个JAR的Docker层都重建,即使依赖没变也要重新下载。

分层JAR原理:按变更频率分层打包

bash
# 生成分层JAR
java -Djarmode=layertools -jar app.jar list
# 输出:
# dependencies
# spring-boot-loader
# snapshot-dependencies
# application

Dockerfile优化:

dockerfile
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配置:

xml
<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以内。