一、配置优先级
在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。通过以上的测试,我们也得出了优先级**(从低到高):**
-
application.yaml(忽略)
-
application.yml
-
application.properties
-
java系统属性(-Dxxx=xxx)
-
命令行参数(--xxx=xxx)
二、Bean管理
1. 获取Bean
(1) 常用方法
① 根据name获取bean
java
Object getBean(String name)
② 根据类型获取bean
java
<T> T getBean(Class<T> requiredType)
③ 根据name获取bean(带类型转换)
java
<T> T getBean(String name, Class<T> requiredType)
(2) 代码实现
测试类:
java
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private ApplicationContext applicationContext; //IOC容器对象
//获取bean对象
@Test
public void testGetBean(){
//根据bean的名称获取
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);
//根据bean的类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
//根据bean的名称 及 类型获取
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
}
核心逻辑:
① ApplicationContext 是怎么来的?
ApplicationContext是Spring 框架中 IOC 容器的核心接口,你可以把它理解成 Spring 项目里管理所有 Bean 的 "大管家"
它的核心作用是:
- 管理 Bean 的生命周期 :项目启动时,Spring 会把配置好的 Bean(比如你代码里的
DeptController)都创建好,存到ApplicationContext这个容器里; - 提供 Bean 的获取方式 :就是你代码里用的
getBean()方法 ------ 不管按名称、按类型,都能从容器里拿到对应的 Bean 对象;
2. Bean作用域
(1) 在Spring中支持五种作用域,后三种在web环境才生效:
| 作用域 | 说明 |
|---|---|
| singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
| prototype | 每次使用该bean时会创建新的实例(非单例) |
| request | 每个请求范围内会创建新的实例(web环境中,了解) |
| session | 每个会话范围内会创建新的实例(web环境中,了解) |
| application | 每个应用范围内会创建新的实例(web环境中,了解) |
知道了bean的5种作用域了,我们要怎么去设置一个bean的作用域呢?
- 可以借助Spring中的@Scope注解来进行配置作用域

(2) 代码示例
① 测试一
- 控制器
java
//默认bean的作用域为:singleton (单例)
@Lazy //延迟加载(第一次使用bean对象时,才会创建bean对象并交给ioc容器管理)
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
//省略其他代码...
}
- 测试类
java
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private ApplicationContext applicationContext; //IOC容器对象
//bean的作用域
@Test
public void testScope(){
for (int i = 0; i < 10; i++) {
DeptController deptController = applicationContext.getBean(DeptController.class);
System.out.println(deptController);
}
}
}
② 测试二
修改控制器DeptController代码:
java
@Scope("prototype") //bean作用域为非单例
@Lazy //延迟加载
@RestController
@RequestMapping("/depts")
public class DeptController {
@Autowired
private DeptService deptService;
public DeptController(){
System.out.println("DeptController constructor ....");
}
//省略其他代码...
}
3. 第三方Bean
(1) 存在的问题
如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。
(2) 解决方案
方案一:在启动类上启动@Bean标识的方法(不建议)
java
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader saxReader(){
return new SAXReader();
}
}
xml文件:
XML
<?xml version="1.0" encoding="UTF-8"?>
<emp>
<name>Tom</name>
<age>18</age>
</emp>
测试类:
java
@SpringBootTest
class SpringbootWebConfig2ApplicationTests {
@Autowired
private SAXReader saxReader;
//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
//省略其他代码...
}
说明:以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性)
解决方案2:在配置类中定义@Bean标识的方法
- 如果需要定义第三方Bean时, 通常会单独定义一个配置类
java
@Configuration //配置类 (在配置类当中对第三方bean进行集中的配置管理)
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
public SAXReader reader(DeptService deptService){
System.out.println(deptService);
return new SAXReader();
}
}
核心逻辑:@Bean 方法的参数会自动从容器中找匹配的 Bean 注入
你写的reader(DeptService deptService)这个方法,Spring 会帮你做这几件事:
- 执行
reader()方法创建 SAXReader 之前,Spring 先看方法有没有参数(这里参数是 DeptService); - Spring 会去自己的 IOC 容器里,找类型为 DeptService的 Bean(就是上面 @Service 注解生成的那个);
- 把找到的 DeptService Bean 自动 "传" 到方法参数里,所以你在方法里能直接用
deptService(比如打印它); - 方法执行完返回 SAXReader 对象,Spring 再把这个 SAXReader 注册成容器里的 Bean(默认名称是方法名
reader)。
三、SpringBoot 原理
1. 自动配置
(1) 存在的问题
① 在SpringBoot项目 spring-boot-web-config2 工程中,通过坐标引入itheima-utils依赖

java
@Component
public class TokenParser {
public void parse(){
System.out.println("TokenParser ... parse ...");
}
}
② 在测试类中,添加测试方法
java
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testTokenParse(){
System.out.println(applicationContext.getBean(TokenParser.class));
}
//省略其他代码...
}
③ 测试方法

异常信息描述: 没有com.example.TokenParse类型的bean
说明:在Spring容器中没有找到com.example.TokenParse类型的bean对象
思考:引入进来的第三方依赖当中的bean以及配置类为什么没有生效?
-
原因在我们之前讲解IOC的时候有提到过,在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。
-
SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。
-
当前包:com.itheima, 第三方依赖中提供的包:com.example(扫描不到)
(2) 解决方案
方案一:@ComponentScan组件扫描(不推荐)
java
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要扫描的包
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
缺点:
-
使用繁琐
-
性能低
方案二:@Import导入(一般般)
导入形式主要有以下几种:
-
导入普通类
-
导入配置类
-
导入ImportSelector接口实现类
① 使用@Import导入普通类:
java
@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
② 使用@Import导入配置类:
- 配置类
java
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
- 启动类
java
@Import(HeaderConfig.class) //导入配置类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
③ 使用@Import导入ImportSelector接口实现类:
- ImportSelector接口实现类
java
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回值字符串数组(数组中封装了全限定名称的类)
return new String[]{"com.example.HeaderConfig"};
}
}
核心逻辑:
return new String[]{"com.example.HeaderConfig"}:这是核心!返回值是类的全限定名数组 (包名 + 类名),意思是 "告诉 Spring,把这些类创建成 Bean,加入容器"。这里只返回了HeaderConfig,就是让 Spring 强制注册这个类为 Bean。
- 启动类
java
@Import(MyImportSelector.class) //导入ImportSelector接口实现类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
方案三:使用第三方依赖提供的 @EnableXxxxx注解(推荐)
- 第三方依赖中提供的注解
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig {
}
- 在使用时只需在启动类上加上@EnableXxxxx注解即可
java
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
2. 原理分析
(1) 要搞清楚SpringBoot的自动配置原理,要从SpringBoot启动类上使用的核心注解@SpringBootApplication开始分析:

在@SpringBootApplication注解中包含了:
-
元注解
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
① @Target(ElementType.TYPE)
- 核心作用:指定当前注解可以标注在哪些代码元素上。
ElementType.TYPE表示这个注解只能用在类、接口、枚举类型上,不能用在方法、字段、参数等位置。- 对
@SpringBootApplication的意义:这就是为什么它只能加在 SpringBoot 的启动类(类级别)上,而不能加在方法或字段上。
② @SpringBootConfiguration

- @SpringBootConfiguration注解上使用了@Configuration,表明SpringBoot启动类就是一个配置类。
- @Indexed注解,是用来加速应用启动的(不用关心)。
③ @ComponentScan

-
@ComponentScan注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被@Component及其衍生注解声明的类。
-
SpringBoot启动类,之所以具备扫描包功能,就是因为包含了@ComponentScan注解。
④ @EnableAutoConfiguration

封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)
- 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。
3. @Conditional
(1) 介绍
① 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
② 位置:方法、类
③ @Conditional本身是一个父注解,派生出大量的子注解:
-
@ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
-
@ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
-
@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
(2) 代码示例
① @ConditionalOnClass注解
java
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnClass(name="io.jsonwebtoken.Jwts")//环境中存在指定的这个类,才会将该bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
核心逻辑:
@ConditionalOnClass(name="io.jsonwebtoken.Jwts"):条件判断
- 作用: 给
@Bean加 "注册条件"------ 只有当项目的类路径(classpath) 中存在io.jsonwebtoken.Jwts这个类时,Spring 才会执行这个headerParser()方法,把HeaderParser注册成 Bean;如果不存在这个类,这个@Bean方法会被直接忽略,HeaderParser不会被创建。 HeaderParser大概率是用来解析 JWT 令牌的工具类,而io.jsonwebtoken.Jwts是 JWT 框架(jjwt)的核心类 ------ 如果你的项目没引入 jjwt 的依赖(比如 Maven 的 jjwt 坐标),Jwts类就不存在,此时创建HeaderParser不仅没用,还可能因为缺少依赖报错。
pom.xml
java
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
② @ConditionalOnMissingBean注解
java
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean //不存在该类型的bean,才会将该bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
SpringBoot在调用@Bean标识的headerParser()前,**IOC容器中是没有HeaderParser类型的bean,**所以HeaderParser对象正常创建,并注册到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
java
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(name="deptController2")//不存在指定名称的bean,才会将该bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
核心逻辑:
Spring 会先检查 IoC 容器:有没有名称为deptController2的 Bean(不管这个 Bean 是什么类型)。
- ✅ 容器中没有 叫
deptController2的 Bean → 执行headerParser()方法,注册HeaderParser; - ❌ 容器中有 叫
deptController2的 Bean(比如有个@Controller("deptController2") public class DeptController {})→ 跳过方法,HeaderParser不注册。
再次修改@ConditionalOnMissingBean注解:
java
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(HeaderConfig.class)//不存在指定类型的bean,才会将bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
核心逻辑:按「Bean 类型」判断
Spring 会检查 IoC 容器:有没有HeaderConfig类型的 Bean(不管这个 Bean 叫什么名字)。
⚠️ 这里有个关键坑(新手必踩):HeaderConfig上加了@Configuration注解,而@Configuration本质是@Component的变种 ------Spring 启动时,会自动把HeaderConfig本身注册成 Bean(类型就是 HeaderConfig)。
所以这个判断的结果永远是:
- ❌ 容器中一定有
HeaderConfig类型的 Bean → 永远跳过headerParser()方法 →HeaderParser永远不会被注册!
③ @ConditionalOnProperty注解(这个注解和配置文件当中配置的属性有关系)
先在application.yml配置文件中添加如下的键值对:
XML
name: itheima
在声明bean的时候就可以指定一个条件@ConditionalOnProperty
java
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnProperty(name ="name",havingValue = "itheima")//配置文件中存在指定属性名与值,才会将bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
核心逻辑: @ConditionalOnProperty注解
这个注解是 SpringBoot 特有的 "配置条件注解",核心是读取配置文件的属性,判断是否满足条件,满足才注册 Bean。
4. 案例
(1) 需求
自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSUtils的自动配置。
目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunOSSUtils直接使用即可。
(2) 思路分析
① 创建自定义starter模块(进行依赖管理)
- 把阿里云OSS所有的依赖统一管理起来
② 创建autoconfigure模块
- 在starter中引入autoconfigure (我们使用时只需要引入starter起步依赖即可)
③ 在autoconfigure中完成自动配置
-
定义一个自动配置类,在自动配置类中将所要配置的bean都提前配置好
-
定义配置文件,把自动配置类的全类名定义在配置文件中
(3) 代码实现
① 在类上添加的@Component注解还有用吗?

1). 先明确@Component的作用
@Component的核心作用是:告诉 Spring"扫描到这个类时,把它注册成 IoC 容器里的 Bean" 。但这个作用生效的前提是:Spring 能扫描到这个类所在的包。
2). SpringBoot 的默认扫描规则
SpringBoot 启动类上的@SpringBootApplication注解,自带了@ComponentScan(组件扫描)功能,默认只扫描:启动类所在的包,以及这个包的所有子包。
比如:
- 如果启动类在
com.example.demo包下,Spring 只会扫描com.example.demo、com.example.demo.service、com.example.demo.controller等子包; - 而你截图里的类在
com.aliyun.oss包下,这个包不在启动类的包范围内,所以 Spring根本不会扫描这个包。
② AliOSSAutoConfiguration类:
java
@Configuration//当前类为Spring配置类
@EnableConfigurationProperties(AliOSSProperties.class)//导入AliOSSProperties类,并交给SpringIOC管理
public class AliOSSAutoConfiguration {
//创建AliOSSUtils对象,并交给SpringIOC容器
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}
核心逻辑:
1). @EnableConfigurationProperties
这个注解加在配置类上,传入AliOSSProperties.class,会做两件事:
- 第一件事 :把
AliOSSProperties类注册成 Spring IoC 容器中的 Bean (相当于给AliOSSProperties加了@Component); - 第二件事 :启用
AliOSSProperties上的@ConfigurationProperties注解,让 Spring 把配置文件中ali.oss.*的属性值,自动绑定到AliOSSProperties对象的字段中(比如把ali.oss.access-key的值赋给accessKey字段)。
2). 看配置类中的@Bean方法:
AliOSSProperties aliOSSProperties:方法参数 → Spring 会自动从 IoC 容器中找到AliOSSProperties类型的 Bean,注入到这个参数里(依赖注入,不用你手动 new);aliOSSUtils.setAliOSSProperties(aliOSSProperties):把注入进来的 "绑定了配置的 AliOSSProperties 对象",通过 setter 方法 "塞" 到AliOSSUtils实例中;- 最终返回的
AliOSSUtils对象,就拥有了 OSS 的配置信息,后续调用它的uploadFile方法时,就能直接用这些配置。
③ 在aliyun-oss-spring-boot-autoconfigure模块中的resources下,新建自动配置文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
java
com.aliyun.oss.AliOSSAutoConfiguration
1). 为什么需要这个文件?
这个文件的作用就是:给 SpringBoot "递一张地址单",告诉它 "去加载 com.aliyun.oss.AliOSSAutoConfiguration 这个配置类",不管这个类在哪个包,SpringBoot 都会找到它。
2). 这个文件的具体作用(串起整个流程)
- 你在这个文件里写了
com.aliyun.oss.AliOSSAutoConfiguration(配置类的全限定名); - 当用户的项目引入你的 aliyun-oss-spring-boot-starter 依赖后,这个文件会被打包到 Starter 的 Jar 包中;
- 用户启动 SpringBoot 项目时,SpringBoot 会自动扫描所有依赖 Jar 包中的
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件; - 读取到你写的
AliOSSAutoConfiguration类名,然后加载这个配置类; - 加载后,
AliOSSAutoConfiguration里的逻辑(注册 AliOSSProperties、创建 AliOSSUtils Bean)就会执行,最终用户能直接 @Autowired 使用 AliOSSUtils。