springboot 2.* 自动配置原理探究&demo实现

springboot-2.1.0 自动配置原理解析&demo实现

前言

最近准备升级线上服务的版本到springboot 2.1.0,所以抽空重新研究学习了下springboot的一些特性、原理。本文的核心就是学习理解下springboot的自动配置原理,版本是目前springboot的最新release版本:2.1.0。博客包括如下几个部分:

  1. 以server.port为例,探究自动配置的内部实现流程
  2. 总结下springboot自动配置涉及的知识点
  3. 实现一个自动配置的demo!

自动配置的流程探究

我们知道,对于springboot服务,只需要在application.properties/application.yml里配置server.port,即可自动绑定web server的访问端口,今天我们就一起来探究下这个自动配置服务端口号是如何实现的。

  • 环境准备

新建一个springboot 2.1.0项目,可以直接使用 springboot initialize进行生成,很方便,如图1所示。

appliaction.properties配置一个参数,server.port=8888。然后启动项目,查看日志,可以看到Tomcat服务的访问端口已经变成了8888。

很显然,服务端口的自动配置,目前只有一处触发点,就是注解:@SpringBootApplication。我们需要探究下通过该注解,如何实现自动配置的实现。

  • @SpringBootApplication注解探究

@SpringBootApplication注解内部,依赖多个注解,大部分注解都是java的常规注解,看命名就能发现@EnableAutoConfiguration这个注解应该是我们想要的,所以继续看这个注解。

@EnableAutoConfiguration注解内部有两个注解需要引起注意,一个是@AutoConfigurationPackage注解,一个是@import一个类:AutoConfigurationImportSelector.class

@AutoConfigurationPackage注解表示啥意思呢?来,一起谷歌一下:解释如下:
启动自动配置,该注解开启,使用Spring boot启动项目时,就会把找的jar包,自动配置整合。

ok,很重要的注解。也就是说通过该注解,我们会去找需要需要自动配置的类。那如何确认需要自动配置哪些类呢?我们再看下AutoConfigurationImportSelector.class这个类,看命名就晓得跟自动配置很相关。看下方法,比较多,无从下手。ok看下实现了哪些接口吧,该类一共实现了6个接口,那我们就从这几个接口来着手,第一个接口@DeferredImportSelector,他还继承一个父接口:ImportSelect,好的,我们继续谷歌一下,看看这个接口干嘛的?

@ImportSelect主要作用是收集需要导入的配置类,如果该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法,如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口。

我们这边就是实现的@DeferredImportSelector,很棒。那说明通过此接口的方法,可以收集需要导入的配置类。那我们一起看下实现该接口的方法:selectImports()

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

该方法内部掉用了一个getAutoConfigurationEntry()方法,看方法命名,非常有戏。我们继续看该方法内部。

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            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 new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

不要被那么多行代码所迷惑,仔细看会发现,核心在与获取的configurations,我们会发现List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);这行代码之后的操作,都是围绕configurations这个对象来进行的。ok,我们马上进入此方法内部探究下。

    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;
    }

可以看到,核心在于SpringFactoriesLoader.loadFactoryNames()这个方法。继续往里探究下,最终,我们发现是调用了下图这个方法。对此,我们不展开对于这块代码的分析,直接抛结论,这块代码就是找resources/META-INF路径下的spring.factories里的自动配置类。

我们可以去依赖里,找个spring.factories里探个究竟,看下图,我们查看了spring-boot-autoconfiguration这个jar内部的spring.factories,可以看到springboot启动时大部分的自动配置类。

到这里,基于上述的各类分析,我们简单总结下:
springboot服务,通过服务入口类的注解,实现自动配置能力,实现的流程时,在启动时,会去扫描resources/META-INF路径下的spring.factories里的自动配置类,从而实现类的自动配置。

ok,知晓了自动配置的大致原理,但我们还是缺少很多信息,比如,对于前文提到的tomcat端口的配置,到底是如何完成的?

通过上面的源码探究,我们知道,每一个自动配置项,都依赖对应的自动配置类,所以这个tomcat端口的自动配置,肯定也依赖一个自动配置类,我们可以在依赖项中找出来。我们在spring-boot-autoconfiguration里的spring.factories查找下web server相关的自动配置项,跟着感觉走,看命名类似的,可以找到这个配置类:ServletWebServerFactoryAutoConfiguration。我们一起看下这个配置类的源码:

看到方法注释,了然啦!就是这个类实现了web server的自动配置。首先我们分析下这个类的注解,我们会发现里面有很多Contional开头的注解,偷偷告诉你,这是精髓。

看方法,发现这个方法就是我们需要的:

@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

该方法的入参是ServerProperties类,这个类很重要,因为我们发现最上面的注解要求,只有在该类存在的情况下,才注入ServletWebServerFactoryAutoConfiguration这个类。所以我们必须探究下ServerProperties这个类:

哇哦,有没有看到很熟悉的东西,prefix = "server" 前缀等于“server”,这不就是我们最开始在application.properties里配置的内容吗?

好吧,原来我们配置的server.*的内容,就是ServerProperties里定义的属性哇。然后回到前面的ServletWebServerFactoryAutoConfiguration类,可以看到通过ServerProperties类,构建了TomcatServletWebServerFactoryCustomizer类,而此类的作用就是构建一个 Tomcat Web。

原理总结

现在整体梳理下springboot自动配置的流程,我们通过一个属性类,配置一堆具备公共前缀的属性,然后新建一个自动配置类,通过一些特定条件,完成自动配置的动作。

再上一层,我们需要将自动配置类,填写在resources/META-INF路径下的spring.factories。然后springboot启动时,会去扫描这里的所有自动配置信息,完成类的注入。

以上整个过程,似乎比较简单,但其实完成起来还是很复杂的。比如我们回到刚刚的ServletWebServerFactoryAutoConfiguration类,我们会发现,Tomcat服务类的注入,有一个依赖条件:@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")。这里表示的含义是只有在org.apache.catalina.startup.Tomcat这个类存在时,才注入该类。其实,自动配置能力的实现,主要就归功于@Conditional注解。

  • @Conditional

对于@Conditional注解的原理实现,这里不做展开,@Conditional是由Spring 4提供的一个新特性,用于根据特定条件来控制Bean的创建行为。原理很简单,其实该注解依赖于FilteringSpringBootCondition这个类的match方法。我们这边介绍下@Conditional开头的常用的几个注解的含义,完整的注解 可以去org.springframework.boot.autoconfigure.condition这个package下自己研究下。

@ConditionalOnBean,表示当具体的bean存在时创建该bean。
@ConditionalOnClass,表示当具体的class存在时
@ConditionalOnMissingBean,表示缺失具体bean时。
@ConditionalOnWebApplication(type = Type.SERVLET), 是当该应用是基于Servlet的Web应用时。
@ConditionalOnMissingBean,是当Spring容器中不存在某个Bean时。
@ConditionalOnProperty,指定的属性是否有。如果没有(IfMissing),设为true。

  • 总结
  1. 利用@ConfigurationProperties注解,完成公共前缀配置的类创建
  2. 利用@Condional开头的注解,来实现特定条件下自动配置类
  3. 实现一个服务类,作为自动配置类的配置对象
  4. 利用springboot启动注解中依赖的@EnableAutoConfiguration注解,在resources/META-INF路径下的spring.factories里添加上需要自动配置的类
  5. done。

demo实现

上面讨论了一大堆源码分析和原理说明,下面直接以实现一个demo作为本文结束

我们实现一个demo,来进行springboot服务的作者信息的自动配置。实现的效果,就是我们在application.properties里增加对应的配置项,即可完成类的注入。

自动配置类的实现

首先我们需要创建一个项目,后续作为我们springboot项目的依赖。具体需要做的工作总结如下:

  1. 创建你一个maven项目,引入依赖:spring-boot-autoconfigure

  2. 创建一个属性类,包装需要自动配置的属性,该属性类上增加@ConfigurationProperties注解,在此注解上配置对应的前缀

  3. 创建一个Bean,作为自动配置完成后注入的bean

  4. 创建一个自动配置类,完成获取属性,封装实例bean的工作.这里我门用到ConditionOnClass注解,要求只有在AutherServer类存在时候,该类才注入。

  5. 在resources目录下,新建META-INF/spring.factories文件,配置需要进行自动配置的配置类,格式如下

  6. maven本地install

测试

  1. 我们创建一个新的springboot项目,引入之前创建项目的依赖
        <dependency>
            <groupId>com.trey</groupId>
            <artifactId>auther-autoconfigure</artifactId>
            <version>1.0.0</version>
        </dependency>
  1. application.properties里增加对应的配置,我们这边增加一个配置:auther.name=doudou

  2. 编写测试controller,启动服务测试

  3. 访问测试页面127.0.0.1:8888/auther,测试结果如下

demo代码分享

对于上述的demo代码,我已上传至github,github项目地址为:https://github.com/trey-tao/springboot-learn,可以下载学习。

这个项目主要是分享一些springboot的demo练习,会不定期更新,欢迎一起学习。

posted @ 2018-12-02 14:48  Trey  阅读(572)  评论(0编辑  收藏  举报