欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot自动配置原理与实践(一)

程序员文章站 2022-04-09 08:53:49
前言 Spring Boot众所周知是为了简化Spring的配置,省去XML的复杂化配置(虽然Spring官方推荐也使用Java配置)采用Java+Annotation方式配置。如下几个问题是我刚开始接触Spring Boot的时候经常遇到的一些疑问,现在总结出来希望能帮助到更多的人理解Spring ......

前言

  spring boot众所周知是为了简化spring的配置,省去xml的复杂化配置(虽然spring官方推荐也使用java配置)采用java+annotation方式配置。如下几个问题是我刚开始接触spring boot的时候经常遇到的一些疑问,现在总结出来希望能帮助到更多的人理解spring boot,当然这只是个人的理解,稍微显得肤浅但易懂!当时我明白了以下几个问题后,觉得spring boot也不过如此,没有啥花里胡哨的,希望能帮到大家!

  本博文主要由两个部分组成:第一篇通过源码等形式介绍自动配置的原理与组成部分,第二篇通过实现自定义的starter实现自动配置加深对自动配置的理解。

  注:创作不易啊,虽然写只写了5个小时左右,但是整个准备过程很早就开始了,当然这样的记录方式主要是督促自己对spring boot要深入理解,顺带希望能帮到更多人

  问题一:那么spring boot是靠什么样的手段简化配置呢?

  从广义上讲,从软件设计范式来说。spring boot提供一种“约定优于配置”来简化配置;减少开发人员去开发没必要的配置模块时间,采用约定,并且对不符合约定的部分采用灵活配置满足即可!

  问题二:那么什么叫约定优于配置呢?

  比如:我们知道spring boot都是内嵌tomcat、jetty等服务器的,其中默认的tomcat服务器,相应的监听端口也默认绑定,这些默认即为约定大部分情况下我们都会使用默认默认的spring boot自带的内嵌的默认自动配置约定,不会大量去自定义配置,即这就是我理解的约定优于配置。那么问题三来了。当我们觉得不符合我们实际生产环境的时候才会去改变默认配置,那么需要很麻烦的手段吗?

  问题三:更改默认的端口或者服务器(即上述提到的约定)需要很麻烦的代码编写吗?甚至需要更改源码吗?

  答案肯定是否定的,只需要更改application.yml/properties(application-*.yml/properties)配置文件即可。当然也可以从源头上开始重新创建自己需要的模块(准确来说可以是starter),需要的注册的配置类bean。从而达到灵活的自动配置目的,这就是本篇要介绍的spring boot的自动配置。

  明白以上三个问题后,其实就能理解为什么spring boot相对于spring更加灵活,方便,简单的原因了========>无非就是大量使用默认自动配置+灵活的自动配置

 

 


 

一、spring boot自动配置的原理介绍

 

  主要从spring boot是如何启动后自动检测自动配置与spring boot如何灵活处理自动配置两个方面讲解,前者主要分析源码,从简单的注解入手到最后的读取spring.facoties文件。后者主要举例redisautoconfiguration自动配置来说明spring boot对于相关的配置类bean的通过注解的灵活处理。

1、spring boot是如何发现自动配置的?

都知道spring boot程序入口处(xxxapplication.java)都会有 @springbootapplication 的注解。这个注解表明该程序是主程序入口,也是springboot应用

@springbootapplication
public class demoapplication {

    public static void main(string[] args) {
        springapplication.run(demoapplication.class, args);
    }

}

查看 @springbootapplication 注解,不难发现是一个组合注解,包括了 @springbootconfiguration @enableautoconfiguration @componentscan 。换句话说这三个注解可以替代 @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}
)}
)

其中注解  @enableautoconfiguration  就是实现springboot自动配置的关键所在,值得注意的是@enablexxxx注解并不是springboot特有,在spring 3.x中就已经引出,比如:@enablewebmvc用来启用spring mvc而不需要xml配置,@enabletransactionmanagement注释用来声明事务管理而不需要xml配置。如此可见,@enablexxxx是用来开启某一个特定的功能,从而使用java来配置,省去xml配置。在springboot中自动配置的开启就是依靠注解  @enableautoconfiguration  ,继续查看注解接口 @enableautoconfiguration 

@target({elementtype.type})
@retention(retentionpolicy.runtime)
@documented
@inherited
@autoconfigurationpackage
@import({autoconfigurationimportselector.class})
public @interface enableautoconfiguration {
    string enabled_override_property = "spring.boot.enableautoconfiguration";

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

    string[] excludename() default {};
}

其中关键是 @import({autoconfigurationimportselector.class}) ,表示选择所有springboot的自动配置。废话不多说,还得继续看该选择器源码,其中最主要的方法是 getautoconfiguationentry 方法,该方法的主要功能是:

获得自动配置---->删除重复自动配置----->删除排除掉的自动配置---->删除过滤掉的自动配置------>最终的自动配置

Spring Boot自动配置原理与实践(一)

这里我们主要关注如何获取自动配置,即图中红圈部分,即 getcandidateconfigurations 方法,该方法通过springfactoresloader.loadfactorynames方法读取某个classpath下的文件获取所有的configurations

Spring Boot自动配置原理与实践(一)

注意到: no auto configuration classes found in meta-inf/spring.factories..... ,它应该是从meta-inf/spring.factories文件中下读取configurations,我们再往下查看方法 springfactoresloader.loadfactorynames ,该方法果然是读取meta-inf/spring.factories文件的,从中获取自动配置键值对。

 Spring Boot自动配置原理与实践(一)

最后,我们找到meta-inf/spring.factories文件(位于spring-boot-autoconfigure下)

Spring Boot自动配置原理与实践(一)

查看其内容:

...
# auto configure
org.springframework.boot.autoconfigure.enableautoconfiguration=\
org.springframework.boot.autoconfigure.admin.springapplicationadminjmxautoconfiguration,\
org.springframework.boot.autoconfigure.aop.aopautoconfiguration,\
org.springframework.boot.autoconfigure.amqp.rabbitautoconfiguration,\
org.springframework.boot.autoconfigure.batch.batchautoconfiguration,\
org.springframework.boot.autoconfigure.cache.cacheautoconfiguration,\
org.springframework.boot.autoconfigure.cassandra.cassandraautoconfiguration,\
org.springframework.boot.autoconfigure.cloud.cloudserviceconnectorsautoconfiguration,\
org.springframework.boot.autoconfigure.context.configurationpropertiesautoconfiguration,\
org.springframework.boot.autoconfigure.context.messagesourceautoconfiguration,\
org.springframework.boot.autoconfigure.context.propertyplaceholderautoconfiguration,\
org.springframework.boot.autoconfigure.couchbase.couchbaseautoconfiguration,\
org.springframework.boot.autoconfigure.dao.persistenceexceptiontranslationautoconfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.cassandradataautoconfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.cassandrareactivedataautoconfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.cassandrareactiverepositoriesautoconfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.cassandrarepositoriesautoconfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.couchbasedataautoconfiguration,\
...

 这时候才恍然大悟,原来如此。简单总结起来就是,当springboot应用启动读取自动配置之后,从上到下的相关调用栈(文字简单描述)

  • @enableautoconfiguration开启启动配置功能
  • 通过@import导入autoconfigurationimportselector选择器发现(注册)自动配置
  • getautoconfigurationentry方法获取并处理所有的自动配置
  • springfactoriesloader.loadfactorynames读取spring.factories文件并加载自动配置键值对。

 

 2、spring boot是如何灵活处理自动配置的?

 

  从spring.factories文件中我们选取redisautoconfiguration举例说明,至于为什么选这个呢?这是因为之前我有写过关于spring boot整合redis的相关博文。所以好入手!

首先,还是一样进入redisautoconfiguration(idea大法好,点击进入即可查看代码)

Spring Boot自动配置原理与实践(一)

源码如下,主要关注以下几个注解:

1)@conditionalonclass在当前classpath下是否存在指定类,若是则将当前的配置注册到spring容器。可见其灵活性。相关的@conditionalxxx注解是spring 4.x推出的灵活注册bean的注解,称之为“条件注解”。有如下一系列条件注解:

  • @conditionalonmissingbean: 当spring容器中不存在指定的bean时,才会注册该bean。
  • @conditionalonmissingclass:当不存在指定的类时,才会注册该bean。
  • @conditionalonproperty:根据配置文件中某个属性来决定是否注册该bean,相对于其它条件注解较为复杂。主要有以下两种情况应用:

  一种是@conditionalonproperty(prefix = “mysql”, name = “enable”, havingvalue = “true”)表示以为mysql为前缀的mysql.enable属性如果为空或者不是true时不会注册指定bean,只有mysql.enable属性为true时候才会注册该指定bean

    一种是@conditionalonproperty(prefix = “mysql”, name = “enable”, havingvalue = “true”, matchifmissing = true)matchifmissing表示如果没有以mysql为前缀的enable属性,则为true表示符合条件,可以注册该指定bean,默认为false。

  • @conditionalonjava:是否是java应用
  • @conditionalonwebapplication:是否是web应用
  • @conditionalonexpression:根据表达式判断是否注册bean

  ...........剩下的还有很多,在autoconfigure.condition包下...........

    Spring Boot自动配置原理与实践(一)

 

2)@enableconfigurationproperties让 @configurationproperties 注解的properties类生效并使用。如让redisproperties生效并注册到spring容器中,并且使用!

3)@conditionalonmissingbean当前spring容器中没有该bean(name或者class指定),则注册该bean到spring容器。

@configuration
@conditionalonclass({redisoperations.class})
@enableconfigurationproperties({redisproperties.class})
@import({lettuceconnectionconfiguration.class, jedisconnectionconfiguration.class})
public class redisautoconfiguration {
    public redisautoconfiguration() {
    }

    @bean
    @conditionalonmissingbean(
        name = {"redistemplate"}
    )
    public redistemplate<object, object> redistemplate(redisconnectionfactory redisconnectionfactory) throws unknownhostexception {
        redistemplate<object, object> template = new redistemplate();
        template.setconnectionfactory(redisconnectionfactory);
        return template;
    }

    @bean
    @conditionalonmissingbean
    public stringredistemplate stringredistemplate(redisconnectionfactory redisconnectionfactory) throws unknownhostexception {
        stringredistemplate template = new stringredistemplate();
        template.setconnectionfactory(redisconnectionfactory);
        return template;
    }
}

 由此可见spring boot对于自动配置的灵活性,可以传递给我们一种信息,spring boot是如何灵活处理自动配置的?

往大了说,就是你可以选择约定使用大量默认配置简化开发过程,你也可以自定义开发过程中需要的自动配置。往细了说,就是通过各式各样的条件注解注册需要的bean来实现灵活配置。

那么实现一个自动配置有哪些重要的环节呢?

继续往下看第二部分!

 

二、spring boot自动配置的重要组成部分

 

  上面只是对自动配置中几个重要的注解做了简单介绍,但是并没有介绍要开发一个简单的自动配置需要哪些环节(部分),同样地我们还是以redisautoconfiguration与rabbitautoconfiguration为例,主要查看注解头:

redisautoconfiguration:

@configuration
@conditionalonclass({redisoperations.class})
@enableconfigurationproperties({redisproperties.class})
@import({lettuceconnectionconfiguration.class, jedisconnectionconfiguration.class})

rabbitautoconfiguration:

@configuration
@conditionalonclass({rabbittemplate.class, channel.class})
@enableconfigurationproperties({rabbitproperties.class})
@import({rabbitannotationdrivenconfiguration.class})

你会发现通常的一般的autoconfiguration都有@configuration,@conditionalonclass,@enableconfigurationproperties,@import 注解:

  • @configuration:自然不必多说,表示是配置类,是必须存在的!
  • @conditionalonclass:表示该配置依赖于某些class是否在classpath中,如果不存在那么这个配置类是毫无意义的。也可以说是该class在配置类中充当重要的角色,是必须存在的!。在后面的自定义spring boot starter中我们可以简单统称为服务类
  • @enableconfigurationproperties:读取配置文件,需要让相关的properties生效,每个自动配置都离不开properties属性,是必须存在的!
  • @import:导入配置类中需要用到的bean,通常是一些配置类。表示此时的自动配置类需要基于一些配置类而实现自动配置功能。当然不是必须的,有些是没有这个注解的,是不需要基于某些配置类的!

那么,可以简单总结得到一个自动配置类主要有以下几部分组成(单纯是根据个人理解总结出来的,没有官方说明)

  • properties bean配置属性:用来读取spring配置文件中的属性,@enableconfigurationproperties与@configurationproperties结合使用,具体请看下一篇实践stater例子。
  • 该配置类依赖的一些class,使用@conditionalclass判断;
  • 该配置类依赖的一些配置bean,使用@import导入。
  • 可能还有加载顺序的控制,如@autoconfigureafter,@autoconfigureorder等
  • 一些bean的加载,往往通过方法返回object,加@bean以及一些条件注解来实现

========================================未完待续(下一篇补充自定义starter会涉及自动配置组成部分)==========================================