• 【详细学习SpringBoot自动装配原理分析之核心流程初解析-1】


    一.知识回顾

    【0.SpringBoot专栏的相关文章都在这里哟,后续更多的文章内容可以点击查看】
    【1.SpringBoot初识之Spring注解发展流程以及常用的Spring和SpringBoot注解】
    【2.SpringBoot自动装配之SPI机制&SPI案例实操学习&SPI机制核心源码学习】

    二.自动装配原理分析之核心流程初解析

    2.1 什么是自动装配?

    1. 所谓的自动装配,实际上就是如何自动将bean装载到Ioc容器中来。
    2. 在spring 3.x版本中,Enable模块驱动注解的出现,已经有了一定的自动装配的雏形,而真正能够实现这一机制,还是在spirng 4.x版本中,conditional条件注解的出现。

    2.2 如何进行一个简单的自动装配?自动装配案例的演示?

    Redis为案例演示

    2.2.1 首先在pom.xml文件中引入依赖文件
     <dependency>
         <groupId>org.springframework.bootgroupId>
         <artifactId>spring-boot-starter-data-redisartifactId>
    dependency> 
    
    • 1
    • 2
    • 3
    • 4
    2.2.2 项目的application.yaml配置文件中进行我们redis相关的配置
    spring:
        redis:
          host: 127.0.0.1 
          port: 6379
    
    • 1
    • 2
    • 3
    • 4
    2.2.3 项目代码中直接使用注解注入
     	@Autowired
        private RedisTemplate<String,String> redisTemplate;
    
    • 1
    • 2

    结论:通过上上面的步骤我们就可以将一些其它服务,插件和我们的SpringBoot很好的融合在一起,步骤简单,操作容器,但是,为什么RedisTemplate可以被直接注入?它是什么时候加入到Ioc容器的呢?好的,接下来我们就带着问题继续向下学习。

    2.3 深入分析EnableAutoConfiguration注解

    2.3.1 EnableAutoConfiguration注解的作用?

    EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

    2.3.2 进入到@EnableAutoConfiguration注解中

    在这里插入图片描述

    2.3.3 我们看EnableAutoConfiguration这个注解中,它的关键之处在于使用了@Import注解并导入了AutoConfigurationImportSelector这个类
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    那么问题就来了:从EnableAutoConfiguration上面的Import注解来看,这里面并不是引入另外一个Configuration。而是导入了一个ImportSelector。这个是什么东西呢?不要着急,我们继续往下看。

    2.3.4 AutoConfigurationImportSelector这个类的作用是什么?底层原理?
    2.3.4.1 先来学习一下通过@Import注解可以配置三种不同的class类

    Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合,还可以实现一些复杂的场景,比如可以根据上下文来激活不同类型的bean,@Import注解可以配置三种不同的class。

    1. 第一种:基于普通bean或者带有@Configuration的bean进行注入
    2. 第二种:实现ImportSelector接口进行动态注入
    3. 第三种:实现ImportBeanDefinitionRegistrar接口进行动态注入
    2.3.4.2 前面文章学习了ImportSelector和ImportBeanDefinitionRegistrary实现的动态注入功能后,我们大致也能猜到AutoConfigurationImportSelector注解的作用了,我们继续向下看?
    1. 从名字来看,可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。之前我们讲过Springboot中@Enable*注解的工作原理ImportSelector接口selectImports方法返回的数组(类的全类名)都会被纳入到spring容器中。
    2. 那么这里的实现原理也一定是一样的,定位AutoConfigurationImportSelector这个类中的selectImports方法,我们继续学习研究。

    了解了selector的基本原理之后,后续再去分析AutoConfigurationImportSelector的原理就很简单了,它本质上也是对于bean的动态加载。

    2.3.4.3 查看AutoConfigurationImportSelector类中的selectImports方法
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
       }
       // 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadata
       AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
             .loadMetadata(this.beanClassLoader);
       // 获取所有候选配置类EnableAutoConfiguration
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
             autoConfigurationMetadata, annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    捕捉关键信息:AutoConfigurationImportSelector类中selectImports()方法中最关键的是getAutoConfigurationEntry()这个方法,接下来还用我教你你吗?直接进入getAutoConfigurationEntry方法一探究竟。

    2.3.4.4 自动装配最核心的方法:一探究竟—getAutoConfigurationEntry
    protected AutoConfigurationEntry getAutoConfigurationEntry(
          AutoConfigurationMetadata autoConfigurationMetadata,
          AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return EMPTY_ENTRY;
       }
       //获取元注解中的属性
       AnnotationAttributes attributes = getAttributes(annotationMetadata);
       //使用SpringFactoriesLoader 加载classpath路径下META-INF\spring.factories中,
       //key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
       List<String> configurations = getCandidateConfigurations(annotationMetadata,
             attributes);
    	//去重
       configurations = removeDuplicates(configurations);
    	//应用exclusion属性
       Set<String> exclusions = getExclusions(annotationMetadata, attributes);
       checkExcludedClasses(configurations, exclusions);
       configurations.removeAll(exclusions);
    	//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
       configurations = filter(configurations, autoConfigurationMetadata);
       //广播事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
       return new AutoConfigurationEntry(configurations, exclusions);
    }
    
    
    • 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

    本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性的针对需要加载的bean进行条件过滤

    2.4 工具类SpringFactoriesLoader?

    public final class SpringFactoriesLoader {
    	
    	//省略大部分代码
    }
    
    • 1
    • 2
    • 3
    • 4
    2.4.1 SpringFactoriesLoader
    1. SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。

    2. 首先,SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载对应的类到spring IoC容器中。

    2.4.2 实践案例走起
    2.4.2.1创建外部项目jar
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>4.3.13.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.4.2.2 创建bean以及config

    创建bean

    public class TestCore {
        public void testCore(){
            System.out.println("testCore.....");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建config

    @Configuration
    public class TestConfig {
    	@Bean
        public TestCore testConfig(){
            return new TestCore();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.4.2.3 创建另外一个工程(spring-boot)

    把前面的工程打包成jar,当前项目依赖该jar包

    <dependency>
        <groupId>com.ljwgroupId>
        <artifactId>test-CoreartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.4.2.4 通过下面代码获取依赖包中的属性

    运行结果会报错,原因是mashibingCore并没有被Spring的IoC容器所加载,也就是没有被EnableAutoConfiguration导入

    @SpringBootApplication
    public class SpringBootTestCoreApplication {
        public static void main(String[] args) throws IOException {
            ConfigurableApplicationContext ac=SpringApplication.run(SpringBootTestCoreApplication.class, args);
            TestCore core=ac.getBean(TestConfig.class);
            System.out.println(core.testCore());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.4.2.5 解决方案

    在test-Core项目resources下新建文件夹META-INF,在文件夹下面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全路径,value是配置类的全路径。

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ljw.TestConfig
    
    • 1

    重新打包,重新运行SpringBootStudyApplication这个类。可以发现,我们编写的那个类,就被加载进来了。

    2.4.2.6 演示一下springboot中通过spring-autoconfigure-metadata.properties过滤

    1. 在test-Core项目中的META-INF/增加配置文件,spring-autoconfigure-metadata.properties。

    com.ljw.TestConfig.ConditionalOnClass=com.test.TestDemo
    
    • 1

    格式:自动配置的类全名.条件=值

    上面这段代码的意思就是,如果当前的classpath下存在TestDemo,则会对TestConfig这个Configuration进行加载
    如果不存在,我们需要根据下面的代码进行测试调试。

    2. 运行调试

    1. 沿用前面spring-boot工程的测试案例,直接运行main方法,发现原本能够被加载的TestCore,发现在ioc容器中找不到了。
    2. 在当前工程中指定的包com.test下创建一个TestDemo以后,再运行上面这段代码,程序能够正常执行。

    2.5 Spring Boot中的条件过滤

    2.5.1 spring-autoconfiguration-metadata.properties文件的作用?

    在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件,最后扫描spring.factories对应的类时,会结合前面的元数据spring-autoconfiguration-metadata.properties进行过滤。

    2.5.2 为什么要过滤呢?

    原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。

    final class AutoConfigurationMetadataLoader {
    
    	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
    
    	private AutoConfigurationMetadataLoader() {
    	}
    
    	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    		return loadMetadata(classLoader, PATH);
    	}
    
    	//........
    	//省略大部分代码
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    好了,到这里【详细学习SpringBoot自动装配原理分析之核心流程初解析-1】就先到这里,关于SpringBoot自动装配的原理后续不断更新创作中。

  • 相关阅读:
    String 类的基本用法及String 类的常见操作
    Chapter6:Atomic Accesses
    【Flutter】自定义分段选择器Slider
    Taocms 代码注入漏洞(CVE-2022-25578)
    c++新特性之模板 对象
    PG 多表连接查询
    Python灰帽编程——定制EXP武器库
    6 Processes 下
    STM32物联网项目-高级定时器软件仿真输出互补PWM信号
    noip2012Vigenère 密码
  • 原文地址:https://blog.csdn.net/Coder_ljw/article/details/127710629