SpringBoot原理

配置优先级

  • SpringBoot中支持三种格式的配置文件:
1
2
3
4
5
6
7
8
9
10
// application.properties
server.port = 8081

// application.yaml
server:
port: 8082

// application.yml
server:
port: 8083

如果同时存在以上三份配置文件,那么配置生效的优先级为:properties > yml > yaml

不过,开发中尽量统一配置文件的格式,只使用同一种格式的配置文件

  • SpringBoot除了支持文件属性配置,还支持Java系统属性命令行参数的方式进行属性配置
    • Java系统属性:-Dserver.port=9000
    • 命令行参数:--server.port=10010

Snipaste_2025-08-16_20-49-17

Snipaste_2025-08-16_20-50-45

命令行参数优先级大于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
2
3
4
5
// 在bean类中添加注解@Scope
@Scope("prototype")
@RequestMapping("/depts")
@RestController
public class DeptController {...}

如果不指定作用域,默认IOC容器内的同名称bean对象都是同一个

获取IOC容器内的Bean对象:

1
2
3
4
5
6
7
@Autowired
private ApplicationContext applicationContext;

@Test
public void getIOCBean() {
applicationContext.getBean("deptController");
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class ThirdPartyConfig {
@Bean
public ThirdPartyService thirdPartyService() {
return new ThirdPartyService();
}

@Bean
public TargetBean targetBean(@Autowired DependencyBean dependencyBean) { // @Autowired可以省略,这里加上方便理解
TargetBean target = new TargetBean();
target.setDependency(dependencyBean);
return target;
}
}

在 @Bean 方法中,返回值类型就是你想要注册到IOC容器中的Bean类型

当Bean依赖其他Bean时,可以通过方法参数注入依赖(Spring会自动从IOC容器中找到对应的bean对象并传入)

建议:虽然可以直接在启动类中通过@Bean添加第三方bean,但是为了方便第三方bean的管理,建议单独创建一个配置类(@Configuration),在配置类中对第三方的bean进行统一管理

起步依赖

SpringBoot中的起步依赖就是Maven中的依赖传递,通过引入起步依赖将所需要的所有依赖自动引入

Snipaste_2025-08-19_07-08-04

像包名中带有xxx-starter的包,大部分就是起步依赖包

自动配置(面试重点)

  • SpringBoot的自动配置就是当spring项目启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作

实现方案

环境搭建:先在项目中导入第三方包:

1
2
3
4
5
6
7
<!--    第三方依赖    -->
<dependency>
<groupId>com.example</groupId>
<artifactId>itheima-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

方案一:@Component + @ComponentScan

如果第三方包下声明了bean(带有@Component的类),在注入时,还要考虑组件扫描的范围(默认扫描启动类所在包及其子包)。所以需要修改扫描范围:

1
2
3
4
5
6
7
8
9
@ComponentScan(basePackages = {"com.example","com.itheima"})
@SpringBootApplication
public class SpringbootWebConfigApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfigApplication.class, args);
}

}

com.example包下有一个工具类(TokenParser)被声明为了bean

@ComponentScan(basePackages = ...)注解可以换成@SpringBootApplication(scanBasePackages = ...)

有可能会存在IDEA无法识别到目标包,但是实际上是可以运行的

1
2
3
4
5
6
7
@Autowired
private TokenParser tokenParser;

@Test
public void testTokenParser() {
tokenParser.parse();
}

方案一的问题:首先需要对方被声明为bean才可以使用,其次由于扫描范围的增加,导致配置繁琐且性能降低


方案二:通过@Import导入。@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种

  1. 导入普通类
  2. 导入配置类(@Configuration)
1
2
3
4
5
6
7
8
// 		 导入普通类           导入配置类
@Import({TokenParser.class, HeaderConfig.class})
@SpringBootApplication
public class SpringbootWebConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfigApplication.class, args);
}
}
  1. 导入ImportSelector接口的实现类(第三方可以决定哪些类能够被加载到IOC容器中,且有批量导入的功能)

    • 第三方依赖中有一个类实现了ImportSelector接口

      1
      2
      3
      4
      5
      public class MyImportSelector implements ImportSelector {
      public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      return new String[]{"com.example.HeaderConfig"};
      }
      }
    • 使用的时候直接导入这个类即可

      1
      2
      3
      4
      5
      6
      7
      @Import({MyImportSelector.class})
      @SpringBootApplication
      public class SpringbootWebConfigApplication {
      public static void main(String[] args) {
      SpringApplication.run(SpringbootWebConfigApplication.class, args);
      }
      }
  2. 根据第三种方式,第三方还可以提供注解类简化使用方的导入操作(封装@Import注解)

    • 注解类如下:

      1
      2
      3
      4
      5
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      @Import(MyImportSelector.class)
      public @interface EnableHeaderConfig {
      }
    • 使用方不再需要使用@Import注解并指定目标类了,可以直接使用提供的注解类进行导入:

      1
      2
      3
      4
      5
      6
      7
      @EnableHeaderConfig
      @SpringBootApplication
      public class SpringbootWebConfigApplication {
      public static void main(String[] args) {
      SpringApplication.run(SpringbootWebConfigApplication.class, args);
      }
      }

源码跟踪

源码跟踪的着重点在启动类上,通过@SpringBootApplication注解查看被层层封装的源码。该注解标识在SpringBoot工程引导类上,是SpringBoot中最最最重要的注解。该注解由三个部分组成:

  1. @SpringBootConfiguration:该注解与@Configuration注解作用相同,用来声明当前也是一个配置类
  2. @ComponentScan:组件扫描,默认扫描引导类所在包及其子包
  3. @EnableAutoConfiguration:SpringBoot实现自动化配置的**核心注解**

Snipaste_2025-08-19_08-43-09

Snipaste_2025-08-19_08-45-12

Snipaste_2025-08-19_08-47-03

Snipaste_2025-08-19_08-50-03

Snipaste_2025-08-19_08-52-08

Snipaste_2025-08-19_08-55-59

@Conditional注解

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring IOC容器中
  • 位置:方法、类
  • @Conditional本身是一个父注解,派生出大量的子注解,以下为常见子注解:
    • @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器
    • @ConditionalOnMissionBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器
    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HeaderConfig {

@Bean
@ConditionalOnClass(name = "io.jsonwebtoken.Jwts") // 判断环境中是否有对应的字节码文件,如果有则创建bean
public HeaderParser headerParser(){
return new HeaderParser();
}

@Bean
@ConditionalOnProperty(name = "myname", havingValue = "itheima")
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}

自己定义自动配置类的核心是定义自动配置类,将自动配置类配置在

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件中

自定义starter

  • 场景:在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot的starter(包含了起步依赖自动配置的功能)

Snipaste_2025-08-19_09-42-29

  • 需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSOperator的自动配置
  • 目标:引入起步依赖后,想要使用阿里云OSS,注入AliyunOSSOperator直接使用即可
  • 步骤:
    1. 创建aliyun-oss-spring-boot-starter模块
    2. 创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块
    3. 在aliyun-oss-spring-boot-autoconfigure模块中定义自动配置功能,并定义自动配置文件META-INF/spring/xxxx.imports

aliyun-oss-spring-boot-autoconfigure下的依赖(就是业务逻辑代码中需要的依赖 + Springboot起步依赖):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 阿里云OSS依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>

aliyun-oss-spring-boot-starter下的依赖(引入自动配置模块的依赖即可):

1
2
3
4
5
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

阿里云OSS配置模块

首先是业务逻辑的相关代码,直接从Tlias系统中复制过来即可:
AliyunOSSOperator,要把@Component注解去掉,因为不通过扫描组件的方式引入bean对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class AliyunOSSOperator {
private final AliyunOSSProperties aliyunOSSProperties;

public AliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties) {
this.aliyunOSSProperties = aliyunOSSProperties;
}

public String upload(byte[] content, String originalFilename) throws Exception {
String endpoint = aliyunOSSProperties.getEndpoint();
String bucketName = aliyunOSSProperties.getBucketName();
String region = aliyunOSSProperties.getRegion();
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();

// 填写Object完整路径,例如202406/1.png。Object完整路径中不能包含Bucket名称。
//获取当前系统日期的字符串,格式为 yyyy/MM
String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
//生成一个新的不重复的文件名
String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = dir + "/" + newFileName;

// 创建OSSClient实例。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();

try {
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content));
} finally {
ossClient.shutdown();
}

return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName;
}

}

AliyunOSSProperties属性配置类也同样去掉@Component,只留一个引入配置文件属性的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.aliyun.oss;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
private String endpoint;
private String bucketName;
private String region;

public AliyunOSSProperties() {
}

public AliyunOSSProperties(String endpoint, String bucketName, String region) {
this.endpoint = endpoint;
this.bucketName = bucketName;
this.region = region;
}

public String getEndpoint() {
return endpoint;
}

public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}

public String getBucketName() {
return bucketName;
}

public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}

public String getRegion() {
return region;
}

public void setRegion(String region) {
this.region = region;
}

@Override
public String toString() {
return "AliyunOSSProperties{" +
"endpoint='" + endpoint + '\'' +
", bucketName='" + bucketName + '\'' +
", region='" + region + '\'' +
'}';
}
}

最后就是将工具类通过AliyunOSSAutoConfiguration配置类保存到IOC容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* AliyunOSS的自动配置类
*/

@EnableConfigurationProperties(AliyunOSSProperties.class)
@Configuration
public class AliyunOSSAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties) {
return new AliyunOSSOperator(aliyunOSSProperties);
}
}

最重要的一步,在resource目录下创建META-INF/Spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(文件名、路径不能错),并引入配置类:

1
com.aliyun.oss.AliyunOSSAutoConfiguration

这样当其他项目使用时SpringBoot就会自动配置并将工具类作为bean对象保存到IOC容器中

测试

在测试的项目中引入依赖:

1
2
3
4
5
6
<!--    引入自定义starter    -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

其他依赖会通过依赖传递的方式引入

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class UploadController {

private final AliyunOSSOperator aliyunOSSOperator;

public UploadController(AliyunOSSOperator aliyunOSSOperator) {
this.aliyunOSSOperator = aliyunOSSOperator;
}

@PostMapping("/upload")
public String upload(MultipartFile image) throws Exception {
//上传文件到阿里云 OSS
String url = aliyunOSSOperator.upload(image.getBytes(), image.getOriginalFilename());
System.out.println(url);
return null;
}
}