zoukankan      html  css  js  c++  java
  • Springboot 自动配置源码调试

    一、SpringBoot 的主类如下(版本:spring-boot-2.1.17):主类上只有一个 @SpringBootApplication 注解,通过这个注解可以帮我们完成各种自动配置.

    二、@SpringBootApplication注解如下:SpringBoot关于自动配置这一块则主要是通过 @EnableAutoConfiguration 这个注解来完成的.

    三、@EnableConfiguration注解上通过@Import注解引入了一个类AutoConfigurationImportSelector,使得该类注入到spring容器中,交由spring容器进行管理

    四、打开AutoConfigurationImportSelector这个类,里面有一个很重要的方法getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata).

     1、我们首先看一下该方法的两个参数 getAutoConfigurationMetadata() 和 annotationMetadata

    一、参数一:getAutoConfigurationMetadata(),该参数的类型是 AutoConfigurationMetadata

      1、该方法加载 META-INF/spring-autoconfigure-metadata.properties文件,并且得到里面的键值对存储在Properties对象中

      2、然后接着看一下 return loadMetadata(properties)这个方法,可以看到最终的结果是:从 META-INF/spring-autoconfigure-metadata.properties中加载到的内容赋值给了 PropertiesAutoConfigurationMetadata 类的 Properties属性,(PropertiesAutoConfigurationMetadata是 AutoConfigurationMetadata的一个实现类).

      3、最终可以第一个参数得到的是一个(AutoConfigurationMetadata)PropertiesAutoConfigurationMetadata对象,里面的properties属性中包含了684个键值对

    二、参数二:annotationMetadata,它是一个AnnotationMetadata类型的参数,它包含的就是@SpringBootApplication注解的元数据信息

     2、接着看一下 getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata)这个方法的具体实现

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    		AnnotationMetadata annotationMetadata) {
    	if (!isEnabled(annotationMetadata)) {
    		return EMPTY_ENTRY;
    	}
    	// 方法一
    	AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	// 方法二
    	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    	// 方法三
    	configurations = removeDuplicates(configurations);
    	// 方法四
    	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    	// 方法五
    	checkExcludedClasses(configurations, exclusions)
    	// 方法六
    	configurations.removeAll(exclusions);
    	// 方法七
    	configurations = filter(configurations, autoConfigurationMetadata);
    	// 方法八
    	fireAutoConfigurationImportEvents(configurations, exclusions);
    	// 方法九
    	return new AutoConfigurationEntry(configurations, exclusions);
    }
    

     一、方法一:getAttributes(annotationMetadata)

      这个方法返回的是一个LinkedHashMap<String, Object>的集合

      该集合中存储了两个key,分别为 exclude、excludeName,但是没有对应的值,也就是我们这里没有需要排除的的元素

     方法二:getCandidateConfigurations(annotationMetadata, attributes)

    1、它的作用就是取出spring-boot-autoconfigure-2.1.17.RELEASE.jar!/META-INF/spring.factories文件中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,把大的字符串进行逗号分割,得到的值存入List集合中.

    2具体的我们可以看一下过程,它调用了SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()),这个方法有两个参数,我们来看一下这两个参数分别是什么?

     

      参数一:getSpringFactoriesLoaderFactoryClass(): org.springframework.boot.autoconfigure.EnableAutoConfiguration

      参数二:getBeanClassLoader():ClassLoader(类加载器)

    3、接着看一下loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader())这个方法,从上面的过程中可以知道该方法的两个参数如下:

      factoryClassName:org.springframework.boot.autoconfigure.EnableAutoConfiguration

      getBeanClassLoader():ClassLoader(类加载器)

    4、loadSpringFactories(classLoader):该方法的作用是应用初始化的时候加载资源中所有的 META-INF/spring.factories文件中的内容,通过一系列转换,最后成为一个Map集合,并且将该Map集合存储在缓存中,自动配置的时候实际上是从缓存中获取到的Map集合(我这里当前只有三个jar包中存在 META-INF/spring.factories 文件)

      一、/repository/org/springframework/boot/spring-boot/2.1.17.RELEASE/spring-boot-2.1.17.RELEASE.jar!/META-INF/spring.factories

    // 前面是键的名称,后面括号内的数字代表属性的个数
    org.springframework.boot.env.PropertySourceLoader=(2)
    org.springframework.boot.SpringApplicationRunListener=(1)
    org.springframework.boot.SpringBootExceptionReporter=(1)
    org.springframework.context.ApplicationContextInitializer=(4)
    org.springframework.context.ApplicationListener=(10)
    org.springframework.boot.env.EnvironmentPostProcessor=(3)
    org.springframework.boot.diagnostics.FailureAnalyzer=(3)
    org.springframework.boot.diagnostics.FailureAnalysisReporter=(1)
    

      二、/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.17.RELEASE/spring-boot-autoconfigure-2.1.17.RELEASE.jar!/META-INF/spring.factories

    org.springframework.context.ApplicationContextInitializer=(2)
    org.springframework.context.ApplicationListener=(1)
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=(1)
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=(3)
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=(117)
    org.springframework.boot.diagnostics.FailureAnalyzer=(4)
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=(5)
    

      、/repository/org/springframework/spring-beans/5.1.18.RELEASE/spring-beans-5.1.18.RELEASE.jar!/META-INF/spring.factories

    org.springframework.beans.BeanInfoFactory(1)

      spring-boot-2.1.17.RELEASE.jar!/META-INF/spring.factories文件中有8个key

      spring-boot-autoconfigure-2.1.17.RELEASE.jar!/META-INF/spring.factories文件中有7个key

      spring-beans-5.1.18.RELEASE.jar!/META-INF/spring.factories文件中有1个key

      总共有16个key,为什么得到最后的是13个key呢?因为各个jar包中存在重复的key,然后把相同的key合并了,最终转换成了Map<String,List<String>>类型的数据

      重复的key如下:

    // 这三个key是jar包中重复的key
    org.springframework.context.ApplicationContextInitializer=  
    org.springframework.context.ApplicationListener=
    org.springframework.boot.diagnostics.FailureAnalyzer=

      最终合并之后的key如下:

    // 前面是key,后面是该key对应的List集合中元素的个数
    org.springframework.boot.diagnostics.FailureAnalyzer=(17)
    org.springframework.boot.env.EnvironmentPostProcessor=(3)
    org.springframework.boot.SpringApplicationRunListener=(1)
    org.springframework.context.ApplicationContextInitializer=(6)
    org.springframework.boot.env.PropertySourceLoader=(2)
    org.springframework.context.ApplicationListener=(11)
    org.springframework.boot.diagnostics.FailureAnalysisReporter=(1)
    org.springframework.boot.SpringBootExceptionReporter=(1)
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=(3)
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=(1)
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=(5)
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=(117)
    org.springframework.beans.BeanInfoFactory(1)
    

      同时查看缓存中的result,与我们上面分析的结果是一致的 

    5、然后我们先看一下 getOrDefault(factoryClassName, Collections.emptyList())这个方法的作用

      factoryClassName:org.springframework.boot.autoconfigure.EnableAutoConfiguration

      该方法的作用是从Map<String,List<String>>这个大的集合中取出key=org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的值,并且把它们存放在一个List<String>集合中.

     

      所以最终 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)方法的返回值就是spring-boot-autoconfigure-2.1.17.RELEASE.jar!/META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration这个key对应的值,(把值用逗号拆分开来使用List集合来进行存储).

    方法三、configurations=removeDuplicates(configurations)

      移除configurations中的重复值(通过LinkedHashSet进行去除重复值)

     方法四、Set<String> exclusions = getExclusions(annotationMetadata, attributes)

      排除的自动配置项

    protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    	Set<String> excluded = new LinkedHashSet<>();
    	// 获取attributes中属性为 exclude对应的值,它是一个String类型的数组,将其转为List集合,并添加到Set集合中
    	excluded.addAll(asList(attributes, "exclude"));
    	// 获取attributes中属性为 excludeName对应的值,它是一个String类型的数组,将其转为List集合,并添加到Set集合中
    	excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    	// 获取SpringBoot配置文件中的配置spring.autoconfigure.exclude= 的内容,并添加到Set集合中
    	excluded.addAll(getExcludeAutoConfigurationsProperty());
    	return excluded;
    }
    
    getExcludeAutoConfigurationsProperty()方法源码如下:

     方法五、checkExcludedClasses(configurations, exclusions):校验排除的类

     方法六、configurations.removeAll(exclusions):移除SpringBoot排除的类

     方法七、configurations = filter(configurations, autoConfigurationMetadata)

    private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    	long startTime = System.nanoTime();
    	// 将List集合转化为String类型的数组(String[])
    	String[] candidates = StringUtils.toStringArray(configurations);
    	// 创建一个boolean类型的数组,默认值都是false,长度为candidates.length
    	boolean[] skip = new boolean[candidates.length];
    	boolean skipped = false;
    	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    		invokeAwareMethods(filter);
    		boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    		for (int i = 0; i < match.length; i++) {
    			if (!match[i]) {
    				skip[i] = true;
    				candidates[i] = null;
    				skipped = true;
    			}
    		}
    	}
    	if (!skipped) {
    		return configurations;
    	}
    	List<String> result = new ArrayList<>(candidates.length);
    	for (int i = 0; i < candidates.length; i++) {
    		if (!skip[i]) {
    			result.add(candidates[i]);
    		}
    	}
    	if (logger.isTraceEnabled()) {
    		int numberFiltered = configurations.size() - result.size();
    		logger.trace("Filtered " + numberFiltered + " auto configuration class in "
    				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    	}
    	return new ArrayList<>(result);
    }
    

     1、我们首先看一下这个for循环

      getAutoConfigurationImportFilters():获取到的是repositoryorgspringframeworkootspring-boot-autoconfigure2.1.17.RELEASEspring-boot-autoconfigure-2.1.17.RELEASE.jar!META-INFspring.factories文件下面key对应的值

    2、然后就是对这三个Filter进行遍历

    3、第一次遍历 OnClassCondition:判断所有候选的配置是否满足系统中都存在指定的类

    首先看一下invokeAwareMethods(filter)这个方法,这个方法主要是判断当前的过滤器是否实现了相关的接口,如果实现了并对其设置相应的值

    我们可以看一下OnClassCondition实现了哪一些接口

    OnClassCondition继承了FilteringSpringBootCondition,而后者又实现了下面几个接口

    所以遍历OnClassCondition对BeanClassLoaderAware、BeanFactoryAware设置了值

    接着看一下boolean[] match = filter.match(candidates, autoConfigurationMetadata)这个方法

    ConditionEvaluationReport report:这个report对象记录的是没有匹配上的结果.

    这个方法中有一个getOutcomes(autoConfigurationClasses, autoConfigurationMetadata)方法,该方法将整个候选的配置进行类似于二分查找的方式进行条件判断匹配.

    匹配完成之后返回的是一个ConditionOutcome[] 数组,该数组记录的是候选的配置匹配的结果,如果匹配上了,那么记录为null(这里 0、4、7、8、9.......匹配上了),如果未匹配上那么记录为false.

    接着看一下getOutcomes方法执行之后后面的代码

    // 定义一个boolean类型的数组match
    boolean[] match = new boolean[outcomes.length];
    for (int i = 0; i < outcomes.length; i++) {
    	// 如果outcomes中的值为null,代表匹配上了,match数组中就将其存入为true
    	match[i] = (outcomes[i] == null || outcomes[i].isMatch());
    	// 如果未匹配上,使用report对象来记录为匹配的属性
    	if (!match[i] && outcomes[i] != null) {
    		logOutcome(autoConfigurationClasses[i], outcomes[i]);
    		if (report != null) {
    			report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
    		}
    	}
    }
    return match;

    由于report对象中存储的是未匹配上的属性,第一次过滤筛选之后report中记录的数据如下,总共有92个,也就是92个配置不能进行自动配置.

     点开outcomes可以看到不能匹配的原因

    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration:

    @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' : 系统中没有找到需要的类 com.rabbitmq.client.Channel,导致RabbitAutoConfiguration不能进行自动匹配

    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration:

    @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect':系统中没有找到需要的类org.aspectj.lang.annotation.Aspect导致AopAutoConfiguration不能进行自动匹配

    我们可以看一下RabbitAutoConfiguration的源码,RabbitAutoConfiguration必须要满足存在 RabbitTemplate类、Channel类才能交由Spring管理,进行自动配置

     

    4、第二次遍历OnWebApplicationCondition:判断是否满足web应用环境

      筛选完117个候选配置之后,发现有2个没有匹配上的,之前第一次遍历的时候有92个没有匹配上的,加上这两个,总共有94个未匹配上的

    5、第三次遍历 OnBeanCondition:判断容器中是否存在指定的Bean

      筛选完117个候选配置之后有1个没有匹配上的,前两次遍历有94个没有匹配上的,加上这一个,总共有95个没有匹配上的

      经过三次遍历筛选之后,总共117个候选的配置,过滤掉了95个,剩下22个可以自动配置的,如下:

      到此AutoConfigurationImportSelector类process(...)方法中的getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata)方法返回值就是我们上面筛选出来的22个能够进行自动配置的项

     

  • 相关阅读:
    常见的查找算法(七):哈希查找
    常见的查找算法(六):分块查找
    常见的查找算法(五):树表查找之一 ---- 二叉查找树
    让div充满整个body
    display:table的用法
    webpack-dev-server
    webpack--loader
    webpack nodejs npm关系
    js中==和===区别
    vue 的点击事件怎么获取当前点击的元素
  • 原文地址:https://www.cnblogs.com/xiaomaomao/p/13893763.html
Copyright © 2011-2022 走看看