Skip to content

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

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


【面试题】1. SpringBoot启动时自动配置是如何生效的?@EnableAutoConfiguration底层原理是什么? ​

【标准答案】 这个问题的核心是理解SpringBoot的自动配置机制。

@SpringBootApplication是一个组合注解,它里面包含了三个关键注解:

  • @SpringBootConfiguration:本质上就是@Configuration,标识这是一个配置类
  • @ComponentScan:开启组件扫描,默认扫描主类所在包及其子包
  • @EnableAutoConfiguration:这是自动配置的核心开关

@EnableAutoConfiguration的工作原理:

  1. 它通过@Import(AutoConfigurationImportSelector.class)导入了一个选择器
  2. AutoConfigurationImportSelector会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(SpringBoot 2.7+)或META-INF/spring.factories(旧版本)文件
  3. 这些文件里定义了大量的自动配置类全限定名
  4. 但不是所有配置类都会生效,每个配置类上有各种条件注解来决定是否加载

条件注解是关键:

  • @ConditionalOnClass:类路径下存在某个类时才生效
  • @ConditionalOnMissingBean:容器中没有某个Bean时才生效
  • @ConditionalOnProperty:配置属性满足条件时才生效
  • @ConditionalOnWebApplication:是Web应用时才生效

比如DataSourceAutoConfiguration上有@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }),只有类路径下有DataSource类才会尝试配置数据源。

面试加分点:你可以说"这也是为什么我们引入starter依赖后几乎不用配置就能用的原因——条件注解会根据类路径下的依赖自动判断是否需要配置,实现了约定优于配置的理念。"


【面试题】2. SpringBoot的配置文件加载顺序是怎样的?多环境Profile如何切换? ​

【标准答案】 配置文件加载顺序(优先级从高到低):

  1. 命令行参数(如--server.port=8081)
  2. Java系统属性(System.setProperty)
  3. 操作系统环境变量
  4. application-{profile}.yml(外部jar包同目录)
  5. application-{profile}.yml(jar包内部)
  6. application.yml(外部jar包同目录)
  7. application.yml(jar包内部classpath)
  8. @PropertySource注解指定的属性文件
  9. SpringApplication.setDefaultProperties设置的默认属性

Profile多环境切换的三种方式:

  1. 配置文件方式:创建application-dev.yml、application-prod.yml等,在application.yml中设置spring.profiles.active: dev
  2. 命令行参数:java -jar app.jar --spring.profiles.active=prod
  3. 环境变量:export SPRING_PROFILES_ACTIVE=prod

面试加分点:"实际项目中,我通常在配置文件中设置默认profile为dev,生产环境通过环境变量或启动参数覆盖。同时可以用spring.profiles.include来组合多个profile,比如dev环境需要同时激活dev和local两个profile的配置。"


【面试题】3. @ConfigurationProperties和@Value有什么区别?松散绑定是什么? ​

【标准答案】 两者区别:

特性@ConfigurationProperties@Value
绑定方式批量绑定属性到对象单个属性注入
松散绑定支持不支持
SpEL表达式不支持支持
JSR303校验支持(配合@Validated)不支持
复杂对象支持(Map、List、嵌套对象)不支持

松散绑定是@ConfigurationProperties的重要特性,指的是属性名的格式可以很灵活:

  • user.first-name、user.firstName、user.first_name、user.FIRST_NAME 都能绑定到 firstName 属性
  • SpringBoot会自动将这些格式转换为驼峰命名

使用示例:

java
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
    @NotBlank
    private String name;
    
    private List<String> servers = new ArrayList<>();
    
    private Map<String, String> mapping = new HashMap<>();
    
    // getters, setters
}

对应配置:

yaml
app:
  name: my-app
  servers:
    - server1
    - server2
  mapping:
    key1: value1
    key2: value2

面试加分点:"在项目中,我倾向于用@ConfigurationProperties来封装一组相关配置,特别是需要校验或配置项多的时候;@Value适合简单场景或需要SpEL表达式的情况。"


【面试题】4. Spring Bean的完整生命周期是怎样的?BeanPostProcessor和BeanFactoryPostProcessor有什么区别? ​

【标准答案】 Bean的完整生命周期(按执行顺序):

  1. 实例化:调用构造函数创建对象
  2. 属性赋值:依赖注入,填充属性
  3. Aware接口回调:BeanNameAware、BeanFactoryAware、ApplicationContextAware等
  4. BeanPostProcessor.postProcessBeforeInitialization():初始化前处理
  5. @PostConstruct注解方法:初始化方法
  6. InitializingBean.afterPropertiesSet():初始化接口
  7. 自定义init-method:XML或@Bean指定的初始化方法
  8. BeanPostProcessor.postProcessAfterInitialization():初始化后处理(AOP代理在这里生成)
  9. Bean就绪可用
  10. @PreDestroy注解方法:销毁前方法
  11. DisposableBean.destroy():销毁接口
  12. 自定义destroy-method:销毁方法

BeanPostProcessor vs BeanFactoryPostProcessor:

对比BeanPostProcessorBeanFactoryPostProcessor
作用时机Bean实例化后,初始化前后BeanDefinition加载后,Bean实例化前
处理对象Bean实例BeanDefinition
典型应用AOP代理生成、属性校验PropertyPlaceholderConfigurer、修改Bean定义
执行次数每个Bean都执行容器启动时执行一次

面试加分点:"经典应用场景:BeanPostProcessor的postProcessAfterInitialization方法返回AOP代理对象,这就是为什么@Async、@Transactional等注解能生效的原因。而BeanFactoryPostProcessor常用于解析配置文件中的占位符,比如${}表达式的处理。"


【面试题】5. @Import的三种使用方式是什么?如何实现按条件动态导入? ​

【标准答案】 @Import的三种使用方式:

  1. 直接导入配置类:
java
@Import(MyConfiguration.class)
public class AppConfig {
}

最简单直接的方式,将指定的配置类注册为Bean。

  1. 实现ImportSelector接口:
java
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // 根据条件动态返回要导入的类全限定名
        return new String[]{"com.example.ServiceA", "com.example.ServiceB"};
    }
}

@Import(MyImportSelector.class)
public class AppConfig {
}

这种方式可以根据注解元数据动态决定导入哪些类,SpringBoot的@EnableAutoConfiguration就是用这种方式实现的。

  1. 实现ImportBeanDefinitionRegistrar接口:
java
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                        BeanDefinitionRegistry registry) {
        // 手动注册BeanDefinition
        BeanDefinition bd = BeanDefinitionBuilder
            .rootBeanDefinition(MyService.class)
            .addPropertyValue("name", "custom")
            .getBeanDefinition();
        registry.registerBeanDefinition("myService", bd);
    }
}

@Import(MyRegistrar.class)
public class AppConfig {
}

这种方式最灵活,可以完全控制BeanDefinition的注册过程。

结合条件注解实现动态导入:

java
@Retention(RetentionPolicy.RUNTIME)
@Import(MyConditionalSelector.class)
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public @interface EnableFeature {
}

面试加分点:"自定义Starter开发中,我最常用的是ImportSelector方式,因为它既能动态决定导入哪些类,又保持了代码的整洁。比如可以读取某个配置,决定是否导入某个功能的配置类。"


【面试题】6. Spring AOP中@Around、@Before、@After等通知的执行顺序是怎样的?同一个切面或不同切面如何控制优先级? ​

【标准答案】 通知类型及执行顺序(正常执行流程):

  1. @Around.proceed()调用前
  2. @Before
  3. 目标方法执行
  4. @AfterReturning
  5. @After
  6. @Around.proceed()调用后

异常执行流程:

  1. @Around.proceed()调用前
  2. @Before
  3. 目标方法抛出异常
  4. @AfterThrowing
  5. @After

执行顺序规则:

  • @Around包围整个流程,必须调用proceed()才会执行目标方法
  • @After无论正常还是异常都会执行(类似finally)
  • @AfterReturning只在正常返回时执行
  • @AfterThrowing只在抛出异常时执行

同一切面内的顺序:按方法定义顺序执行。

不同切面的优先级控制:

java
@Aspect
@Order(1)  // 数字越小优先级越高
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() { }
}

@Aspect
@Order(2)
public class TransactionAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void transactionBefore() { }
}

优先级高的切面:

  • @Before通知先执行
  • @After/AfterReturning/AfterThrowing后执行(后进先出的栈结构)

面试加分点:"实际项目中,我一般把事务切面的@Order设为最低(数字最大),确保事务在最外层,这样其他切面的异常也能被事务捕获回滚。同时要注意,@Around会吞掉目标方法异常如果不主动抛出,这是常见的坑。"


【面试题】7. Spring事件机制的原理是什么?@EventListener和ApplicationListener有什么区别?如何实现异步事件? ​

【标准答案】 Spring事件机制原理:

  • 基于观察者模式,实现了发布-订阅模型
  • 核心组件:ApplicationEvent(事件对象)、ApplicationEventPublisher(发布者)、ApplicationListener(监听者)
  • 容器启动时会自动注册所有ApplicationListener实现类和@EventListener注解方法
  • 发布事件时,通过SimpleApplicationEventMulticaster广播给所有匹配的监听器

@EventListener vs ApplicationListener:

对比@EventListenerApplicationListener<E>
使用方式注解在方法上实现接口
灵活性可以监听多个事件,支持SpEL条件泛型指定单一事件类型
条件过滤支持condition属性需要代码判断
推荐场景大多数情况需要更精细控制时

示例对比:

java
// 方式一:ApplicationListener
@Component
public class MyListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        // 处理事件
    }
}

// 方式二:@EventListener
@Component
public class MyListener {
    @EventListener
    public void handleEvent(MyEvent event) {
        // 处理事件
    }
    
    @EventListener(condition = "#event.type == 'urgent'")
    public void handleUrgentEvent(MyEvent event) {
        // 只处理type=urgent的事件
    }
}

实现异步事件:

java
@Component
public class MyListener {
    @EventListener
    @Async  // 标记为异步
    public void handleAsync(MyEvent event) {
        // 在单独线程中执行
    }
}

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

内置核心事件:

  • ContextRefreshedEvent:容器刷新完成
  • ContextStartedEvent:容器启动
  • ContextStoppedEvent:容器停止
  • ContextClosedEvent:容器关闭
  • ApplicationReadyEvent:应用完全就绪

面试加分点:"异步事件要注意事务问题——@Async的事件监听器在独立线程执行,如果发布事件的方法有事务,监听器无法参与同一事务。如果需要事务传播,应该用同步事件或用@TransactionalEventListener。"


【面试题】8. SpringBoot嵌入式容器(Tomcat)是如何启动的?如何切换到Undertow或Jetty? ​

【标准答案】 嵌入式Tomcat启动原理:

  1. SpringApplication.run()启动
  2. 创建AnnotationConfigServletWebServerApplicationContext
  3. 刷新容器时调用onRefresh()方法
  4. 调用ServletWebServerFactory接口的getWebServer()方法
  5. TomcatServletWebServerFactory创建Tomcat实例
  6. 设置端口、上下文路径、注册Servlet(包括DispatcherServlet)
  7. 启动Tomcat(tomcat.start())
  8. 注册优雅停机的钩子

关键类:

  • ServletWebServerFactory:Web容器工厂接口
  • TomcatServletWebServerFactory:Tomcat工厂实现
  • JettyServletWebServerFactory:Jetty工厂实现
  • UndertowServletWebServerFactory:Undertow工厂实现

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

Undertow调优配置:

yaml
server:
  undertow:
    threads:
      io: 16          # IO线程数,默认CPU核心数
      worker: 256     # 工作线程数
    buffer-size: 1024  # 缓冲区大小
    direct-buffers: true  # 使用直接内存

为什么选Undertow:

  • 更低的内存占用
  • 更高的并发性能
  • 非阻塞IO架构

面试加分点:"在高并发场景下,我会推荐Undertow,它的非阻塞架构在IO密集型场景表现更好。但要注意,如果应用大量使用Servlet API(如WebSocket),需要确保兼容性。Tomcat仍是企业级应用的首选,生态最成熟。"


【面试题】9. Spring事务的传播行为有哪些?@Transactional在什么情况下会失效? ​

【标准答案】 事务传播行为(7种):

传播行为说明
REQUIRED(默认)有事务就加入,没有就新建
SUPPORTS有事务就加入,没有就非事务执行
MANDATORY必须在事务中调用,否则抛异常
REQUIRES_NEW总是新建事务,挂起当前事务
NOT_SUPPORTED非事务执行,挂起当前事务
NEVER非事务执行,有事务就抛异常
NESTED有事务就创建嵌套事务

常用场景:

  • REQUIRED:默认,适合大多数业务
  • REQUIRES_NEW:日志记录,主业务回滚不影响日志
  • NESTED:批量操作,部分失败不影响整体

@Transactional失效场景:

  1. 方法非public:事务代理只能拦截public方法
  2. 同类内部调用:绕过代理,事务不生效
    java
    @Service
    public class OrderService {
        public void placeOrder() {
            this.saveOrder();  // 内部调用,事务失效
        }
        
        @Transactional
        public void saveOrder() { }
    }
  3. 异常被catch捕获:异常未抛出,事务不回滚
  4. 抛出检查异常:默认只回滚RuntimeException和Error
    java
    @Transactional(rollbackFor = Exception.class)  // 需要指定
    public void method() throws IOException { }
  5. 数据库引擎不支持事务:如MySQL的MyISAM
  6. 事务传播行为设置不当:如SUPPORTS、NOT_SUPPORTED
  7. 类没被Spring管理:没有@Service/@Component注解

解决内部调用问题:

java
// 方式一:注入自己
@Service
public class OrderService {
    @Autowired
    private OrderService self;
    
    public void placeOrder() {
        self.saveOrder();  // 通过代理调用
    }
}

// 方式二:AopContext
@Transactional
public void placeOrder() {
    ((OrderService) AopContext.currentProxy()).saveOrder();
}

面试加分点:"实际项目中,最常见的问题是内部调用和异常被吞掉。我的经验是:事务方法尽量设计成public、独立、单一职责,避免在事务方法里try-catch业务异常;必须捕获时,手动设置TransactionAspectSupport.setRollbackOnly()。"


【面试题】10. 如何自定义一个SpringBoot Starter?需要哪些核心组件? ​

【标准答案】 自定义Starter的核心步骤:

1. 项目结构:

my-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

2. 配置属性类:

java
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    private boolean enabled = true;
    private String name;
    private int timeout = 3000;
    // getters, setters
}

3. 自动配置类:

java
@Configuration
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "my.service", name = "enabled", 
                       havingValue = "true", matchIfMissing = true)
public class MyAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService(MyProperties properties) {
        return new MyService(properties.getName(), properties.getTimeout());
    }
}

4. 注册自动配置(SpringBoot 2.7+):

# 文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration

5. 条件注解说明:

  • @ConditionalOnClass:类路径存在指定类
  • @ConditionalOnMissingBean:容器中不存在指定Bean
  • @ConditionalOnProperty:配置属性满足条件
  • @ConditionalOnWebApplication:Web应用环境
  • @ConditionalOnExpression:SpEL表达式条件

6. 提供默认配置(可选):

yaml
# src/main/resources/META-INF/additional-spring-configuration-metadata.json
{
  "properties": [
    {
      "name": "my.service.enabled",
      "type": "java.lang.Boolean",
      "description": "Enable my service.",
      "defaultValue": true
    }
  ]
}

面试加分点:"命名规范很重要,官方Starter命名是spring-boot-starter-xxx,我们自定义的应该是xxx-spring-boot-starter。还有,一定要提供@ConditionalOnMissingBean,让用户可以覆盖默认实现。另外,建议在META-INF下提供配置元数据,这样IDE会有自动提示,用户体验更好。"


本轮题目覆盖考点:

  • 自动配置原理(题1)
  • 配置管理与Profile(题2、3)
  • Bean生命周期与容器扩展(题4)
  • 注解驱动开发(题5)
  • AOP切面编程(题6)
  • 事件机制(题7)
  • Web开发与嵌入式容器(题8)
  • 事务管理(题9)
  • 自定义Starter开发(题10)