从0开始学习Java+AI知识点总结-26.web实战(Springboot原理)
一、SpringBoot 配置优先级:彻底解决配置冲突问题
在 SpringBoot 项目中,配置方式不止一种,当多个配置同时存在时,优先级决定了最终生效的配置。掌握这部分,能帮你快速定位配置不生效的问题。
1.1 三种核心配置文件及优先级
SpringBoot 支持 3 种格式的配置文件,用于定义项目属性(如 Tomcat 端口、数据库连接等):
配置文件类型 | 格式示例(配置 Tomcat 端口) | 特点 |
application.properties | server.port=8081 | 键值对格式,兼容性强,适合简单配置 |
application.yml | server:<br> port: 8082 | 缩进式语法,结构清晰,支持复杂配置(如数组、对象) |
application.yaml | 与 yml 语法完全一致,仅文件名后缀不同 | 同 yml,使用频率较低 |
优先级测试结论(从高到低):
- application.properties > 2. application.yml > 3. application.yaml
实战小贴士:项目中建议统一使用一种格式(推荐 yml,语法简洁且支持复杂结构),避免多格式混用导致的配置冲突。
1.2 外部配置:Java 系统属性与命令行参数
除了配置文件,SpringBoot 还支持通过外部参数动态修改配置,优先级更高,适合线上环境灵活调整。
(1)Java 系统属性(VM Options)
- 格式:-Dkey=value
- 示例:配置端口为 9000 → -Dserver.port=9000
- 配置方式(IDEA 中):
编辑启动配置 → Modify options → Add VM options → 填入参数。
(2)命令行参数(Program Arguments)
- 格式:--key=value
- 示例:配置端口为 10010 → --server.port=10010
- 配置方式(IDEA 中):
编辑启动配置 → Program arguments 输入框填入参数。
优先级测试结论(外部配置 > 配置文件):
命令行参数(--key=value)> Java 系统属性(-Dkey=value)> application.properties > application.yml > application.yaml
1.3 线上环境:打包后如何配置参数?
项目打包为 JAR 后,可通过命令行指定外部参数,灵活调整配置(无需修改代码):
# 同时指定Java系统属性和命令行参数(最终端口10010,命令行优先级更高) java -Dserver.port=9000 -jar your-project.jar --server.port=10010 # 仅指定Java系统属性(端口9000) java -Dserver.port=9000 -jar your-project.jar |
注意:打包 SpringBoot 项目时,需确保 pom.xml 中引入spring-boot-maven-plugin插件(官网骨架创建的项目会自动添加),否则无法正常运行 JAR 包。
二、Spring Bean 管理:从作用域到第三方 Bean
Spring 的核心是 IOC 容器,Bean 是容器中的核心组件。掌握 Bean 的作用域配置和第三方 Bean 管理,是写出优雅代码的关键。
2.1 Bean 的作用域:单例还是多例?
Spring 容器中 Bean 默认是单例(整个容器中只有一个实例),但根据业务场景需要,可通过@Scope注解修改作用域。
5 种作用域详解(后 3 种仅 Web 环境生效)
作用域 | 说明 | 适用场景 |
singleton | 容器内同名称 Bean 仅 1 个实例(默认) | 无状态组件(如 Service、Dao) |
prototype | 每次获取 Bean 时创建新实例 | 有状态组件(如 Request 级别的 Bean) |
request | 每个 HTTP 请求创建 1 个实例,请求结束后销毁(Web 环境) | 存储请求专属数据 |
session | 每个用户会话创建 1 个实例,会话结束后销毁(Web 环境) | 存储用户会话数据(如登录信息) |
application | 整个 Web 应用生命周期内仅 1 个实例(Web 环境,区别于 singleton:前者是 Web 应用级,后者是容器级) | 存储应用全局配置 |
如何配置作用域?
通过@Scope注解指定作用域,示例:
// 配置为多例(prototype) @Scope("prototype") @RestController @RequestMapping("/users") public class UserController { // 构造方法(用于测试实例创建次数) public UserController() { System.out.println("UserController实例创建..."); } } |
关键测试结论:
- singleton(默认):容器启动时创建实例,后续获取均为同一对象(可通过@Lazy注解延迟到第一次使用时创建);
- prototype:每次调用getBean()或注入时,都会创建新实例(构造方法多次执行)。
实战小贴士:90% 以上的业务场景用单例(singleton),因为单例无需频繁创建对象,性能更高;仅当 Bean 有状态(如存储请求参数)时,才用 prototype。
2.2 第三方 Bean 管理:如何集成外部工具类?
自定义的类(如 Controller、Service)可通过@Component及其衍生注解(@Controller、@Service、@Repository)交给 Spring 管理,但第三方依赖中的类(如阿里云 OSS 工具类、Redis 客户端)无法添加这些注解,此时需用@Bean注解。
2.2.1 @Bean 注解的使用场景
当需要管理的 Bean 来自第三方依赖(如AliyunOSSOperator、RedisTemplate)时,使用@Bean注解将其手动注册到 IOC 容器。
2.2.2 两种配置方式(推荐第二种)
方式 1:在启动类中配置(不推荐,职责不清晰)
@SpringBootApplication public class SpringBootApp { // 将AliyunOSSOperator注册为Bean,默认Bean名称为方法名(aliyunOSSOperator) @Bean public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProps) { // 若第三方Bean依赖其他Bean(如配置类),直接在方法参数中声明,Spring会自动注入 return new AliyunOSSOperator(ossProps); } public static void main(String[] args) { SpringApplication.run(SpringBootApp.class, args); } } |
方式 2:在配置类中集中配置(推荐,便于维护)
创建专门的配置类(如OSSConfig、RedisConfig),集中管理第三方 Bean,职责更清晰:
// @Configuration标识当前类是配置类 @Configuration public class OSSConfig { // 配置AliyunOSSOperator的Bean @Bean public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProps) { return new AliyunOSSOperator(ossProps); } // 可在此添加更多第三方Bean配置(如OSS相关的其他工具类) } |
2.2.3 @Bean 的关键细节
- Bean 名称自定义:通过@Bean(name = "customOSS")指定 Bean 名称,若不指定,默认名称为方法名;
- 依赖注入:若第三方 Bean 依赖其他 Bean(如配置属性类AliyunOSSProperties),直接在@Bean方法参数中声明,Spring 会根据类型自动注入;
- 优先级:若同一类型的 Bean 有多个,可通过@Primary注解指定默认注入的 Bean。
核心总结:自定义类用@Component及其衍生注解,第三方类用@Bean;@Bean建议放在专门的配置类中,便于后期维护。
三、SpringBoot 底层原理:起步依赖与自动配置
SpringBoot 之所以 “开箱即用”,核心在于起步依赖和自动配置。掌握这两个原理,不仅能应对面试,还能解决复杂的框架集成问题。
3.1 为什么需要 SpringBoot?(Spring 的痛点)
在 SpringBoot 出现前,直接使用 Spring 框架开发存在两大痛点:
- 依赖配置繁琐:需手动引入 Web、数据库、JSON 等依赖,还要解决版本冲突(如 Spring 与 Tomcat 版本不兼容);
- 框架配置复杂:需编写大量 XML 配置(如扫描包、声明 Bean、配置事务),入门成本高。
SpringBoot 的出现解决了这些问题,核心是起步依赖(简化依赖配置)和自动配置(简化框架配置)。
3.2 起步依赖:Maven 依赖传递的魔法
3.2.1 什么是起步依赖?
SpringBoot 提供的spring-boot-starter-xxx系列依赖,本质是 “依赖的集合”。引入一个起步依赖,Maven 会通过依赖传递,自动引入该场景下所需的所有依赖。
3.2.2 示例:spring-boot-starter-web
当开发 Web 项目时,只需引入以下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.2.8</version> <!-- 版本可根据SpringBoot父工程自动继承 --> </dependency> |
Maven 会自动传递引入以下核心依赖:
- Web 框架:Spring Web、Spring WebMVC;
- 服务器:Tomcat(嵌入式,无需单独部署);
- JSON 处理:Jackson(自动实现 JSON 序列化 / 反序列化);
- 日志:Logback(默认日志框架)。
3.2.3 起步依赖的核心原理
Maven 依赖传递:起步依赖本身不包含代码,仅通过pom.xml声明了该场景所需的依赖,Maven 会自动解析并下载这些依赖到项目中。
常用起步依赖推荐:
- spring-boot-starter-web:Web 开发;
- spring-boot-starter-jdbc:数据库连接;
- spring-boot-starter-data-redis:Redis 集成;
- spring-boot-starter-test:单元测试。
3.3 自动配置:SpringBoot 的 “智能” 所在
3.3.1 什么是自动配置?
SpringBoot 启动时,会自动将符合条件的配置类、Bean 注册到 IOC 容器,无需手动配置。例如:
- 引入spring-boot-starter-web后,自动注册DispatcherServlet(SpringMVC 核心);
- 引入spring-boot-starter-data-redis后,自动注册RedisTemplate。
3.3.2 自动配置的实现方案
要让第三方依赖中的 Bean(如 Gson、RedisTemplate)被 Spring 扫描到,有两种核心方案:
方案 1:@ComponentScan 扫描(不推荐)
通过@ComponentScan指定扫描第三方包,但存在两大问题:
- 繁琐:引入多个第三方依赖时,需手动添加所有包路径;
- 性能低:大面积扫描会增加容器启动时间。
示例(不推荐):
// 扫描自定义包(com.example)和第三方包(com.google.gson) @ComponentScan({"com.example", "com.google.gson"}) @SpringBootApplication public class SpringBootApp { public static void main(String[] args) { SpringApplication.run(SpringBootApp.class, args); } } |
方案 2:@Import 导入(推荐,SpringBoot 底层采用)
@Import注解可直接将类或配置类导入 IOC 容器,无需扫描,支持 4 种导入方式:
导入方式 | 说明 | 示例代码 |
1. 导入普通类 | 直接导入单个类,该类会被注册为 Bean | @Import(Gson.class) |
2. 导入配置类 | 导入@Configuration标注的配置类,配置类中的@Bean会被注册 | @Import(RedisConfig.class) |
3. 导入 ImportSelector 实现类 | 动态返回多个类的全限定名,批量导入(适合复杂场景) | 实现ImportSelector接口,重写selectImports返回类路径 |
4. 导入 @EnableXxxx 注解 | 第三方依赖封装的注解,内部包含@Import(最优雅,如@EnableRedis) | @EnableOSS(内部封装@Import(OSSImportSelector.class)) |
示例 3:ImportSelector 实现类
// 自定义ImportSelector,返回需要导入的配置类 public class OSSImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 返回OSS配置类的全限定名 return new String[]{"com.example.config.OSSConfig"}; } } // 启动类中导入 @Import(OSSImportSelector.class) @SpringBootApplication public class SpringBootApp { public static void main(String[] args) { SpringApplication.run(SpringBootApp.class, args); } } |
示例 4:@EnableXxxx 注解(最优雅)
第三方依赖通常会提供@EnableXxxx注解,封装@Import,使用者只需添加注解即可:
// 第三方依赖提供的@EnableOSS注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(OSSImportSelector.class) // 内部导入配置 public @interface EnableOSS { } // 启动类中启用 @EnableOSS @SpringBootApplication public class SpringBootApp { public static void main(String[] args) { SpringApplication.run(SpringBootApp.class, args); } } |
3.3.3 自动配置源码跟踪:从 @SpringBootApplication 开始
要理解自动配置的底层逻辑,需从启动类的@SpringBootApplication注解入手,该注解是一个 “组合注解”,包含 3 个核心注解:
@SpringBootApplication // 等价于: @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan |
1. @SpringBootConfiguration
本质是@Configuration,标识启动类也是一个配置类,可在启动类中定义@Bean。
2. @ComponentScan
默认扫描启动类所在包及其子包,将@Component及其衍生注解的类注册为 Bean(自定义的 Controller、Service 等)。
3. @EnableAutoConfiguration(自动配置核心)
该注解是自动配置的关键,内部包含:
@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { } |
核心流程:
- AutoConfigurationImportSelector实现ImportSelector接口,重写selectImports方法;
- selectImports调用getAutoConfigurationEntry,读取所有依赖中META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件;
- 该文件中定义了所有自动配置类(如GsonAutoConfiguration、RedisAutoConfiguration);
- SpringBoot 启动时,加载这些自动配置类,通过@Bean注册所需 Bean。
面试高频点:低版本 SpringBoot(2.7.0 前)的自动配置类定义在META-INF/spring.factories文件中,高版本统一迁移到AutoConfiguration.imports。
3.3.4 @Conditional:自动配置的 “开关”
AutoConfiguration.imports中定义了大量自动配置类,但并非所有 Bean 都会被注册,核心在于@Conditional系列注解 ——满足条件才注册 Bean。
常用 @Conditional 衍生注解
注解 | 作用 | 示例代码 |
@ConditionalOnClass | 环境中存在指定类的字节码(即引入了对应依赖),才注册 Bean | @ConditionalOnClass(Gson.class)(引入 Gson 依赖才注册 Gson Bean) |
@ConditionalOnMissingBean | 环境中不存在指定类型 / 名称的 Bean,才注册(避免覆盖用户自定义 Bean) | @ConditionalOnMissingBean(Gson.class)(用户没定义 Gson 才自动注册) |
@ConditionalOnProperty | 配置文件中存在指定属性且值匹配,才注册 Bean | @ConditionalOnProperty(name = "oss.enable", havingValue = "true") |
示例:GsonAutoConfiguration 源码片段
// 仅当环境中有Gson类(引入Gson依赖)时,该配置类才生效 @AutoConfiguration @ConditionalOnClass(Gson.class) // 绑定配置文件中的属性(如spring.gson.date-format) @EnableConfigurationProperties(GsonProperties.class) public class GsonAutoConfiguration { // 仅当环境中没有GsonBuilder Bean时,才注册 @Bean @ConditionalOnMissingBean public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) { GsonBuilder builder = new GsonBuilder(); customizers.forEach(customizer -> customizer.customize(builder)); return builder; } // 仅当环境中没有Gson Bean时,才注册 @Bean @ConditionalOnMissingBean public Gson gson(GsonBuilder gsonBuilder) { return gsonBuilder.create(); } } |
核心总结:自动配置不是 “无脑注册所有 Bean”,而是通过@Conditional按需注册,既保证了 “开箱即用”,又支持用户自定义覆盖。
四、实战:自定义 SpringBoot Starter(公共组件复用)
在团队开发中,经常需要封装公共组件(如文件上传、短信发送)给多个项目使用。将公共组件封装为 SpringBoot Starter,能实现 “引入依赖即能用”,极大提升开发效率。
4.1 Starter 的命名规范
SpringBoot Starter 分为两类,命名需遵循规范,便于识别:
类型 | 命名格式 | 示例 |
官方 Starter | spring-boot-starter-xxx | spring-boot-starter-web |
第三方 Starter | xxx-spring-boot-starter | aliyun-oss-spring-boot-starter |
4.2 自定义 Starter 的核心结构
一个完整的 Starter 包含两个模块,职责分离:
- xxx-spring-boot-starter:仅负责依赖管理,不包含代码;
- xxx-spring-boot-autoconfigure:负责自动配置,包含 Bean 定义、配置类等核心代码。
为什么分两个模块?便于后续单独升级自动配置逻辑,不影响依赖管理。
4.3 实战案例:自定义阿里云 OSS Starter
需求:封装阿里云 OSS 工具类,用户引入 Starter 后,注入工具类即可使用,无需手动配置。
步骤 1:创建 Starter 模块(依赖管理)
模块名:aliyun-oss-spring-boot-starter,仅保留pom.xml,引入 autoconfigure 模块:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.8</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>aliyun-oss-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- 引入自动配置模块 --> <dependency> <groupId>com.example</groupId> <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- 引入SpringBoot基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> </project> |
步骤 2:创建 Autoconfigure 模块(自动配置)
模块名:aliyun-oss-spring-boot-autoconfigure,包含核心代码。
2.1 引入依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.8</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Lombok(简化代码) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 阿里云OSS SDK --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.17.4</version> </dependency> <!-- OSS依赖的JAXB相关包 --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.3</version> </dependency> </dependencies> </project> |
2.2 定义配置属性类(绑定配置文件)
通过@ConfigurationProperties绑定用户配置文件中的属性(如aliyun.oss.endpoint):
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; // 绑定配置文件中前缀为"aliyun.oss"的属性 @Data @ConfigurationProperties(prefix = "aliyun.oss") public class AliyunOSSProperties { // OSS服务端点(如https://oss-cn-beijing.aliyuncs.com) private String endpoint; // OSS桶名 private String bucketName; } |
2.3 定义 OSS 工具类(核心业务逻辑)
import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider; import lombok.RequiredArgsConstructor; import java.io.ByteArrayInputStream; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.UUID; // 构造方法注入配置类(Lombok的@RequiredArgsConstructor简化代码) @RequiredArgsConstructor public class AliyunOSSOperator { private final AliyunOSSProperties ossProperties; // 文件上传方法 public String upload(byte[] content, String originalFilename) { String endpoint = ossProperties.getEndpoint(); String bucketName = ossProperties.getBucketName(); // 生成文件路径(如2024/08/xxx.jpg) String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM")); String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(".")); String objectName = dateDir + "/" + fileName; // 从环境变量获取OSS访问凭证(推荐线上环境使用) EnvironmentVariableCredentialsProvider credentialsProvider = new EnvironmentVariableCredentialsProvider(); OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider); try { // 上传文件 ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content)); // 返回文件访问URL return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName; } finally { if (ossClient != null) { ossClient.shutdown(); } } } // 其他方法(如文件删除、列表查询)可自行扩展 } |
2.4 定义自动配置类(注册 Bean)
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration // 启用配置属性类(使@ConfigurationProperties生效) @EnableConfigurationProperties(AliyunOSSProperties.class) public class AliyunOSSAutoConfiguration { // 注册OSS工具类为Bean,依赖AliyunOSSProperties会自动注入 @Bean public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties ossProperties) { return new AliyunOSSOperator(ossProperties); } } |
2.5 配置自动配置文件
在src/main/resources下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,写入自动配置类的全限定名:
com.example.aliyun.oss.AliyunOSSAutoConfiguration |
步骤 3:测试 Starter
- 将两个模块安装到本地 Maven 仓库(执行mvn clean install);
- 在其他 SpringBoot 项目中引入 Starter 依赖:
<dependency> <groupId>com.example</groupId> <artifactId>aliyun-oss-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> |
- 在配置文件(application.yml)中添加 OSS 配置:
aliyun: oss: endpoint: https://oss-cn-beijing.aliyuncs.com bucketName: your-bucket-name |
- 注入工具类使用:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController public class UploadController { @Autowired private AliyunOSSOperator aliyunOSSOperator; @PostMapping("/upload") public String upload(MultipartFile file) throws Exception { // 调用OSS工具类上传文件 return aliyunOSSOperator.upload(file.getBytes(), file.getOriginalFilename()); } } |
五、总结:这篇文章能帮你解决什么问题?
- 配置冲突:掌握 5 种配置的优先级,快速定位端口、数据库连接等配置不生效的问题;
- Bean 管理:理解 Bean 作用域的选择、第三方 Bean 的注册方式,写出更优雅的代码;
- 面试加分:吃透 SpringBoot 自动配置原理(@SpringBootApplication、@EnableAutoConfiguration、@Conditional),应对面试中的底层问题;
- 实战复用:学会自定义 Starter,封装公共组件,提升团队开发效率。
如果这篇文章帮你解决了 SpringBoot 开发中的实际问题,或者让你对 SpringBoot 原理有了更清晰的认识,欢迎点赞 + 收藏 + 关注!后续会持续更新 Java 后端进阶知识点。