侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

SpringBoot 自动配置原理

再见理想
2022-12-05 / 0 评论 / 0 点赞 / 618 阅读 / 2,736 字

一,什么是 SpringBoot 自动配置?

SpringBoot 自动配置,英文是 Auto-Configuration,它是指基于你引入的依赖 Jar 包,对 SpringBoot 应用进行自动配置。它为 SpringBoot 框架的开箱即用提供了基础支撑。

配置类定义 Configuration Class:

  • 广义的配置类:被注解 @Component 直接或间接修饰的某个类,即我们常说的 Spring 组件,其中包括了 @Configuration 类;
  • 狭义的配置类:特指被注解 @Configuration 所修饰的某个类,又称为 @Configuration 类;

自动配置不同于自动装配,自动配置是 Auto-Configuration,针对的是 SpringBoot 中的配置类;

而自动装配是 @Autoware,针对是的 Spring 中的依赖注入。

二,Springboot 自动配置实例

实例:Redis 的自动配置

  1. 引入依赖;

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置 Redis 服务器连接信息;

  3. @Autoware 引入 RedisTemplate 对应,即可直接使用;

我们通过 maven 引入一个 starter 外,springboot 就自动完成了 Redis 的配置,将相关的 Bean 对象注册到 IOC 容器中,那么 Springboot 是如何做到这一点的?

三,Springboot 的启动流程(简化版)

自动配置作为Springboot 启动流程的一部分,我们有必要从全局的角度去理解它。启动流程可以理解由5步骤组成,简化版代码:

public static void run(class<?> primaryClass){
    //1。创建一个ApplicationContext实例,即我们常说的IoC容器
    ApplicationContext context = createApplicationContext()
    //2。将主类(primaryClass)注册到IOC器中 (简单但重要的第一步)
    loadSourceClass(context,primaryclass);
    //3.递归加载并处理所有的配置类
	processConfigurationClasses(context);
    //4.实例化所有的单例Bean(singleton Bean)
	instantiateSingletonBeans(context);
    //5。如果是web应用,则启动web 服务器(例如Tomcat)
	startWebServer(context);
}

下面主要讲解下第三步,加载并处理所有的配置类。

四,Springboot 加载配置类流程

加载并处理所有的配置类 processConfigurationClasses(context),简化版代码:

public static void processConfigurationCLasses (ApplicationContext context) {
    //1。 首先从 IOC 容器中取出当前存在的源配置类
    Class<?> sourceConfigurationClass = getSourceConfigurationClass(context);
    //2。 创建一个配置类解析器,然后递归加载并处理应用中所有的配置类
    ConfigclassParser parser = new ConfigclassParser(context);
    parser.parse(sourceConfigurationClass);
    //3.1 向IoC需中注顺@Bean方法对应的		
    BeanDefinitionToadBeanDefinitionsFromBeanMethods(parser.configurationClasses);
    //3.2 向IoC谷器中注册ImportBeanDefinitionRegistrar导入的
    BeanDefinitionloadBeanDefinitionsFromRegistrars(parser.configurationClasses);
}

首先从 IOC 容器中取出当前存在的源配置类,然后递归处理,处理流程如下:

public void parse(Class<?> configClass){
    //1.处理 @ComponentScan: 根据 @ComponentScan扫描得到的 package,得到一系列配置类
    if (hasComponentScan(configClass)) {
		for (class<?> clazz : doScan(configClass))
            this.parse(clazz); //递归处理
    }
	//2.处理注解 @Import: 根注解 @Import,得到一系列被导入的配置类
    if (hasImportedClasses(configClass)) {
		for (class<?> clazz : getImports(configClass))
            this.parse(clazz); //递归处理
    }
	//3.处理 @Bean 方法
	processBeanMethods(configClass);
    //4.处理 Import 入的 ImportBeanDefinitionRegistrar
    processRegistrars(configClass);
    //5.加入到一个全局的配置类集合中
	this.configurationClasses.add(configClass);
}

从源配置类开始,通过注解 @ComponentScan 和 @Import,不断去遍历新的配置类,知道没有新的配置类被发现为止。通过递归遍历,得到一系列配置类后,再对每个配置类进行解析处理,并将得到的 Beandefinition 注册到 IOC 容器中。

可以看到,上述过程中涉及两个重要注解,@ComponentScan 和 @Import。

4.1,SpringBoot 加载配置类的方式

@ComponentScan

@ComponentScan,是来自 Spring 框架的一个注解:

它的作用是对指定的 package 进行扫描,找到符合条件的类,默认是搜索被注解 @Component 修饰的配置类;

通过属性 basePackages 或 basePackageClasses,来指定要进行扫描的 package;

如果未指定 package,则默认扫描当前 @ComponentScan 所修饰的类所在的 package

@Import

@lmport,是来自 Spring 框架的一个注解,它的作用是提供了一种显式地从其它地方(第三方 Jar 包)加载配置类的方式,这样可以避免使用性能较差的组件扫描(Component Scan)。

支持导入的三种方式:

  • 导入普通类,这里的普通是相对于随后的两个接口而言;
    • 效果类似于被 @Configuration 所修饰。
  • 导入选择器,接口 ImportSelector 的实现类;
    • 接口 ImportSelector 中有一个 selectlmports 方法,它的返回值是一个字符串数组,数组中的每个元素分别代表一个将被导入的配置类的全限定名。利用该特性可以给 lOC 容器动态地导入多个配置类
  • 导入注册器,接口 ImportBeanDefinitionRegistrar 的实现类;
    • 通过它,我们可以手动将多个 BeanDefinition 注册到 IoC 容器中,从而实现个性化的定制。利用该特性我们可以给 loC 容器动态地导入多个 BeanDefinition

了解了Springboot 加载配置类的几种方式,那么,自动配置应该使用哪种方式实现呢?

首先排除注解@ComponentScan 。因为使用它很不方便,开发人员需要记住每个第三方Jar 包中的package 名称,然后把它们写到应用程序中;

@lmport 导入普通类方式更不合适,因为甚至要记住第三方 Jar 包中具体的类名;

@lmport 导入注册器方式,它针对的是 BeanDefinition 层面的,其设计目标是对 @Bean 方法的一个补充,并不是用来导入配置类,所以也不合适;

那就剩下了@lmport 导入选择器方式,Springboot 正是通过这种方式,实现自动配置的功能。下面从源码层面验证下这结论。

五,Springboot 自动配置源码剖析

一切都从 @SpringBootApplication 开始:

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

接下来看看 @SpringBootApplication 注解源码:

可以看到,它的元注解中,包括 @SpringBootConfiguration 注解,以及 @EnableAutoConfiguration 注解和@ComponentScan 注解。

其中,@EnableAutoConfiguration 的含义是开启了自动配置功能,它使用 @lmport 导入注册器方式导入配置类。

接下来看看 @SpringBootApplication 注解的结构图:

首先,@SpringBootApplication 修饰的类,也会被 @Configuration 间接修饰,即源配置类;

其次,Springboot 框架会对源配置类所在的 package 进行组件扫描(Component Scan);

最重要一点,SpringBoot 框架最终会导入 AutoConfigurationlmportSelector 的一个选择器 来实现自动配置;

问题来了,AutoConfigurationlmportSelector 应该如何实现,才能优雅地去发现 classpath 中 Jar 包的自动配置类

用户只需引入jar 包即可,至于jar包中有哪些自动配置类、类名是什么,等等这些信息用户都不用关心。

5.1,SpringFactories 机制

Spring 框架中有一个 SpringFactories 机制,它是 Java SPI 设计思想的延伸和扩展,它是 Spring 框架的基础机制,在 Spring 以及 SpringBoot 源码中到处可见;可以基于它来实现 SpringBoot 的自动配置功能。

它的核心逻辑是从 classpath 中读取到所有 Jar 包中的配置文件 META-IF/spring.factories,然后根据指定的 key 从配置文件中解析出对应的 value 值。

使用约定的配置文件

一,文件的路径是META-IF/spring.factories,文件内容是 “key=value1,value2,…valueN” 的格式。其中 key 是某个类的全限定名,value 是逗号隔开的多个类名;

全限定类名:就是类名全称,带包路径的用点隔开,例如: java.lang.String

二,第三方 Jar 包,负责提供配置文件,高内聚低耦合,使用 ClassLoader 来读取 classpath 中的配置文件;

三,通过类 SpringFactoriesLoader ,返回一个类名的集合,可以根据实际需求对这些类名进行下一步的处理;

5.2,AutoConfigurationlmportSelector 源码

spring-boot-starter-parent 依赖的版本为 2.2.7.RELEASE

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        //Springboot 自动配置的入口方法
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        // 1,获取annotationMetadata的注解@EnableAutoConfiguration的属性
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 2,从资源文件spring.factories中获取EnableAutoConfiguration对应的所有类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        // 3,通过在注解@EnableAutoConfiguration设置exclude属性,排除指定自动配置类
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 4,根据注解@Conditional 来判断是否乤排除某些自动配置类
        configurations = this.filter(configurations, autoConfigurationMetadata);
        // 5,触发AutoConfiguration 导入的相关事件
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

我们看看第二步中 getCandidateConfigurations 方法,它是基于SpringFactories 机制来获取第三方 Jar 包中所有自动配置类的方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 通过 SpringFactories 机制,从配置文件 spring.factories 中找出所有的自动配置类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        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;
    }

注意到从配置文件获取自动配置类时,使用的 key 是 EnableAutoConfiguration 的全限定名。

总结下 AutoConfigurationlmportSelector 是实现过程:

  1. 首先基于 SpringFactories 机制加载配置文件,通过 ClassLoader 去获取 classpath 中的配置文件 META-INF/spring.factories
  2. 找出所有的自动配置类,在所有的配置文件 METAINF/spring.factories 中,筛选出以EnableAutoConfiguration.class 为 key 的、符合条件的配置类;
  3. 根据注解 @Conditional 过滤掉不必要的自动配置类;

@Conditional 的作用是只有在特定条件满足时,才会向 IOC 容器注册指定的组件。Spring框架提供了一系列常用的 Conditional 扩展注解:

ConditionalOnBean 注解:当容器中存在指定的 Bean 时,满足条件;

ConditionalOnMissingBean 注解:当容器中不存在指定的 Bean 时,满足条件;

ConditionalOnClass 注解:当classpath 中存在指定的类时,满足条件;

ConditionalOnMissingClass 注解:当classpath 中不存在指定的类时,满足条件;

ConditionalOnProperty 注解:当指定的属性具备指定的值时,满足条件;

ConditionalOnWebApplication 注解:当应用程序是 web 应用时,满足条件;

5.3,整体流程图回顾

0

评论区