Spring-Boot原理篇
在封包声明、导入声明和每个函数之间,都有空白行隔开。这条极其简单的规则极大的影响代码的视觉外观。
配置优先级
配置文件
SpringBoot中支持三种格式的配置文件:
优先级从高到低:
application.properties
:
1 |
|
application.yml
:
1 |
|
application.yaml
:
1 |
|
同时存在时,三种配置都会加载,但是重复项以优先级来确定
命令
SpringBoot除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置。
优先级:
命令行参数:
1
--server.port=10010
Java系统属性:
1
-Dserver.port=9000
使用示例:
1 |
|
此时实际会监听10010端口
优先级
- 命令行参数
- java系统属性
- application.properties
- application.yml
- application.yaml
Bean管理
- IoC 容器 :Spring 项目启动时,会自动创建并管理 Bean(对象实例),存放在 IoC 容器中。
- 获取 Bean 场景 :在运行时需要手动获取容器中的 Bean(例如动态调用、非 Spring 管理类中使用 Bean)。
获取Bean的三种方式:
获取方式 | 方法签名 | 适用场景 | 特点 |
---|---|---|---|
按名称获取 | Object getBean(String name) |
已知 Bean 名称,但类型不确定 | 返回 Object ,需强制类型转换 |
按类型获取 | <T> T getBean(Class<T> requiredType) |
已知 Bean 类型,且容器中该类型唯一 | 类型安全,无需强转 |
按名称 + 类型获取(推荐) | <T> T getBean(String name, Class<T> requiredType) |
已知 Bean 名称和类型 | 类型安全,避免歧义 |
1 |
|
Bean作用域
Spring支持五种作用域,后三种在web环境才生效
作用域 | 说明 |
---|---|
singleton | 容器内同名称的 bean 只有一个实例(单例)(默认) |
prototype | 每次获取 bean 的时候创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web 请求中,了解) |
session | 每个会话范围内会创建新的实例(web 请求中,了解) |
application | 每个应用范围内会创建新的实例(web 请求中,了解) |
配置
1 |
|
延迟加载 @Lazy
默认行为:Bean在容器启动时立即创建
延迟加载:使用 @Lazy
注解可让Bean在第一次使用时才创建。
1 |
|
第三方bean
背景:
- 第三方 Bean :指来自外部库或框架的类(非自己编写),无法直接在类上添加
@Component
、@Service
等注解。 - 问题 :Spring 无法自动扫描并注册这些类到 IoC 容器中。
- 解决方案 :使用
@Bean
注解手动注册。
使用方法:启动类(不建议)
1 |
|
- 方法名默认作为 Bean 名称(可通过
@Bean("beanName")
自定义)。 - 方法返回值类型即 Bean 类型。
- 方法体内可进行初始化配置(如设置属性、依赖注入)。
推荐方法:
1 |
|
如果第三方 Bean 需要依赖其他 Bean,建议在
@Bean
定义方法中 显式声明必要的参数 ,Spring 会自动从容器中注入这些依赖。
1
2
3
4
@Bean
public MyService myService(MyRepository repository) {
return new MyService(repository);
}
SpringBoot原理
起步依赖
SprngBoot starter 依赖的特别之处在于它们本身并不包含代码,而是传递性地拉取其他库。
优势:
- 构建文件会显著减小并更加易于管理,因为这样不必为所需的每个依赖库都声明依赖。
- 我们能够根据它们所提供的功能来思考依赖,而不是更具库的名字。如果要开发Web应用,只需添加web starter依赖,而不必添加一堆单独的库。
- 我们不必再担心库版本的问题。你可以直接相信给定版本的SpringBoot,传递性引入的库的版本都是兼容的。现在你只需要关心使用哪个版本的SpringBoot就可以了。
核心原理
Starter本质时一个Maven POM,不包含业务代码,只声明依赖。
通过依赖传递,引入Starter就会自动引入相关库
自动配置
注册第三方包中的bean
方法一:增加扫描范围
@SpringBootApplication
注解有包扫描的作用,但是默认范围为当前包及其子包。
方法:使用@ComponentScan指定组件扫描范围 项目中
1 |
|
缺点:
- 使用繁琐
- 性能差
方法二:@Import导入
@Import
是 Spring 提供的一种显式装配机制,可以将指定的类注册到 IoC 容器中。
常用于:
- 跨模块引入配置类或工具类
- 手动控制 Bean 装配
- 配合自动配置机制实现模块化加载
导入类型 | 说明 | 示例 |
---|---|---|
普通类 | 直接将该类注册为 Bean | @Import(TokenParser.class) |
配置类 | 导入 @Configuration 标注的类,加载其定义的所有 Bean |
@Import(HeaderConfig.class) |
ImportSelector 实现类 | 通过返回类名数组的方式,动态选择要导入的配置类 | @Import(MyImportSelector.class) |
ImportSelectot实现示例:依赖中:
1
2
3
4
5
6
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] { "com.example.HeaderConfig" }; //返回字符串数组
}
}
示例代码:项目中:
1 |
|
方法三:@EnableXxxx注解
依赖中:自定义一个注解:
1 |
|
项目中:直接使用封装后的注解
1 |
|
自动配置原理
核心理念:约定优于配置
目的:在引入起步依赖后,SpringBoot能自动为你配置好常用组件,让你开箱即用,只需要关注业务逻辑。
启动类:
1 |
|
在 @SpringBootApplication
注解上有三个重要注解:
1 |
|
- @SpringBootConfiguration :标识该类是配置类,作用类似于
@Configuration
。- @EnableAutoConfiguration :开启 Spring Boot 的自动配置功能,根据项目依赖自动进行配置。
- @ComponentScan :开启组件扫描,让 Spring 自动发现并注册指定包下的 Bean。
其中 @EnableAutoConfiguration
用于开启自动配置功能,我们再来看看其中的内部实现:
1 |
|
@EnableAutoConfiguration
中包含了一条 @Import
注解,用于将外部类注册到IOC容器中,而具体导入了哪些类,取决于 AutoConfigurationImportSelector
的具体实现。
所以我们继续深入查看 AutoConfigurationImportSelector
的实现:
1 |
|
AutoConfigurationImportSelector
继承实现了很多接口,但是对于自动配置的核心原理,我只需要关心 DeferredImportSelector
接口:
1 |
|
该接口是一ImportSelecter的一个子接口。
那么我们只需要查看 @AutoConfigurationImportSelector
的 selectImports
方法,即可知道自动配置为我们导入了哪些类。以下是selectImports方法的具体实现:
1 |
|
继续深入追查几个方法后,最终我们可以找到 getCandidateConfigurations
方法:
1 |
|
翻译:
在
META-INF/spring/
目录下未找到+ this.autoConfigurationAnnotation.getName() + .imports
中指定的自动配置类。如果你使用了自定义打包方式,请确保该文件是正确的。
找到文件:
在一些SpringBoot的起步依赖中,会引入以autoconfigure结尾的依赖项,这些依赖项中会有 META-INF/spring/*.imports
文件:
我们选择一个依赖项打开其 imports
文件:
文件中列举了需要注册到Bean容器的全类限定名。其中,在imports文件中列举的都是 AutoConfiguration
类。
以Gson为例:
1 |
|
查看具体实现:
在AutoConfiguration类中,通过Bean注解将所需类注册到IOC容器。
条件装配
imports文件中列举的所有类并不会都被注册到IOC容器中。而是只会导入那些满足条件的类,这就是条件装配。
在 Spring 中,你可以使用 @Conditional
注解来定义装配 bean 的条件。
@Conditional
作用:根据特定条件,决定是否向IOC容器注册Bean。
位置:类或方法上。
常用派生注解:
注解 | 条件判断逻辑 | 常见使用场景 | 注意事项 |
---|---|---|---|
@ConditionalOnClass |
类路径中存在指定类 | 依赖存在时才启用某功能 | 类名需全限定名,避免类加载异常 |
@ConditionalOnMissingBean |
容器中不存在指定 Bean(按类型、名称、注解匹配) | 提供默认 Bean 实现 | 若已有用户自定义 Bean,则不会注册 |
@ConditionalOnProperty |
配置文件中存在指定属性且值匹配 | 按配置开关启用功能 | 支持 havingValue 、matchIfMissing 参数 |
@ConditionalOnBean |
容器中存在指定 Bean | 功能依赖其他 Bean 时 | 类型或名称需精确匹配 |
@ConditionalOnExpression |
SpEL 表达式结果为 true |
灵活条件控制 | 表达式需返回布尔值 |
@ConditionalOnJava |
Java 版本匹配 | 针对不同 JDK 版本适配 | 需指定 range |
@ConditionalOnWebApplication |
当前是 Web 应用环境 | Web 相关 Bean 注册 | 可指定 Web 类型(Servlet/Reactive) |
@ConditionalOnNotWebApplication |
当前非 Web 应用环境 | CLI 工具、批处理应用 | 与上者互斥 |
@ConditionalOnResource |
类路径存在指定资源文件 | 配置文件、模板存在时启用 | 路径需正确 |
@ConditionalOnSingleCandidate |
指定类型 Bean 只有一个候选 | 自动注入唯一实现 | 多实现时需加 @Primary |
示例:
1 |
|
以GSON的AutoConfiguration为例:
1 |
|
只有在引入Gson依赖后,SpringBoot才会自动配置Gson类。
并且只有在没有自定义声明的Gson注册到Bean时,才会将默认的Gson注册到Bean。
自定义Starter
Spring Boot Starter :一组预定义的 Maven 依赖集合,通常包含:
- 依赖管理功能(dependency management):自动引入所需依赖及版本。
- 自动配置功能(auto-configuration):基于条件注解自动装配Bean。
使用场景:
在实际项目中,某些功能模块(如日志、监控、数据访问层封装)会在多个项目中重复使用。
将这些模块封装为 Starter,可以:
- 简化依赖引入 :只需引入一个 Starter 依赖即可。
- 减少重复配置 :通过自动配置类统一初始化。
- 提高可维护性 :升级或修改只需更新 Starter。
命名规范:
SpringBoot官方starter依赖:
spring-boot-starter-web
功能名放在最后一个词
第三方starter依赖:
mybatis-spring-boot-starter
功能名放在第一个词
创建步骤
创建Starter模块
模块命名规范:
aliyun-oss-spring-boot-starter
作用:依赖聚合
pom.xml
示例:1
2
3
4
5
6
7
8<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 其他必要依赖 -->
</dependencies>
创建Autoconfiguration模块
模块命名规范:
aliyun-oss-spring-boot-autoconfigure
作用:提供自动配置功能
典型结构:
1
2src/main/java/com/example/oss/autoconfigure/OssAutoConfiguration.java
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
编写自动配置类
1
2
3
4
5
6
7
8
9
10
11@Configuration
@ConditionalOnClass(OssClient.class) // 类存在时才生效
@EnableConfigurationProperties(OssProperties.class)
public class OssAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OssClient ossClient(OssProperties properties) {
return new OssClient(properties.getEndpoint(), properties.getAccessKeyId(), properties.getAccessKeySecret());
}
}注册自动配置
1
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:
1
com.example.oss.autoconfigure.OssAutoConfiguration
Maven高级
分模块设计与开发
定义:将项目按功能拆分成若干个相对独立的模块。
核心思想 :高内聚、低耦合,每个模块专注完成特定功能。
优势:
目的 | 说明 |
---|---|
便于管理与维护 | 模块独立,修改或优化时不影响其他模块。 |
便于扩展 | 新功能可通过新增模块实现,而无需大规模改动原有代码。 |
便于模块间调用 | 模块接口清晰,调用关系明确。 |
降低风险 | 问题可快速定位到具体模块,减少全局性影响。 |
设计实例:
将数据模型pojo,工具类utils,单独创建为一个Maven项目,在需要的模块中进行导入。
继承与聚合
单纯的将项目进行拆分,可能遇到在每一个模块中我们都需要引入某些依赖的情况。
多个项目必须都单独引入一次依赖,并且必须保证依赖的版本号相同。
可以使用继承解决这一问题。
继承
- 定义 :Maven 中的继承类似于 Java 类的继承, 子工程可以继承父工程的配置信息 (依赖、插件、属性等)。
- 作用 :减少重复配置,实现依赖与构建的统一管理。
继承的核心机制:
特性 | 说明 |
---|---|
单继承 | 一个子工程只能有一个直接父工程 |
配置合并 | 子工程会继承父工程的 <dependencies> 、<dependencyManagement> 、<build> 等配置 |
可覆盖 | 子工程可覆盖父工程中相同元素的配置 |
版本统一 | 父工程可集中管理依赖版本,子工程无需重复声明版本号 |
实现:<parent>...</parent>
步骤
创建父工程(Parent Project)
设置父工程的 packaging 类型为 pom
1
<packaging>pom</packaging>
父工程继承springBoot
1
2
3
4
5
6<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>在父工程中统一管理依赖、插件、属性等
1
2
3
4
5
6
7
8
9
10<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
</dependencyManagement>创建子工程(Submodules)
子工程中无需再配置GroupID,会自动继承父工程
在子工程的 pom.xml 中声明
<parent>
标签,引用父工程1
2
3
4
5
6<parent>
<groupId>com.itheima</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../tlias-parent/pom.xml</relativePath>
</parent><relativepath>
:- 父项目pom.xml文件相对于当前子模块 pom.xml 所在目录的路径。
- 如果父模块在本地文件系统中不存在(例如只在远程仓库),可以留空
<relativePath/>
,Maven 会去仓库查找。
子工程继承父工程配置,可按需覆盖或扩展
如果父工程与子工程配置了相同依赖的不同版本,会以子工程为准。
版本锁定
背景与问题:
- 继承会让所有子模块都引入父工程中的依赖。
- 有些依赖只有一部分模块需要引入,另一部分模块并不需要引入。
- 我们需要保证每个模块引入依赖版本号相同。
解决方法:<dependencyManagement>
在 父 POM 中统一声明依赖及版本:
1
2
3
4
5
6
7
8
9<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
</dependencyManagement>子模块只需:
1
2
3
4<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>不写version ,自动继承父POM中的版本。
优势
- 统一管理 :所有模块依赖版本一致
- 易于升级 :只改父 POM 一处即可
- 减少冲突 :避免 jar 包版本不匹配
- 可扩展性 :适用于多种公共依赖
版本约定
目的 :用 <properties></properties>
定义版本号,集中管理,便于统一修改。
父 POM 示例 :
1 |
|
将所有的版本号集中管理。
聚合
定义:将多个模块组织在一起,统一构建和管理。
聚合工程:
- 一个不包含业务功能的空工程
- 有且仅有一个pom.xml文件(
<packaging>pom</packaging>
) - 可以使用父工程作为聚合工程
作用:
- 快速构建项目
- 无需手动根据依赖关系逐个构建模块
- 直接在聚合工程上执行构建命令,即可一次性构建所有子模块
结构示例:
1 |
|
父工程(聚合工程):
1 |
|
<modules>
:标签定义所有子模块路径(相对路径)- 子模块的
pom.xml
中需要指定<parent>
关联父工程
聚合工程中所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关。
私服
定义:
- 私服是一种特殊的 远程仓库 ,部署在局域网内。
- 作用:作为外部中央仓库的代理,解决内网资源共享与同步问题。
三层仓库结构与依赖关系:
层级 | 名称 | 位置 | 作用 |
---|---|---|---|
1️⃣ | 本地仓库(Local Repository) | 开发者本机 | 开发、提交、推送代码的第一站 |
2️⃣ | 私服(Private Repository) | 内网服务器 | 缓存/代理中央仓库,统一内网访问入口 |
3️⃣ | 中央仓库(Central Repository) | 外网服务器 | 官方源代码或依赖的最终权威版本 |
资源上传与下载
私服仓库
仓库类型 | 英文名称 | 主要用途 | 特点 | 典型存放内容 |
---|---|---|---|---|
发布仓库 | Release Repository | 存放稳定版本构件 | 版本号固定(如 1.0.0 ),一旦发布不可修改 |
生产可用的正式版本 Jar、War、Pom |
快照仓库 | Snapshot Repository | 存放开发中版本构件 | 版本号带 -SNAPSHOT ,可被覆盖更新 |
开发迭代中的临时构件 |
中央仓库 | Central Repository | Maven 官方公共仓库 | 提供开源依赖的权威源,全球可访问 | 各类开源库的正式版本 |
步骤
配置用户名与密码
设置私服的访问用户名/密码(settings.xml 中的 servers 中配置)
配置私服上传S地址
maven工程的pom文件中配置上传(发布地址):
配置私服下载地址
设置私服的仓库组地址(settings.xml 中的 mirrors 中配置):
1 |
|
settings.xml 中的 profiles 中配置:
1 |
|
允许下载release、snapshots仓库中的模块。
配置完毕后,执行Maven生命周期deploy,即可上传至私服。