SpringBoot底层剖析
SpringBoot原理
配置优先级
- SpringBoot中支持三种格式的配置文件:
1 | // application.properties |
如果同时存在以上三份配置文件,那么配置生效的优先级为:properties > yml > yaml
不过,开发中尽量统一配置文件的格式,只使用同一种格式的配置文件
- SpringBoot除了支持文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置
- Java系统属性:
-Dserver.port=9000 - 命令行参数:
--server.port=10010
- Java系统属性:


命令行参数优先级大于Java系统属性,且它们都优先于配置文件
通过Maven打包后的Jar包,使用
Java -Dserver.port=9000 -jar tlias-web-management-0.0.1-SNAPSHOT.jar --server.port=10010命令即可运行(Springboot项目需要引入spring-boot-maven-plugin依赖进行打包)
Bean管理
Bean的作用域
- Spring支持五种作用域,后三种在web环境才生效
| 作用域 | 说明 |
|---|---|
| singleton | 容器内 同名称 的bean只有一个实例(单例)(默认) |
| prototype | 每次使用该bean时会创建新的实例(非单例/多例) |
| request | 每个请求范围内会创建新的实例(web环境中,了解) |
| session | 每个会话范围内会创建新的实例(web环境中,了解) |
| application | 每个应用范围内会创建新的实例(web环境中,了解) |
声明作用域:
1 | // 在bean类中添加注解@Scope |
如果不指定作用域,默认IOC容器内的同名称bean对象都是同一个
获取IOC容器内的Bean对象:
1 |
|
Bean对象在IOC容器中的名称默认是类名首字母小写
当然Bean也可以起别名,通过
@Component("bean")、@Bean(name = {"bean"})等等
- 默认singleton作用域的bean创建时机:
- 在**项目启动时创建**,创建完毕后bean存入IOC容器,后续就不会再重新创建
- 可以在Bean类上加上
@Lazy注解,这样只有当**第一次使用**到这个Bean对象时,才会创建并存入IOC容器,后续不再创建
- prototype作用域的bean创建时机:
- 每当需要使用到该bean对象时,就会创建一个新的bean对象
- prototype bean本身就是按需创建,**lazy对其无效**
Bean作用域的应用场景
Singleton的适用场景:
- 无状态的bean就可以设置为singleton
- 无状态指的就是bean对象中不会保存数据,只有逻辑相关的操作
- 当bean保存了数据后,该数据会在所有对该bean的调用中共享,如果多个调用方同时对其进行修改,会导致数据竞争和状态污染,无法保证数据的一致性和正确性
Prototype的适用场景:
- 有状态的bean就应该设置为prototype
- 有状态指的是bean对象中会保存数据,这些数据通常是用户相关或请求相关的临时信息
- 当bean保存了数据后,每个调用方都需要独立的实例来存储自己的数据,避免数据在不同调用方之间共享造成的状态污染和数据不一致问题
开发中大部分的bean都是无状态的,所以不需要配置scope。只需要遵循Bean类中保存数据使用Prototype,不保存使用Singleton
Singleton作用域的bean对于系统的性能压力更小
Bean作用域面试题
面试题1:Spring容器的bean是单例还是多例?单例的bean是什么时候实例化的?
答1:默认是单例Singleton。不配置@Lazy注解,在程序启动时进行实例化,配置@Lazy在使用到时进行实例化
面试2:Spring容器的bean是线程安全的吗?
答2:
- bean的线程安全取决于bean的状态和作用域
- 单例bean:如果是无状态的bean,内部不保存任何状态信息,则是**线程安全**的
- 单例bean:如果是有状态的bean,内部会保存状态信息,多个线程操作该bean时,可能会出现数据不一致的问题,这样的bean则是**线程不安全**的
- 多例bean:无论是有状态还是无状态,每次调用底层都会创建单独的实例,所以不存在多个线程操作同一个bean,所以多例bean是**线程安全**的
第三方bean
- 当引入第三方依赖时,是无法通过添加
@Component注解将某个类交给IOC容器的(只读权限,且一定避免对源码进行直接修改) - 不过,可以通过
@Bean注解解决以上问题(注意不是在源码中加)
1 |
|
在 @Bean 方法中,返回值类型就是你想要注册到IOC容器中的Bean类型
当Bean依赖其他Bean时,可以通过方法参数注入依赖(Spring会自动从IOC容器中找到对应的bean对象并传入)
建议:虽然可以直接在启动类中通过@Bean添加第三方bean,但是为了方便第三方bean的管理,建议单独创建一个配置类(@Configuration),在配置类中对第三方的bean进行统一管理
起步依赖
SpringBoot中的起步依赖就是Maven中的依赖传递,通过引入起步依赖将所需要的所有依赖自动引入

像包名中带有
xxx-starter的包,大部分就是起步依赖包
自动配置(面试重点)
- SpringBoot的自动配置就是当spring项目启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作
实现方案
环境搭建:先在项目中导入第三方包:
1 | <!-- 第三方依赖 --> |
方案一:@Component + @ComponentScan
如果第三方包下声明了bean(带有@Component的类),在注入时,还要考虑组件扫描的范围(默认扫描启动类所在包及其子包)。所以需要修改扫描范围:
1 |
|
com.example包下有一个工具类(TokenParser)被声明为了bean
@ComponentScan(basePackages = ...)注解可以换成@SpringBootApplication(scanBasePackages = ...)有可能会存在IDEA无法识别到目标包,但是实际上是可以运行的
1 |
|
方案一的问题:首先需要对方被声明为bean才可以使用,其次由于扫描范围的增加,导致配置繁琐且性能降低
方案二:通过@Import导入。@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种
- 导入普通类
- 导入配置类(@Configuration)
1 | // 导入普通类 导入配置类 |
导入
ImportSelector接口的实现类(第三方可以决定哪些类能够被加载到IOC容器中,且有批量导入的功能)当第三方依赖中有一个类实现了
ImportSelector接口1
2
3
4
5public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
}
}使用的时候直接导入这个类即可
1
2
3
4
5
6
7
public class SpringbootWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfigApplication.class, args);
}
}
根据第三种方式,第三方还可以提供注解类简化使用方的导入操作(封装@Import注解)
注解类如下:
1
2
3
4
5
public EnableHeaderConfig {
}使用方不再需要使用@Import注解并指定目标类了,可以直接使用提供的注解类进行导入:
1
2
3
4
5
6
7
public class SpringbootWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfigApplication.class, args);
}
}
源码跟踪
源码跟踪的着重点在启动类上,通过@SpringBootApplication注解查看被层层封装的源码。该注解标识在SpringBoot工程引导类上,是SpringBoot中最最最重要的注解。该注解由三个部分组成:
- @SpringBootConfiguration:该注解与@Configuration注解作用相同,用来声明当前也是一个配置类
- @ComponentScan:组件扫描,默认扫描引导类所在包及其子包
- @EnableAutoConfiguration:SpringBoot实现自动化配置的**核心注解**






@Conditional注解
- 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring IOC容器中
- 位置:方法、类
- @Conditional本身是一个父注解,派生出大量的子注解,以下为常见子注解:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器
- @ConditionalOnMissionBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器
- @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器
示例:
1 | package com.example; |
自己定义自动配置类的核心是定义自动配置类,将自动配置类配置在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中
自定义starter
- 场景:在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot的starter(包含了起步依赖和自动配置的功能)

- 需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSOperator的自动配置
- 目标:引入起步依赖后,想要使用阿里云OSS,注入AliyunOSSOperator直接使用即可
- 步骤:
- 创建aliyun-oss-spring-boot-starter模块
- 创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块
- 在aliyun-oss-spring-boot-autoconfigure模块中定义自动配置功能,并定义自动配置文件META-INF/spring/xxxx.imports
aliyun-oss-spring-boot-autoconfigure下的依赖(就是业务逻辑代码中需要的依赖 + Springboot起步依赖):
1 | <dependency> |
aliyun-oss-spring-boot-starter下的依赖(引入自动配置模块的依赖即可):
1 | <dependency> |
阿里云OSS配置模块
首先是业务逻辑的相关代码,直接从Tlias系统中复制过来即可:
AliyunOSSOperator,要把@Component注解去掉,因为不通过扫描组件的方式引入bean对象:
1 | public class AliyunOSSOperator { |
AliyunOSSProperties属性配置类也同样去掉@Component,只留一个引入配置文件属性的注解:
1 | package com.aliyun.oss; |
最后就是将工具类通过AliyunOSSAutoConfiguration配置类保存到IOC容器中:
1 | /** |
最重要的一步,在resource目录下创建META-INF/Spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(文件名、路径不能错),并引入配置类:
1 | com.aliyun.oss.AliyunOSSAutoConfiguration |
这样当其他项目使用时SpringBoot就会自动配置并将工具类作为bean对象保存到IOC容器中
测试
在测试的项目中引入依赖:
1 | <!-- 引入自定义starter --> |
其他依赖会通过依赖传递的方式引入
测试代码:
1 |
|