• 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练习,会不定期更新,欢迎一起学习。

  • 相关阅读:
    【Azure 环境】自动化账号生成的时候怎么生成连接与证书
    IntelliJ IDEA 查看类继承关系图,太强大了!
    我们到底为什么要用 IoC 和 AOP
    二叉树、平衡二叉树、红黑树、B树、B+树与B*树
    B-Tree 和 B+Tree傻傻分不清楚
    13K点赞都基于 Vue+Spring 前后端分离管理系统ELAdmin,大爱
    Spring Boot快速开发企业级Admin管理后台
    盘点 Github 上的高仿 app 项目,B站 微博 微信等等
    LeSS 的诞生(一):大规模团队该何去何从
    同事有话说 | 那些所谓的敏捷仪式感
  • 原文地址:https://www.cnblogs.com/trey/p/10053459.html
Copyright © 2020-2023  润新知