SpringBoot基础
开篇:SpringBoot 解决了什么问题?
如果你用过"原始版"的 Spring 开发 Web 应用,一定对那种体验记忆犹新:在 pom.xml 里小心翼翼地管理几十个依赖的版本号,在 applicationContext.xml 里写上百行的 Bean 配置,部署时还得把 war 包丢到外部 Tomcat 里……改一个配置,重启一次,喝杯咖啡,回来还没好。
SpringBoot 的出现,就是为了终结这种"XML 地狱"。它的核心理念是 约定大于配置(Convention over Configuration) ——你不配置的东西,我帮你用合理的默认值搞定;你需要的依赖,一个 Starter 就全部引进来。
具体来说,SpringBoot 带来了三大改变:
- 依赖管理自动化:父项目帮你管理所有依赖的版本号,告别版本冲突
- 自动配置:根据你引入的 Starter 自动配置 Bean,不用手写配置文件
- 内嵌 Web 服务器:Tomcat 直接内嵌,
java -jar就能跑,不用外部部署
一句话总结:SpringBoot 让你专注于写业务代码,而不是和配置文件较劲。
一、自动装配原理
自动装配是 SpringBoot 最核心的能力。理解它,就理解了 SpringBoot 为什么"开箱即用"。
从 @SpringBootApplication 开始
每个 SpringBoot 应用都有一个启动类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}@SpringBootApplication 是一个组合注解,拆开来看由三部分组成:
前两个注解你一定很熟悉了。@SpringBootConfiguration 就是 @Configuration 的包装,说明启动类本身也是一个配置类。@ComponentScan 决定了包扫描的范围——默认是启动类所在包及其子包,所以启动类一般放在最顶层的包下面。
真正的魔法在第三个注解 @EnableAutoConfiguration。
@EnableAutoConfiguration 做了什么?
进入这个注解,你会发现它包含两个关键部分:
@AutoConfigurationPackage:批量导入启动类所在包下的所有组件@Import(AutoConfigurationImportSelector.class):加载所有候选的自动配置类
重点是第二个。AutoConfigurationImportSelector 的 selectImports() 方法会去 classpath 中寻找所有的自动配置类,加载流程如下:
在 getCandidateConfigurations() 方法中,Spring 使用 SpringFactoriesLoader.loadFactoryNames() 从所有 jar 包的 META-INF/spring.factories 文件中读取 EnableAutoConfiguration 对应的配置类列表。SpringBoot 的 spring-boot-autoconfigure 包里就注册了上百个配置类。
但重点来了——不是所有配置类都会生效。每个自动配置类上都标注了各种 @Conditional 注解,只有满足条件的才会真正注册 Bean。这就是"按需加载"的精髓。
@Conditional 条件装配
@Conditional 是 Spring 4 引入的条件化配置注解,SpringBoot 在此基础上扩展了大量的派生注解:
| 注解 | 含义 |
|---|---|
@ConditionalOnClass | classpath 中存在指定类时生效 |
@ConditionalOnMissingClass | classpath 中不存在指定类时生效 |
@ConditionalOnBean | 容器中已有指定 Bean 时生效 |
@ConditionalOnMissingBean | 容器中没有指定 Bean 时才生效 |
@ConditionalOnProperty | 配置文件中存在指定属性且值匹配时生效 |
@ConditionalOnWebApplication | 是 Web 应用时生效 |
举个例子,@ConditionalOnBean(name = "tom") 加在 user01 方法上,意味着只有容器中有名为 tom 的 Bean 时,user01 才会被注册。如果标注在类上,整个类中的所有 Bean 都受影响。
一个典型的自动配置类
看看 DataSourceAutoConfiguration 的简化版:
@Configuration
@ConditionalOnClass(DataSource.class) // 有数据库驱动才生效
@EnableConfigurationProperties(DataSourceProperties.class) // 绑定配置文件
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户没自定义才创建
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}这就是自动配置的精髓:有条件地、有默认值地帮你创建 Bean。如果你自己在配置类中定义了一个 DataSource Bean,@ConditionalOnMissingBean 会感知到并退让——这就是"用户自定义优先"的原则。
配置绑定:xxxProperties 类
自动配置类通常会绑定一个 xxxProperties 类,把配置文件中的值映射到 Java 对象上:
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
// getter / setter
}这样你在 application.yml 中写的配置就和 Bean 的创建参数关联起来了:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456你也可以在自己的项目中使用这个特性。给你的 Bean 加上 @ConfigurationProperties(prefix = "mycar"),再配合 @Component 或 @EnableConfigurationProperties,就能把配置文件的值自动注入进来。
总结:自动配置的四步链条
@EnableAutoConfiguration触发自动配置扫描- 从
spring.factories或.imports文件加载所有候选配置类 - 每个配置类通过
@Conditional注解判断是否应该生效 - 生效的配置类注册 Bean,属性值从
xxxProperties(配置文件)中读取
spring.factories 为什么被替换了?
SpringBoot 2.7 开始推荐用 AutoConfiguration.imports 文件替代 spring.factories,3.0 中正式移除了对后者的支持。
原因和云原生有关。spring.factories 依赖运行时扫描和反射来加载配置类,而新方式配合 @AutoConfiguration 注解,可以使用 Java 的编译期注解处理器(APT)在编译阶段就确定配置类列表。这对 GraalVM 原生镜像的 AOT 编译至关重要——能带来更快的启动速度和更低的内存占用。
二、Starter 机制
Starter 是什么?
你引入 spring-boot-starter-web,Web 开发需要的 Tomcat、SpringMVC、Jackson 等十几个依赖就全部引进来了。这就是 Starter 的作用——它是某个场景的依赖集合,利用了 Maven 的依赖传递原则。
所有的 Starter 底层都依赖一个核心包 spring-boot-starter,里面包含自动配置的基础设施(spring-boot-autoconfigure 等)。
命名规范:
- 官方 Starter:
spring-boot-starter-{场景名},如spring-boot-starter-data-redis - 自定义 Starter:
{场景名}-spring-boot-starter,如xxl-job-spring-boot-starter
依赖管理:版本自动仲裁
SpringBoot 项目的 pom.xml 中一般会继承一个父项目:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
</parent>这个父项目的父项目 spring-boot-dependencies 里声明了几乎所有常见依赖的版本号,这就是版本自动仲裁机制。引入依赖时你不需要写版本号,父项目帮你管好了。
如果需要覆盖某个依赖的版本,可以在自己的 pom.xml 中用 properties 标签指定,利用 Maven 的就近优先原则。
自定义 Starter 实战
以自定义一个 xxl-job 的 Starter 为例:
第一步:定义配置属性类
@ConfigurationProperties(prefix = "spring.xxl.job")
public class XxlJobProperties {
private boolean enabled;
private String adminAddresses;
private String appName;
private int port;
private String accessToken;
private String logPath;
private int logRetentionDays = 30;
// getter / setter
}第二步:编写自动配置类
@Configuration
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.xxl.job", value = "enabled", havingValue = "true")
public XxlJobSpringExecutor xxlJobExecutor(XxlJobProperties properties) {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses(properties.getAdminAddresses());
executor.setAppname(properties.getAppName());
executor.setPort(properties.getPort());
executor.setAccessToken(properties.getAccessToken());
executor.setLogPath(properties.getLogPath());
executor.setLogRetentionDays(properties.getLogRetentionDays());
return executor;
}
}@ConditionalOnProperty 约定了只有配置了 spring.xxl.job.enabled=true 时才生效。@ConditionalOnMissingBean 保证了用户可以自定义覆盖。
第三步:注册配置入口
在 src/main/resources/META-INF/spring/ 下创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:
cn.example.job.config.XxlJobConfiguration使用方只需要引入你的 Starter 依赖,在 application.yml 中配上属性就能用了。
三、配置文件
yml vs properties
SpringBoot 支持两种配置文件格式。功能完全等价,选择哪种纯粹是个人偏好:
# application.properties — 扁平键值对
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/test# application.yml — 层级结构,更直观
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/testyml 格式在配置项很多时层次更清晰,推荐使用。需要注意的是,yml 中冒号后面必须有空格。如果两个文件同时存在,properties 的优先级更高。
多环境配置(Profile)
开发环境、测试环境、生产环境的配置往往不同。SpringBoot 通过 Profile 机制来管理:
# application.yml — 公共配置
spring:
profiles:
active: dev # 激活 dev 环境然后为每个环境创建独立的配置文件:
application-dev.yml:开发环境配置application-test.yml:测试环境配置application-prod.yml:生产环境配置
也可以在代码中用 @Profile 注解来控制某些 Bean 只在特定环境下创建:
@Configuration
@Profile("dev")
public class DevConfig {
// 只在 dev 环境下生效的配置
}激活方式有多种优先级从低到高:
- 配置文件中指定:
spring.profiles.active=prod - JVM 参数:
-Dspring.profiles.active=prod - 环境变量:
SPRING_PROFILES_ACTIVE=prod - 命令行参数:
--spring.profiles.active=prod
配置优先级
SpringBoot 的配置可以从多个来源加载,优先级从高到低:
- 命令行参数(
--server.port=9090) - JVM 系统属性(
-Dserver.port=9090) - 环境变量(
SERVER_PORT=9090) application-{profile}.yml(特定环境配置)application.yml(通用配置)
高优先级的配性会覆盖低优先级的同名配置。这种设计让你可以在不修改配置文件的情况下,通过环境变量或命令行参数灵活调整应用行为。
四、常用注解速查
配置类注解
| 注解 | 作用 |
|---|---|
@Configuration | 标记类为配置类,替代 XML 配置文件 |
@Bean | 在配置类中注册 Bean,默认方法名为 Bean ID |
@ConfigurationProperties | 将配置文件属性批量绑定到 Java 对象 |
@EnableConfigurationProperties | 启用配置属性绑定,并注册到容器 |
@Value("${key}") | 注入单个配置值 |
@Import | 直接导入组件类到容器 |
@ImportResource | 导入传统 Spring XML 配置文件 |
@Configuration 有一个重要属性 proxyBeanMethods:
true(默认,Full 模式):配置类被代理,@Bean方法多次调用返回同一个单例实例。适用于组件之间有依赖关系的场景。false(Lite 模式):不代理,每次调用创建新实例,但容器启动更快。适用于组件之间无依赖的场景。
@Value vs @ConfigurationProperties
两者都能从配置文件读取值,但适用场景不同:
// @Value:适合读取少量、分散的配置
@Value("${app.name}")
private String appName;
// @ConfigurationProperties:适合读取一组相关的配置
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private int maxRetry;
// getter / setter
}@ConfigurationProperties 支持松散绑定(max-retry 可以匹配 maxRetry)、JSR303 校验(@Validated)和复杂类型绑定(List、Map、嵌套对象)。在需要读取多个相关属性时,优先选择它。
五、Spring Boot 与 Spring 的区别
这是面试中经常被问到的基础题。一句话总结:Spring 是基础框架,SpringBoot 是让你用起来更爽的脚手架。
| 对比维度 | Spring | SpringBoot |
|---|---|---|
| 配置方式 | XML + 注解,手动配置 | 自动配置 + 约定优先 |
| 依赖管理 | 手动管理版本号 | 父项目统一管理,版本自动仲裁 |
| Web 服务器 | 需要外部 Tomcat/Jetty | 内嵌 Tomcat/Jetty/Undertow |
| 启动方式 | 打 war 包部署到容器 | java -jar 直接运行 |
| 上手难度 | 较高,需要理解大量配置 | 极低,开箱即用 |
SpringBoot 没有引入新的编程模型,它只是让 Spring 的使用变得更简单、更高效。
六、SpringBoot 版本演进
Spring Boot 3.0(2022)
- Java 17 最低要求:彻底抛弃 Java 8,拥抱新特性
- Jakarta EE 替代 Java EE:
javax.*包名全部变成jakarta.* - GraalVM 原生镜像:从实验特性升级为正式支持,AOT 编译使启动时间降到毫秒级
- spring.factories 废弃:改用
.imports文件注册自动配置类
Spring Boot 4.0(2025)
- RestTemplate 弃用:推荐
RestClient(同步)或WebClient(响应式) - 声明式 HTTP 客户端:
@HttpExchange替代 Feign,成为官方推荐方案 - Jackson 3 集成:包名从
com.fasterxml.jackson改为tools.jackson - 内置韧性能力:
@Retryable和@ConcurrencyLimit成为框架内置注解,不再需要额外依赖 - 虚拟线程深度支持:一行配置
spring.threads.virtual.enabled=true即可启用,@Async默认使用虚拟线程 - API 版本控制:
@GetMapping(version = "2")优雅管理多版本接口 - JSpecify 空安全:全面采用
@Nullable/@NonNull注解,解决 Java 空值注解混乱问题
SpringBoot 的类加载机制
SpringBoot 打破了传统的双亲委派模型。传统的 AppClassLoader 无法加载嵌套 JAR(JAR 包里面的 JAR 包),而 SpringBoot 的 Fat JAR 恰恰就是这种结构。
所以 SpringBoot 使用自定义的 LaunchedURLClassLoader,加载顺序变成了:
- 先加载
BOOT-INF/classes下的应用类 - 再加载
BOOT-INF/lib下的依赖 JAR - 最后才交给父类加载器(JDK 的 AppClassLoader)
这种"先自己加载、找不到再委托父类"的做法,正是对双亲委派的打破。
七、常见面试题精选
1. SpringBoot 是如何实现自动配置的?
通过 @EnableAutoConfiguration 触发,读取 spring.factories 或 .imports 文件中注册的候选配置类,再用 @Conditional 条件注解按需加载。最终生效的配置类会绑定 xxxProperties,从配置文件中读取值来创建 Bean。
2. 如何自定义一个 Starter?
三步走:定义 @ConfigurationProperties 属性类、编写 @Configuration 配置类(使用 @ConditionalOnMissingBean 等条件注解)、在 META-INF/spring/ 下创建 .imports 注册文件。
3. SpringBoot 和 Spring 的区别?
SpringBoot 不是新框架,而是 Spring 的脚手架。它通过自动配置、Starter 依赖管理和内嵌 Web 服务器,大幅简化了 Spring 应用的开发和部署流程。
4. 如何让你的 Bean 在其他 Bean 之前加载?
三种方式:(1) 直接 @Autowired 依赖注入——被依赖的 Bean 必然先初始化;(2) @DependsOn("beanB") 注解显式指定依赖顺序;(3) 实现 BeanFactoryPostProcessor,在工厂后处理阶段提前获取 Bean,触发其初始化。注意 @Order 只控制同类型 Bean 集合中的排序位置,不控制初始化先后顺序。
5. 如何实现多环境配置?
使用 Profile 机制。创建 application-{profile}.yml 配置文件,通过 spring.profiles.active 属性激活。也可以用 @Profile 注解控制特定 Bean 只在某个环境下注册。激活方式支持配置文件、JVM 参数、环境变量和命令行参数。
小结
SpringBoot 的核心价值可以用一句话概括:用约定和自动化替代手动配置,让开发者专注于业务。
理解自动装配原理是掌握 SpringBoot 的关键——从 @EnableAutoConfiguration 出发,经过 spring.factories 加载候选类,通过 @Conditional 按需过滤,最终绑定 xxxProperties 完成 Bean 的自动创建。这条链路串起来,SpringBoot 的"魔法"就不再神秘了。
掌握了 Starter 机制和配置文件体系,你不仅能用好别人写的 Starter,还能为团队封装自己的基础设施组件,真正发挥 SpringBoot 的威力。
附录:深入理解自动配置的源码链路
如果你想亲自跟一遍自动配置的源码,这里给出完整的调用链路,方便你打断点调试。
selectImports 入口
// AutoConfigurationImportSelector.java
public String[] selectImports(AnnotationMetadata annotationMetadata) {
AutoConfigurationEntry entry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(entry.getConfigurations());
}获取候选配置类
// getAutoConfigurationEntry() 方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata metadata) {
// 1. 获取所有候选配置类
List<String> configurations = getCandidateConfigurations(metadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 获取需要排除的配置类
Set<String> exclusions = getExclusions(metadata, attributes);
// 4. 移除排除项
configurations.removeAll(exclusions);
// 5. 过滤(@Conditional 条件)
configurations = getConfigurationClassFilter().filter(configurations);
// 6. 返回
return new AutoConfigurationEntry(configurations, exclusions);
}从 spring.factories 加载
// getCandidateConfigurations() 最终调用
// SpringFactoriesLoader.loadFactoryNames()
// 扫描所有 jar 包中的 META-INF/spring.factories
// 读取 key 为 EnableAutoConfiguration 的值列表以 spring-boot-autoconfigure 包为例,它的 spring.factories 中注册了上百个自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...虽然一股脑加载了这么多候选类,但实际生效的只是满足 @Conditional 条件的那一小部分。你可以在启动时设置 debug=true 来查看哪些配置类被激活、哪些被排除,这对排查自动配置问题非常有帮助。
Web 场景中的静态资源配置
以 WebMvcAutoConfiguration 为例,看看自动配置是如何设置默认行为的。
这个配置类通过 @EnableConfigurationProperties 绑定了两个属性类:
WebMvcProperties:对应配置前缀spring.mvcWebProperties.Resources:对应配置前缀spring.web.resources
然后在 addResourceHandlers() 方法中设置了静态资源的默认规则:
// 简化后的逻辑
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 如果禁用了静态资源映射,直接返回
if (!this.resourceProperties.isAddMappings()) {
return;
}
// webjars 的访问规则
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
// 默认静态资源路径(/**)
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
);
}所以你不用配置任何东西,把前端文件放到 src/main/resources/static 下就能直接访问——这就是自动配置在幕后做的事情。
如果你想改变默认行为,比如禁用静态资源或修改映射路径,只需要在配置文件中设置:
spring:
web:
resources:
add-mappings: false # 禁用静态资源映射
mvc:
static-path-pattern: /res/** # 修改访问前缀SpringMVC 流式输出
在现代应用中(尤其是大模型对话场景),流式输出越来越常见。SpringMVC 提供了三种方式:
方式一:StreamingResponseBody(同步,简单场景)
@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> stream() {
StreamingResponseBody body = out -> {
for (int i = 0; i < 10; i++) {
out.write(("chunk " + i + "\n").getBytes());
out.flush();
Thread.sleep(500);
}
};
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(body);
}方式二:SseEmitter(异步,支持 SSE 协议)
@GetMapping("/sse")
public SseEmitter sse() {
SseEmitter emitter = new SseEmitter(60_000L);
Executors.newSingleThreadExecutor().submit(() -> {
for (int i = 0; i < 10; i++) {
emitter.send("Message " + i);
Thread.sleep(1000);
}
emitter.complete();
});
return emitter;
}方式三:Flux(响应式,推荐)
@GetMapping("/flux")
public Flux<String> flux() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> "Element " + seq);
}Flux 是最简洁的方式,异步非阻塞,适合高并发场景。但需要引入 WebFlux 依赖。