SpringBoot-自动配置核心原理

一、概述

在Spring Boot的开发过程中,我们想要在某一个场景下开发,只需要引入这个场景对应的starter,Spring Boot会自动加载相关依赖,配置好相关初始化参数,本文将从源码的角度解析Spring Boot自动配置的原理。从本文中,您将学习Spring Boot自动配置的核心原理、Spring、Spring Boot的一些核心注解,自动配置类的加载、注册的条件判断等。

二、源码解析

Spring Boot工程创建以后会默认生成一个*Application的启动类,这个类就是一个普通的包含Java main方法的类。在这个类中引入了@SpringBootApplication注解,自动配置的故事就是从这儿开始的。

1、@SpringBootApplication

这个注解是一个组合注解,源码如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
	/**
	@AliasFor注解的作用是桥接到其他注解,该注解的属性中指定了所桥接的注解类。因为在@SpringBootApplication 定义的属性已经在其他注解中定义过了,这儿只是为了方便用户使用定义的组合注解。
	**/
	//排除指定的自动配置类
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};
	//排除指定的自动配置类名
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};
	//扫描指定的包,用于激活@Component等注解类的初始化
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};
	//指定扫描的类,用于组件的初始化。
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

从源码可以看到,@SpringBootApplication是一个组合注解,包含了:@SpringBootConfiguration、
@EnableAutoConfiguration、@ComponentScan 三个注解。@SpringBootApplication注解的层级关系如图所示:
@SpringBootApplication注解层级关系
上图中很多注解都是在Spring中的基础注解,说明如下
①、@Configuration:标注在类上,用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
②、@Import:用来导入配置类或者一些需要前置加载的类。该注解的使用方法有三种:

a、带有@Configuration的配置类(4.2 版本之前只可以导入配置类,4.2版本之后 也可以导入 普通类)
b、ImportSelector 的实现
c、ImportBeanDefinitionRegistrar 的实现

由于篇幅原因,对@Import注解就不展开说明了。
③、@ComponentScan注解:根据定义的扫描路径,把符合扫描规则的类装配到spring容器中。
解释完Spring的基本注解后,我们来看一下@SpringBootConfiguration
@EnableAutoConfiguration注解。

2、@SpringBootConfiguration

@Configuration
public @interface SpringBootConfiguration {
}

通过以上源码可以发现,其实SpringBootConfiguration 只是标注了@Configuration,那么被SpringBootConfiguration 注解标注的类就是一个配置类。

3、@EnableAutoConfiguration

@EnableAutoConfiguration注解是开启自动配置的关键,源码如下:

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

我们通过源码可以看到,Spring Boot通过@Import注解给我们导入了AutoConfigurationImportSelector类。
在讲解AutoConfigurationImportSelector类之前呢,我们有必要先了解ImportSelector接口。该接口源码如下:

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。

比如如下代码,就能够将TestC类加载到容器中:

public class Myclass implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.xwfintech.model.TestC"};
    }
}

AutoConfigurationImportSelector正是采用这种方式。源码如下:

//DeferredImportSelector 是ImportSelector的子接口,它和ImportSelector的区别是,它会在所有@Configuration类加载完成后在去加载返回的配置类。而ImportSelector则是@Configuration类加载完成前在去加载返回的配置类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

从上述代码可以看到AutoConfigurationImportSelector实现了DeferredImportSelector接口,那么它导入自动的配置类的源码必定在selectImports方法中。源码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
        	// 加载自动配置的元信息,该信息包含了各种自动配置类的过滤条件,配置文件为类路径中META-INF目录下的spring-autoconfigure-metadata.properties文件。该文件在spring-boot-autoconfigure包下
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            // 返回符合条件的配置类的全限定名数组
            return StringUtils.toStringArray(configurations);
        }
    }

上述代码分步骤讲解:

1、加载自动配置的元信息,该信息包含了各种自动配置类的过滤条件,配置文件为类路径中META-INF目录下的spring-autoconfigure-metadata.properties文件。该文件在spring-boot-autoconfigure包下

 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 //AutoConfigurationMetadataLoader.loadMetadata()
 public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }

2、获取需要排除的类,我们知道在@SpringBootApplication上能够指定排除的类,像这种写法:@SpringBootApplication(exclude = MyConfig.class)

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

attributes结构如图所示:
在这里插入图片描述
3、从META-INF目录的spring.factories文件中,获取候选的自动配置类

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        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;
}
    
 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        //调用loadSpringFactories方法获取自动配置的类
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
            //获取系统资源
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }

从源码中我们可以看到,是从META-INF/spring.factories获取到了自动配置的类。

4、移除重复获取的自动配置类

configurations = this.removeDuplicates(configurations);

protected final <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList(new LinkedHashSet(list));
    }

这一步我们可以看到,其实就是使用LinkedHashSet去重。
5、获取需要排除的类的全类名。(需要排除的类是第二步获取的)

 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

6、将需要排除的类从候选的自动配置类中移除

 configurations.removeAll(exclusions);

7、根据条件移除需要注册的自动配置类,(条件为第一步从spring-autoconfigure-metadata.properties文件中获得的)。

configurations = this.filter(configurations, autoConfigurationMetadata);


private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        //定义一个标识位,标识指定位置的自动配置类,是否满足地导入条件
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
        //从spring.factories中获取AutoConfigurationImportFilter类型的过滤器
        //org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=org.springframework.boot.autoconfigure.condition.OnClassCondition
        Iterator var8 = this.getAutoConfigurationImportFilters().iterator();

        while(var8.hasNext()) {
            AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
            this.invokeAwareMethods(filter);
            //调用filter的match方法,设置标识位
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);

            for(int i = 0; i < match.length; ++i) {
                if (!match[i]) {
                    skip[i] = true;
                    skipped = true;
                }
            }
        }
        //如果所有的配置都符合条件,直接返回即可	
        if (!skipped) {
            return configurations;
        } else {
            List<String> result = new ArrayList(candidates.length);

            int numberFiltered;
            //将符合条件的自动配置类加入结果集
            for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
                if (!skip[numberFiltered]) {
                    result.add(candidates[numberFiltered]);
                }
            }

            if (logger.isTraceEnabled()) {
                numberFiltered = configurations.size() - result.size();
                logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
            }

            return new ArrayList(result);
        }
    }

8、在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播,关于事件的封装和广播,本文就不展开详述了。

附加题 -_-:

从上一步中,我们可以清晰的看到,SpringBoot是如何过滤自动配置类的。其中调用了filter.match()方法去匹配。我们可以继续深入的看看match方法的运行原理,filter在配置中是org.springframework.boot.autoconfigure.condition.OnClassCondition类型。所以我们直接查看该类型的match方法即可:

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        ConditionEvaluationReport report = this.getConditionEvaluationReport();
        //在此处判断候选的自动配置类是否满足条件
        ConditionOutcome[] outcomes = this.getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];

        for(int i = 0; i < outcomes.length; ++i) {
            match[i] = outcomes[i] == null || outcomes[i].isMatch();
            if (!match[i] && outcomes[i] != null) {
                this.logOutcome(autoConfigurationClasses[i], outcomes[i]);
                if (report != null) {
                    report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
                }
            }
        }
        return match;
    }

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
		//将数组分为两个部分,如果是多核CPU,那么就使用多线程去处理,提升效率
        int split = autoConfigurationClasses.length / 2;
        //创建一个ThreadedOutcomesResolver类型的解析器,其实就是新开一个线程去处理
        OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
        //使用标准的解析器,主线程去处理
        OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, this.beanClassLoader);
        //获取到解析结果
        ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
        ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
        ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
        //使用系统类提供的拷贝方法,将结果拷贝到新的数组中
        System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
        System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
        return outcomes;
    }


//在上一个方法中,我们看到Spring Boot使用StandardOutcomesResolver.resolveOutcomes()方法去匹配结果,该方法源码如下:
public ConditionOutcome[] resolveOutcomes() {
//调用getOutcomes获取返回值
            return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
        }
//
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
            ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
			
            for(int i = start; i < end; ++i) {
            	//获取自动配置类
                String autoConfigurationClass = autoConfigurationClasses[i];
                //根据自动配置类的类名+ConditionalOnClass,从条件筛选列表中获取对应的条件。比如:org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration.ConditionalOnClass=org.springframework.transaction.PlatformTransactionManager,即:要想TransactionAutoConfiguration自动配置类生效,那么类路径下需要存在PlatformTransactionManager类。
                Set<String> candidates = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnClass");
                if (candidates != null) {
                	//继续调用getOutcome,获得返回结果,具体的判断逻辑
                    outcomes[i - start] = this.getOutcome(candidates);
                }
            }

            return outcomes;
        }
//具体的判断逻辑
private ConditionOutcome getOutcome(Set<String> candidates) {
            try {
            //调用OnClassCondition.MatchType.MISSING去判断
                List<String> missing = OnClassCondition.this.getMatches(candidates, OnClassCondition.MatchType.MISSING, this.beanClassLoader);
                if (!missing.isEmpty()) {
                    return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, missing));
                }
            } catch (Exception var3) {
            }

            return null;
        }

MISSING {
            public boolean matches(String className, ClassLoader classLoader) {
                return !OnClassCondition.MatchType.isPresent(className, classLoader);
            }
        };
 //最终就是根据传入的类名,去加载这个类,如果加载成功,返回true,加载失败返回false
private static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }

            try {
                forName(className, classLoader);
                return true;
            } catch (Throwable var3) {
                return false;
            }
        }

总结:SpringBoot加载时,通过spring-autoconfigure-metadata.properties中配置的条件,依次循环每一个候选的自动配置类,去判断是否应该导入某一个自动配置类。

本文有不足之处,请大家指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值