Spring Boot自动配置原理详解

Spring Boot是Pivotal团队提供的全新框架,使用“习惯优于配置”的理念,将开发过程中的习惯配置以自动注入的形式注入配置,只需很少的配置就可以快速开始项目。

本文使用的Spring Boot版本为2.1.3.RELEASE作为示例。

自定义一个自动配置类

首先我们使用idea新建一个Spring Boot应用,发现入口类上面有一个@SpringBootApplication的注解,这个就是Spring Boot自动配置的核心。

@SpringBootApplication
public class TestApplication {

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

}

接下来我们新建一个配置类CustomConfig

@Configuration
public class CustomConfig {

    @Bean
    public CustomConfig customConfig() {
        System.out.println("CustomConfig has been loaded");
        return new CustomConfig();
    }
}

resources目录下新建META-INF文件夹,然后新建spring.factories文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.superlee.CustomConfig

指定了CustomConfig的类路径。这样我们自定义的一个自动配置类就完成类,然后就是启动应用验证了,启用应用后控制台打印如下:

Connected to the target VM, address: '127.0.0.1:50615', transport: 'socket'

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-03-10 15:19:53.716  INFO 7969 --- [           main] com.superlee.TestApplication             : Starting TestApplication on superleedeMacBook-Pro.local with PID 7969 (/Users/superlee/Documents/idea_work/superlee/target/classes started by superlee in /Users/superlee/Documents/idea_work/superlee)
2019-03-10 15:19:53.719  INFO 7969 --- [           main] com.superlee.TestApplication             : No active profile set, falling back to default profiles: default
CustomConfig has been loaded
2019-03-10 15:19:54.172  INFO 7969 --- [           main] com.superlee.TestApplication             : Started TestApplication in 0.73 seconds (JVM running for 1.219)
Disconnected from the target VM, address: '127.0.0.1:50615', transport: 'socket'

Process finished with exit code 0

我们可以看到打印出来了CustomConfig has been loaded这句话,说明我们的自动配置生效了。

自动配置的思考

Spring Boot怎么会知道要把CustomConfig这个类自动加载到配置中呢?回想一下我们做了哪些操作:
1.在程序入口TestApplication上加了@SpringBootApplication注解
2.在CustomConfig上加上@Configuration注解
3.新建了resources/META-INF/spring.factories文件,并将CustomConfig的包路径配置到了EnableAutoConfiguration的key下面

我们可以猜测,Spring Boot应该是根据我们配置的CustomConfig包路径去实例化该对象,然后放到Spring context中,接下来就要看下源码看看是如何实现的。

源代码分析

SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//省略
}

我们可以发现SpringBootApplication这个注解集成了很多个注解,重点关注SpringBootConfiguration EnableAutoConfiguration ComponentScan这三个注解。

@Configuration
public @interface SpringBootConfiguration {
}

SpringBootConfiguration注解其实和Configuration等效的,标示为Spring Boot应用提供配置。

ComponentScan组件扫描注解,用于扫描指定包的bean,没有指定包名则扫描该注解所在的路径下的文件。本例即TestApplication所在的根目录。
这里需要注意excludeFilters属性,即组件扫描不包含的过滤器,看下AutoConfigurationExcludeFilter这个过滤器:

public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {

    private ClassLoader beanClassLoader;

    private volatile List<String> autoConfigurations;

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.beanClassLoader = beanClassLoader;
    }

    @Override
    public boolean match(MetadataReader metadataReader,
            MetadataReaderFactory metadataReaderFactory) throws IOException {
        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    }

    private boolean isConfiguration(MetadataReader metadataReader) {
        return metadataReader.getAnnotationMetadata()
                .isAnnotated(Configuration.class.getName());
    }

    private boolean isAutoConfiguration(MetadataReader metadataReader) {
        return getAutoConfigurations()
                .contains(metadataReader.getClassMetadata().getClassName());
    }

    protected List<String> getAutoConfigurations() {
        if (this.autoConfigurations == null) {
            this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(
                    EnableAutoConfiguration.class, this.beanClassLoader);
        }
        return this.autoConfigurations;
    }

}

我们可以看到这个过滤器有个match()方法,如果类加了@Configuration并且声明为EnableAutoConfiguration的自动配置类,则匹配。
但这里组件扫描注解不包含这个filter,也就是说组件扫描的时候,并不会示例化自动配置的类。那么这些自动配置类是如何被找到然后加载到IOC容器中呢?
下面我们看下EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};
}

我们可以看到该注解导入了一个AutoConfigurationImportSelector选择器,该选择器有一个AutoConfigurationGroup的内部类,我们关注一下该内部类的process()方法

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered {
    ···
    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        //判断是否启用了EnableAutoConfiguration注解
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        //获取excludeName和exclude属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //拿到所有的自动配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        //移除重复的自动配置类
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        //校验注解中不包含的自动配置类的合法性,如果classpath中不包含忽略的类或者自动配置的类中不包含忽略的类,抛异常
        checkExcludedClasses(configurations, exclusions);
        //移除要忽略的类
        configurations.removeAll(exclusions);
        //类加载器遍历class,如果某个自动配置类的Conditional不满足,则移除改配置
        configurations = filter(configurations, autoConfigurationMetadata);
        //发送自动配置类导入的事件
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    ...
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
            BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        ...

        @Override
        public void process(AnnotationMetadata annotationMetadata,
                            DeferredImportSelector deferredImportSelector) {
            Assert.state(
                    deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            //加载autoConfiguration的路径名。
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                            annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }
    }
}

我们看一下getAutoConfigurationEntry(_, _)方法里调用的getCandidateConfigurations(annotationMetadata, attributes)方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                      AnnotationAttributes attributes) {
        //getSpringFactoriesLoaderFactoryClass()方法返回EnableAutoConfiguration.class类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), 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;
    }

SpringFactoriesLoader是自动配置扫描过程中很重要的一个类,我们看下它的代码:

public final class SpringFactoriesLoader {

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        //拿到传入的EnableAutoConfiguration.class
        String factoryClassName = factoryClass.getName();
        //获取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有集合
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
            //classLoader去搜索所有包含META-INF/spring.factories文件的jar包
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                //读取jar中的spring.factories文件,然后转换为key-value形式
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    //spring.factories文件中的属性名
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}

这样就可以把classpath里所有的自动配置类找出来,放到list集合中,后面可以根据包路径使用反射的方法去实例化。

Conditional条件注解

条件注解为自动配置提供类实现基础,我们可以看到很多自动配置类都和条件注解结合使用,这样就可以灵活的根据classpath中的具体类去加载具体的配置。

总结

1.SpringBoot程序启动的时候,回去看是否启用了EnableAutoConfiguration
2.启用的情况下,classLoader在classpath中搜索所有的包含META-INF/spring.factories文件的jar包,然后解析spring.factories文件,拿到所有key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的字符集合
3.根据条件注解和classpath中包含的类去筛选满足条件的配置,然后使用反射实例化
4.把实例化的配置加载到Spring IOC容器中,然后刷新上下文,即可自动加载配置了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容