本轮 SpringBoot2核心功能面试题库(带答案)
生成时间:2026-05-22 00:12
【面试题】1. SpringBoot启动时自动配置是如何生效的?@EnableAutoConfiguration底层原理是什么?
【标准答案】 这个问题的核心是理解SpringBoot的自动配置机制。
@SpringBootApplication是一个组合注解,它里面包含了三个关键注解:
@SpringBootConfiguration:本质上就是@Configuration,标识这是一个配置类@ComponentScan:开启组件扫描,默认扫描主类所在包及其子包@EnableAutoConfiguration:这是自动配置的核心开关
@EnableAutoConfiguration的工作原理:
- 它通过
@Import(AutoConfigurationImportSelector.class)导入了一个选择器 AutoConfigurationImportSelector会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(SpringBoot 2.7+)或META-INF/spring.factories(旧版本)文件- 这些文件里定义了大量的自动配置类全限定名
- 但不是所有配置类都会生效,每个配置类上有各种条件注解来决定是否加载
条件注解是关键:
@ConditionalOnClass:类路径下存在某个类时才生效@ConditionalOnMissingBean:容器中没有某个Bean时才生效@ConditionalOnProperty:配置属性满足条件时才生效@ConditionalOnWebApplication:是Web应用时才生效
比如DataSourceAutoConfiguration上有@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }),只有类路径下有DataSource类才会尝试配置数据源。
面试加分点:你可以说"这也是为什么我们引入starter依赖后几乎不用配置就能用的原因——条件注解会根据类路径下的依赖自动判断是否需要配置,实现了约定优于配置的理念。"
【面试题】2. SpringBoot的配置文件加载顺序是怎样的?多环境Profile如何切换?
【标准答案】 配置文件加载顺序(优先级从高到低):
- 命令行参数(如
--server.port=8081) - Java系统属性(
System.setProperty) - 操作系统环境变量
application-{profile}.yml(外部jar包同目录)application-{profile}.yml(jar包内部)application.yml(外部jar包同目录)application.yml(jar包内部classpath)@PropertySource注解指定的属性文件SpringApplication.setDefaultProperties设置的默认属性
Profile多环境切换的三种方式:
- 配置文件方式:创建
application-dev.yml、application-prod.yml等,在application.yml中设置spring.profiles.active: dev - 命令行参数:
java -jar app.jar --spring.profiles.active=prod - 环境变量:
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会自动将这些格式转换为驼峰命名
使用示例:
@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
}对应配置:
app:
name: my-app
servers:
- server1
- server2
mapping:
key1: value1
key2: value2面试加分点:"在项目中,我倾向于用@ConfigurationProperties来封装一组相关配置,特别是需要校验或配置项多的时候;@Value适合简单场景或需要SpEL表达式的情况。"
【面试题】4. Spring Bean的完整生命周期是怎样的?BeanPostProcessor和BeanFactoryPostProcessor有什么区别?
【标准答案】 Bean的完整生命周期(按执行顺序):
- 实例化:调用构造函数创建对象
- 属性赋值:依赖注入,填充属性
- Aware接口回调:
BeanNameAware、BeanFactoryAware、ApplicationContextAware等 - BeanPostProcessor.postProcessBeforeInitialization():初始化前处理
- @PostConstruct注解方法:初始化方法
- InitializingBean.afterPropertiesSet():初始化接口
- 自定义init-method:XML或@Bean指定的初始化方法
- BeanPostProcessor.postProcessAfterInitialization():初始化后处理(AOP代理在这里生成)
- Bean就绪可用
- @PreDestroy注解方法:销毁前方法
- DisposableBean.destroy():销毁接口
- 自定义destroy-method:销毁方法
BeanPostProcessor vs BeanFactoryPostProcessor:
| 对比 | BeanPostProcessor | BeanFactoryPostProcessor |
|---|---|---|
| 作用时机 | Bean实例化后,初始化前后 | BeanDefinition加载后,Bean实例化前 |
| 处理对象 | Bean实例 | BeanDefinition |
| 典型应用 | AOP代理生成、属性校验 | PropertyPlaceholderConfigurer、修改Bean定义 |
| 执行次数 | 每个Bean都执行 | 容器启动时执行一次 |
面试加分点:"经典应用场景:BeanPostProcessor的postProcessAfterInitialization方法返回AOP代理对象,这就是为什么@Async、@Transactional等注解能生效的原因。而BeanFactoryPostProcessor常用于解析配置文件中的占位符,比如${}表达式的处理。"
【面试题】5. @Import的三种使用方式是什么?如何实现按条件动态导入?
【标准答案】 @Import的三种使用方式:
- 直接导入配置类:
@Import(MyConfiguration.class)
public class AppConfig {
}最简单直接的方式,将指定的配置类注册为Bean。
- 实现ImportSelector接口:
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就是用这种方式实现的。
- 实现ImportBeanDefinitionRegistrar接口:
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的注册过程。
结合条件注解实现动态导入:
@Retention(RetentionPolicy.RUNTIME)
@Import(MyConditionalSelector.class)
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public @interface EnableFeature {
}面试加分点:"自定义Starter开发中,我最常用的是ImportSelector方式,因为它既能动态决定导入哪些类,又保持了代码的整洁。比如可以读取某个配置,决定是否导入某个功能的配置类。"
【面试题】6. Spring AOP中@Around、@Before、@After等通知的执行顺序是怎样的?同一个切面或不同切面如何控制优先级?
【标准答案】 通知类型及执行顺序(正常执行流程):
@Around.proceed()调用前@Before- 目标方法执行
@AfterReturning@After@Around.proceed()调用后
异常执行流程:
@Around.proceed()调用前@Before- 目标方法抛出异常
@AfterThrowing@After
执行顺序规则:
@Around包围整个流程,必须调用proceed()才会执行目标方法@After无论正常还是异常都会执行(类似finally)@AfterReturning只在正常返回时执行@AfterThrowing只在抛出异常时执行
同一切面内的顺序:按方法定义顺序执行。
不同切面的优先级控制:
@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:
| 对比 | @EventListener | ApplicationListener<E> |
|---|---|---|
| 使用方式 | 注解在方法上 | 实现接口 |
| 灵活性 | 可以监听多个事件,支持SpEL条件 | 泛型指定单一事件类型 |
| 条件过滤 | 支持condition属性 | 需要代码判断 |
| 推荐场景 | 大多数情况 | 需要更精细控制时 |
示例对比:
// 方式一: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的事件
}
}实现异步事件:
@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启动原理:
- SpringApplication.run()启动
- 创建
AnnotationConfigServletWebServerApplicationContext - 刷新容器时调用
onRefresh()方法 - 调用
ServletWebServerFactory接口的getWebServer()方法 TomcatServletWebServerFactory创建Tomcat实例- 设置端口、上下文路径、注册Servlet(包括DispatcherServlet)
- 启动Tomcat(
tomcat.start()) - 注册优雅停机的钩子
关键类:
ServletWebServerFactory:Web容器工厂接口TomcatServletWebServerFactory:Tomcat工厂实现JettyServletWebServerFactory:Jetty工厂实现UndertowServletWebServerFactory:Undertow工厂实现
切换到Undertow:
<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调优配置:
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失效场景:
- 方法非public:事务代理只能拦截public方法
- 同类内部调用:绕过代理,事务不生效java
@Service public class OrderService { public void placeOrder() { this.saveOrder(); // 内部调用,事务失效 } @Transactional public void saveOrder() { } } - 异常被catch捕获:异常未抛出,事务不回滚
- 抛出检查异常:默认只回滚RuntimeException和Errorjava
@Transactional(rollbackFor = Exception.class) // 需要指定 public void method() throws IOException { } - 数据库引擎不支持事务:如MySQL的MyISAM
- 事务传播行为设置不当:如SUPPORTS、NOT_SUPPORTED
- 类没被Spring管理:没有@Service/@Component注解
解决内部调用问题:
// 方式一:注入自己
@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.imports2. 配置属性类:
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
private boolean enabled = true;
private String name;
private int timeout = 3000;
// getters, setters
}3. 自动配置类:
@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.MyAutoConfiguration5. 条件注解说明:
@ConditionalOnClass:类路径存在指定类@ConditionalOnMissingBean:容器中不存在指定Bean@ConditionalOnProperty:配置属性满足条件@ConditionalOnWebApplication:Web应用环境@ConditionalOnExpression:SpEL表达式条件
6. 提供默认配置(可选):
# 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)