SpringBoot原理

本文基于黑马课程介绍了SpringBoot的部分原理

SpringBoot原理

起步依赖原理

在SpringBoot中经常需要导入一些起步依赖,如spring-boot-starter-webspring-boot-starter-aopspring-boot-starter-testmybatis-spring-boot-starter等等

这些起步依赖会通过Maven的依赖传递,将与之相关的常见依赖自动传递进来

spring-boot-starter-web为例,从下方图中可以看出这些起步依赖传递进来了着许多依赖

image-20250923223439250

image-20250923223559381

所以简单来说,起步依赖的原理就是依赖传递

自动配置原理

什么是自动配置?在SpringBoot中,有时你可以在未在项目中声明的前提下,直接使用@Autowired自动注入一个第三方工具类,原因就是SpringBoot自动配置好了使用的第三方bean

实现方案

SpringBoot是如何实现的会在后面解释,这里说明SpringBoot可以有什么实现方式

如果第三方提供的工具类上已经加上了@Component的注解,那么引入后是不是一定就能直接用呢?

答案是不行,因为项目的启动类中@SpringBootApplication注解虽然具备组件扫描功能,但是默认扫描的范围是启动类所在包及其子包,所以扫描不到第三方包也就无法将其进行管理

实现方式一

在启动类上加上@ComponentScan注解手动指定扫描访问,从而找到有@Component的第三方注解

实现方式二

在启动类上加上@Import注解,用该注解导入工具类时,无需第三方工具类有@Component注解

一般第三方工具包会帮你把要用到的工具类封装进一个统一配置类或接口实现类中

所以直接导入对应配置类或接口实现类即可,无需一个个导入工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 第三方已封装好的配置类
@Configuration
public class HeaderConfig {
    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}

// 第三方已封装好的接口实现类
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.HeaderConfig"};
    }
}

还有的第三方会进一步将上述内容封装进一个注解类,此时只需要在自己项目上的启动类上加上第三方封装好的注解即可

1
2
3
4
5
6
// 第三方封装好的注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig {
}

源码跟踪

黑马课程讲解:145. 原理篇-SpringBoot原理-自动配置-源码跟踪_哔哩哔哩_bilibili

源码跟踪技巧:

在跟踪框架源码的时候,一定要抓住关键点,找到核心流程。一定不要从头到尾一行代码去看,一个方法的去研究,一定要找到关键流程,抓住关键点,先在宏观上对整个流程或者整个原理有一个认识,有精力再去研究其中的细节。

在idea中可以快速定位当前查看类的所在位置

image-20250925230501904

源码跟踪:

自动配置原理源码跟踪流程图

自动配置的核心就在@SpringBootApplication注解上,SpringBootApplication这个注解底层包含了3个注解,分别是:

  • @SpringBootConfiguration
  • @ComponentScan
  • @EnableAutoConfiguration

@EnableAutoConfiguration这个注解才是自动配置的核心

  • 它封装了一个@Import注解,Import注解里面指定了一个ImportSelector接口的实现类
  • 在这个实现类中,重写了ImportSelector接口中的selectImports()方法
  • 而selectImports()方法中会去读取两份配置文件,并将配置文件中定义的配置类做为selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给Spring的IOC容器进行管理
  • @Enable 开头的注解底层,它就封装了一个注解 import 注解,它里面指定了一个类,是 ImportSelector 接口的实现类。在实现类当中,我们需要去实现 ImportSelector 接口当中的一个方法 selectImports 这个方法。这个方法的返回值代表的就是我需要将哪些类交给 spring 的 IOC容器进行管理
  • 此时它会去读取两份配置文件,一份儿是 spring.factories,另外一份儿是 autoConfiguration.imports。而在 autoConfiguration.imports 这份文件当中,它就会去配置大量的自动配置的类

原理就是在配置类中定义一个@Bean标识的方法,而Spring会自动调用配置类中使用@Bean标识的方法,并把方法的返回值注册到IOC容器中

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理

从源码中可以看见,INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的配置类非常多,而且每个配置类中又可以定义很多的bean,那这些bean都会注册到Spring的IOC容器中吗?

并不会。 在声明bean对象时,上面有加一个以 @Conditional 开头的注解,这种注解的作用就是按照条件进行装配,只有满足条件之后,才会将bean注册到Spring的IOC容器中

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

为什么第三方类能被自动注入

核心原因是SpringBoot 的自动配置机制与第三方库的 Starter 支持共同作用的结果

很多主流第三方工具(如 MyBatis、Redis、RabbitMQ 等)会提供专门的spring-boot-starter-xxx依赖(Starter)。这些 Starter 中包含了自动配置类(AutoConfiguration),这些配置类会在应用启动时被 Spring Boot 自动扫描并执行,通过@Bean注解将第三方工具的核心类(如RedisTemplateSqlSessionFactory)注册为 Spring 容器中的 Bean。

image-20250926091430973

例如,Redis 的 Starter(spring-boot-starter-data-redis)中包含RedisAutoConfiguration类,其内部通过@Bean声明了RedisTemplate的实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class RedisAutoConfiguration {
    // 自动注册RedisTemplate为Bean
    @Bean
    @ConditionalOnMissingBean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        return template;
    }
}

自定义starter

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

如何自己自定义一个starter?

案例演示:阿里云OSS操作工具类的自动配置

需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类 AliyunOSSOperator 的自动配置

目标:引入起步依赖引入之后,要想使用阿里云OSS,注入 AliyunOSSOperator 直接使用即可

当前使用方式:

1.在项目的pom.xml中导入阿里云oss提供的所有依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--阿里云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>

2.在application.yml中配置阿里云oss的配置信息

1
2
3
4
5
#阿里云oss配置
aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    bucketName: java422-web-ai

3.定义实体类封装配置信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.itheima.utils;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

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

4.定义工具类AliyunOSSOperator

  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
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
package com.itheima.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Component
public class AliyunOSSOperator {

    @Autowired
    private AliyunOSSProperties aliyunOSSProperties;

    /**
     * 文件上传
     */
    public String upload(byte[] content, String originalFilename) throws Exception {
        String endpoint = aliyunOSSProperties.getEndpoint();
        String bucketName = aliyunOSSProperties.getBucketName();

        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量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"));
        //根据原始文件名originalFilename, 生成一个新的不重复的文件名
        String newFileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = dir + "/" + newFileName;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        //文件上传
        try {
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content));
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

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


    /**
     * 查询文件列表
     */
    public List<String> listFiles() throws Exception {
        String endpoint = aliyunOSSProperties.getEndpoint();
        String bucketName = aliyunOSSProperties.getBucketName();
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 指定前缀,例如exampledir/object。
        String keyPrefix = null;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        try {
            // 列举文件。如果不设置keyPrefix,则列举存储空间下的所有文件。如果设置keyPrefix,则列举包含指定前缀的文件。
            ObjectListing objectListing = ossClient.listObjects(bucketName, keyPrefix);
            List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
            if(sums != null && !sums.isEmpty()){
                return sums.stream().map(OSSObjectSummary::getKey).collect(Collectors.toList());
            }
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }

    /**
     * 删除指定对象
     */
    public void deleteFile(String objectName) throws Exception {
        String endpoint = aliyunOSSProperties.getEndpoint();
        String bucketName = aliyunOSSProperties.getBucketName();

        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        try {
            // 删除文件或目录。如果要删除目录,目录必须为空。
            ossClient.deleteObject(bucketName, objectName);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

5.其他地方要使用阿里云OSS,注入工具类,再使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Slf4j
@RestController
public class UploadController {
    @Autowired
    private AliyunOSSOperator aliyunOSSOperator;

    /**
     * 文件上传
     */
    @PostMapping("/upload")
    public Result upload(MultipartFile file) throws Exception {
        log.info("上传文件:{}",file.getOriginalFilename());
        //调用aliyun OSS进行文件上传
        String url = aliyunOSSOperator.upload(file.getBytes(), file.getOriginalFilename());
        //返回结果
        return Result.success(url);
    }
}
目标思路:
  1. 创建自定义starter模块 aliyun-oss-spring-boot-starter(进行依赖管理)
  2. 创建autoconfigure模块 aliyun-oss-spring-boot-autoconfigure,在starter中引入autoconfigure
  3. 在autoconfigure模块aliyun-oss-spring-boot-autoconfigure中完成自动配置,并定义自动配置文件META-INF/spring/xxxx.imports

步骤:

1.创建一个新的模块(注意不要在当前项目中创建):

image-20250926093633859

2.然后无需勾选任何依赖项,点击创建:

image-20250926093800463

3.创建好项目后,可以把项目中除了pom.xml的文件全部删除:

image-20250926094102994

4.打开pom.xml文件,将版本改为与你的项目相同:

image-20250926094325495

5.再创建一个模块(注意不要在当前项目中创建),然后同样不用选依赖:

image-20250926094631471

6.可以把该模块中除src目录和pom.xml文件以外的内容全部删除,src下的test目录也可以删除,主目录里的启动类和配置文件也都能删除,并不影响

7.回到starter模块的pom.xml文件,将新创建的autoconfigure导入

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>

8.将阿里云oss所需的所有依赖导入autoconfigure的pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--阿里云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>

9.在com.aliyun.oss的包下创建AliyunOSSOperator.javaAliyunOSSProperties.java

AliyunOSSOperator

 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 com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;

import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

// 不再需要@Component注解
public class AliyunOSSOperator {
    private AliyunOSSProperties aliyunOSSProperties;
    // 通过构造函数注入配置属性,而不是使用@Autowired注解
    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完整路径,例如2024/06/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

 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
package com.aliyun.oss;

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

// 手动写出get和set方法,而不是使用@Data注解,减少不必要的依赖
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
    private String 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;
    }
}

10.工具类和属性类已经有了,现在需要创建一个自动配置类AliyunOSSAutoConfiguration.java将他们配置到一起交给IOC容器管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.aliyun.oss;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 自动配置类
 */
@EnableConfigurationProperties(AliyunOSSProperties.class) // 启用配置属性绑定,否则AliyunOSSProperties不会被注册为Bean
@Configuration // 标记这是一个配置类
public class AliyunOSSAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean // 如果容器中没有AliyunOSSOperator的Bean,则创建一个新的
    public AliyunOSSOperator aliyunOSSOperator(AliyunOSSProperties aliyunOSSProperties) {
        return new AliyunOSSOperator(aliyunOSSProperties);
    }
}

11.此时并没有实现自动配置的功能,因为SpringBoot依旧扫描不到这个自动配置类,为了让SpringBoot扫描到这个自动配置类,要在src/main/resources目录下创建META-INF包,再在其中创建spring包,再在spring包下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(注意这几个步骤中一个字也不能错),最后在这个创建文件中写上刚刚定义的自动配置类的全类名com.aliyun.oss.AliyunOSSAutoConfiguration

spring包下的文件名:

1
org.springframework.boot.autoconfigure.AutoConfiguration.imports

全类名:

1
com.aliyun.oss.AliyunOSSAutoConfiguration

12.此时在项目中导入aliyun-oss-spring-boot-starter起步依赖后,就能使用@Autowired对AliyunOSSOperator进行自动注入了,不过依旧需要在application.yml配置好自己的账号信息才能使用

1
2
3
4
5
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
image-20250926173244198
1
2
3
4
5
6
7
#应该填入自己阿里云oss的对应信息
#阿里云OSS
aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    bucketName: java-ai
    region: cn-beijing

阿里云OSS使用教程可见黑马讲义:09-后端Web实战(员工管理) - 飞书云文档

或者黑马视频课程:106. 文件上传-阿里云OSS-准备工作_哔哩哔哩_bilibili

本站于2025年3月26日建立
使用 Hugo 构建
主题 StackJimmy 设计