Marvel-Site Marvel-Site
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

Marvel

吾必当乘此羽葆盖车
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • Java

  • 计算机基础

  • 框架|中间件

    • Spring

      • Spring-控制反转IOC
      • Spring-面向切面编程AOP
      • Spring常见面试问题
      • SpringMVC常见面试问题
      • SpringMVC分析
      • SpringBoot自动配置原理
        • 1. SpringBoot 特点
        • 2. 组件添加
          • 2.1 @Configuration
          • 2.2 @ComponentScan
          • 2.3 @Import
          • 2.4 @Conditional
        • 3. 原生配置文件导入
        • 4. 配置绑定
        • 5. SpringBoot 自动装配原理理解
        • 6. 核心注解 @SpringBootApplication
        • 7. @SpringBootConfiguration
        • 8. @EnableAutoConfiguration
          • 8.1 @AutoConfigurationPackage
          • 8.2 AutoConfigurationImportSelector
        • 9. @ConditionalOnXXX
      • Spring三级缓存解决循环依赖
      • Spring事务测试分析
    • MyBatis

    • MySQL

    • Redis

    • 消息队列

    • Zookeeper

    • Git

    • Maven

    • Gradle

  • 架构

  • 后端
  • 框架|中间件
  • Spring
Marvel
2022-07-16
目录

SpringBoot自动配置原理

# SpringBoot 自动配置原理

# 1. SpringBoot 特点

依赖管理

  • 父项目做依赖配置
  • 开发导入 starter 场景启动器
  • 无需关注版本号,自动版本仲裁
  • 可以修改版本号

自动配置

  • 自动配置 Tomcat
  • 自动配置 SpringMVC
  • 自动配置 Web 常见功能,如:字符编码问题
  • 默认的包结构
    • 主程序所在目录及其子目录下无需配置包扫描
    • 或者使用其他包扫描注解扫描其他位置
  • 各种配置拥有默认值
    • 默认配置最终都映射到 MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有的配置项
    • 引入哪个 Starter 就加载哪个场景的依赖
    • 自动配置功能都在 spring-boot-autoconfigure 内
  • 底层会配置好所有的组件,如果用户自己配了,则使用用户自己的配置
    • ConditionOnMissingBean

# 2. 组件添加

# 2.1 @Configuration

  • 告诉 Spring Boot 这是一个配置类,等价于 xml 配置文件,默认时单例的
  • 其本身也是一个组件
  • proxyBeanMethods:代理 bean 方法
    • @Configuration(proxyBeanMethods = true):默认为 true
    • true:full 模式,内部方法调用时都会去容器中找组件,保持组件单例
    • false:lite 模式,内部方法调用时都会创建新的组件
// 告诉 Spring 这是一个配置类,等价于配置文件
@Configuration
public class MyConfig {
    // 给容器中添加组件,以方法名作为组件的 id,返回类型就是组件类型,返回值就是组件实例
    @Bean
    public User user01() {
        return new User("zqc1");
    }
    
    @Bean
    public User user02() {
        return new User("zqc2");
    }
    
    @Bean("user03")  // 直接指明 bean 的名称
    public User xxx() {
        return new User("zqc3");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

注意:单例 Bean 在容器中也可以有多个相同类型的 Bean,不过他们的 BeanName 不同。

上面这段代码供配置了 4 个组件:myConfig、user01、user02、user03

⭐ proxyBeanMethods

当 proxyBeanMethods = true 时,容器中只有一个 pet。

当 proxyBeanMethods = false 时,容器中只有一个 pet,user() 方法在 setPet 时会重新创建一个新的 pet,此时容器中的 pet 不是 user 所拥有的 pet。

@Configuration(proxyBeanMethods = true)
public class MyConfig {
    @Bean
    public User user() {
        User zqc = new User("zqc");
        zqc.setPet(pet());
        return zqc;
    }

    @Bean
    public Pet pet() {
        return new Pet("cat");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.2 @ComponentScan

扫描该报下的所有带 @Component、@Controller、@Service、@Repository 等注解的类,并将其中的组件注册到容器当中。

# 2.3 @Import

给容器中自动创建出该类型的组件

@Import({User.class, Pet.class })
@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);
        System.out.println(1);
    }

}
1
2
3
4
5
6
7
8
9
10

容器启动后,可以在容器内找到 User 和 Pet 对应的组件,默认名字是全限定类名。

"com.zqc.springboot.bean.User" -> {User@4441} 
"com.zqc.springboot.bean.Pet" -> {Pet@4447} 
1
2

# 2.4 @Conditional

条件装配:满足 Contitional 指定的条件,则进行组件注入

image-20220829125136813

举例说明:

  • ConditionalOnBean:当容器中有这个组件的时候进行注入
  • ConditionalOnMissBean:当容器中没有这个组件的时候进行注入
  • ConditionalOnClass:当容器中有某个类的时候进行注入

使用 @ConditionalOnBean 进行测试:

@Configuration
public class MyConfig {

    @Bean
    public Pet pet() {
        return new Pet("cat");
    }

    @ConditionalOnBean(name = "pet")
    @Bean
    public User user() {
        User zqc = new User("zqc");
        zqc.setPet(pet());
        return zqc;
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

此时容器启动,容器中将包含 pet 和 user 两个 bean 对象。

如果将 pet() 方法上的 @Bean 注解去掉,那么容器中将没有 pet 对象,由于条件没有满足,user 对象也不存在。

# 3. 原生配置文件导入

如果已经有 xml 配置文件,但又不像把他转成代码的形式,可以通过 @ImportResource 注解进行导入。

@Configuration
@ImportResource("classpath:beans.xml")
public class MyConfig {
	...
}
1
2
3
4
5

# 4. 配置绑定

@ConfigurationProperties

如何使用 Java 读取到 properties 文件中的内容,并且把它封装到 JavaBean 中,以供随时使用:

// 只有在容器中的组件,才会拥有 SpringBoot 拥有的强大功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private Integer price;
    ...
}
1
2
3
4
5
6
7
8

配置文件

mycar.brand="BYD"
mycar.price=100000
1
2

当容器运行完成后,里面包含 car 的 bean 对象,且 brand 和 price 属性均有值。

也可以不使用 @Component 注解,可以在配置类中通过 @EnableConfigurationProperties(Car.class) 告诉 Spring 容器要把哪个组件自动注入到容器中。比如第三方的包,我们无法对其进行修改,此时可以使用 @EnableConfigurationProperties 注解。

# 5. SpringBoot 自动装配原理理解

pom.xml

SpringBoot 项目有一个 pom.xml,我们需要在里面配置一些需要的启动器 starter,后面会自动将这些 starter 的依赖装配进来,比如说spring-boot-starter-web。

在这个 pom.xml 中也可以看到它的父项目spring-boot-parent,它的父项目是 spring-boot-dependencies,里面定义了当前 springboot 版本所自动装配依赖的版本,几乎包含了所有我们需要的依赖版本,这样我们在选择依赖的时候就不需要指定版本了。

启动器

  • Springboot 的启动场景

  • spring-boot-starter:springBoot 的核心配置依赖

    • Core starter, including auto-configuration support, logging and YAML
  • 比如,spring-boot-starter-web 会帮我们自动导入 web 环境所有的依赖

    • Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container
  • 需要什么功能,就只需要找到对应的启动器。

主启动类

SpringBoot 有一个主启动类,需要注解 @SpringBootApplication,自动装配的实现主要依赖这个注解来实现。其中包含了许多其他的注解,而这些注解实现的主要功能就是:

  • 扫描启动类目录下的文件,如注解了 @Component、@Service、@Controller 的 bean。
  • 加载自己的配置
  • 自动加载的配置,通过一系列的操作读取 META-INF/spring.factorier 下的所有的自动加载配置类,对他们进行遍历加载。但并不是所有的类都会进行加载,在 xxxAutoConfiguration 中会有 @ConditionalOnXXX,只有符合条件的才会被加载

总结

  • SpringBoot 先加载所有的自动配置类,xxxAutoConfiguration

  • 每个自动配置类会按照条件进行生效,通常,生效后会绑定配置文件指定的值,xxxProperties,xxxProperties 与配置文件进行了绑定

  • 生效的配置类就会给容器中装配了很多组件

  • 只要容器中有这些组件,就有了对应的功能

  • 如果用户定义了自己了自己的组件,则以用户配置的优先

  • 定制化配置

    • 用户创建新的 bean 组件,替换底层组件
    • 用户去看这个组件获取了配置文件哪些值,去修改就可以了,就是修改 properties 或 xml 文件。

xxxAutoConfiguration --> 组件 ---> xxxProperties 里面取值 --> application.properties

# 6. 核心注解 @SpringBootApplication

SpringBoot 的核心注解:@SpringBootApplication 是合成注解,它可以看作 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan注解的集合,这三个注解的作用分别是:

  • @EnableAutoConfiguration:启动SpringBoot的自动配置
  • @SpringBootConfiguration:允许在上下文中注册额外的bean或者导入其他配置类
  • @ComponentScan:扫描被 @Component(@Service、@Controller)注解的bean,注解默认会扫描启动类所在的包下所有的类,可以自定义不扫描某些 bean。

SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到Spring容器,并执行类定义的各种操作。对于外部jar来说,只需按照 SpringBoot 定义的标准,就能将自己的功能装置进 Spring Boot。

# 7. @SpringBootConfiguration

...
@Configuration
public @interface SpringBootConfiguration {
	@AliasFor(annotation = Configuration.class)
	...
}
1
2
3
4
5
6

包含注解 @Configuration,表示他是一个 Spring 的配置类。

...
@Component
public @interface Configuration {
	...
}
1
2
3
4
5

包含注解 @Component,表示他是 Spring 的组件

# 8. @EnableAutoConfiguration

自动配置的注解

...
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
    ...
}
1
2
3
4
5
6

主要包含 @AutoConfigurationPackage 和 @Import({AutoConfigurationImportSelector.class})

# 8.1 @AutoConfigurationPackage

自动配置包,指定了默认包规则

...
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
1
2
3
4

可以看到,它是将AutoConfigurationPackages.Registrar.class 类注入到容器中,这个 Registrar 类可以帮我们导入一系列组件。

将一个指定包下的所有组件注册到 register,这个包其实就是主启动类所在的包下。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	
    // AnnotationMetadata metadata 注解的元信息,可以看到通过它读取注解中的属性值
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

⭐ metadata 说明

注解的元信息,可以看到通过它读取注解中的属性值。代表这个注解标在哪(就是指 AutoConfigurationPackage 注解),这个注解就是标在 SpringBoot 启动类上。

⭐ PackageImport(metadata).getPackageName() 说明

metadata 可以理解为指向主类,所以这段代码的意思就是获取主类所在的包的位置。

image-20220829143948036

# 8.2 AutoConfigurationImportSelector

自动配置导入选择 @Import({AutoConfigurationImportSelector.class})

下面看看类 AutoConfigurationImportSelector

利用 selectImports()方法得出需要导入哪些组件的数组,selectImports() 中的主要方法为 getAutoConfigurationEntry(),给容器中批量导入一些组件。

public class AutoConfigurationImportSelector implements ... {

	...
    // 到底给容器中导入哪些
    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
        // 通过 getAutoConfigurationEntry() 批量导入一些组件
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
    
    ...
        
    // 批量导入一些组件
	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		...
        // 获取所有候选配置
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		...
	}
    ...
}
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

通过方法 getCandidateConfigurations() 来获取所有候选配置,可以看到该行代码运行完,configuration 中包含了 124 个数值,这 124 个候选组件默认导入到容器当中的。

image-20220829145214167

下面详细看看 getCandidateConfigurations() 方法

// 获取所有候选配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 获取配置
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(),  // 这里就是 EnableAutoConfiguration.class
        getBeanClassLoader());
    
    // 断言这个配置是否为空
    // 如果为空,说明没有读取到 spring.factories
    Assert.notEmpty(configurations, 
                    "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    // SpringBoot的启动类标注了这个注解 (@SpringBootApplication 包含 @EnableAutoConfiguration)
    return EnableAutoConfiguration.class;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

使用 SpringFactoriesLoader.loadFactoryNames() 方法,该方法又调用了 loadSpringFactories() 得到所有的组件

// 获取所有的加载配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 这里的 factoryType 就是 EnableAutoConfiguration.class
    // 所以,就是启动类下的所有资源被导入
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// 加载配置文件
// 上面的方法调用了这个方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // FACTORIES_RESOURCE_LOCATION:"META-INF/spring.factories",里面包含了所有的自动配置类
        Enumeration<URL> urls = (classLoader != null ?
                                 // 从项目资源中获取
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 // 从系统资源中获取
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 遍历了所有自动配置,封装成 Properties 供我们使用
        while (urls.hasMoreElements()) {  // 判断有没有更多的元素
            URL url = urls.nextElement();  // 获取 url
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
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

loadSpringFactories() 方法就是通过扫描 META-INF/spring.factories 位置获取所有自动配置类。也就是在这个配置文件中写死了 Spring 启动时要加载的所有配置类。虽然全部加载进来了,但是不一定会生效,会按需开启。

META-INF/spring.factories:自动配置的核心文件,它在 spring-boot-autoconfigure-版本\META-INF\spring.factories下,所有的自动配置类都在这里

# Initializers 初始化
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners  监听
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners  自动选择导入的包
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

获取所有符合条件的类的全限定类名,这些类需要被加载到IoC容器中。

# 9. @ConditionalOnXXX

为什么导入了这么多自动配置没有生效?需要导入对应的 start 才能生效?

通过注解 @ConditionalOnXXX 判断条件是否满足,如果其中的条件都满足才会生效。当没有导入对应的 starter,就没有对用的 jar 包,此时 @ConditionalOnXXX 就不会生效。

编辑 (opens new window)
#Spring#SpringBoot#源码#框架
上次更新: 2023/08/20, 21:21:52
SpringMVC分析
Spring三级缓存解决循环依赖

← SpringMVC分析 Spring三级缓存解决循环依赖→

最近更新
01
位运算
05-21
02
二叉树
05-12
03
Spring三级缓存解决循环依赖
03-25
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Marvel
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式