springboot学习01 – 自定义自动配置

概述

SpringBoot提供了自动配置能力。通过自动配置我们可以非常方便地启动相关的服务。

SpringBoot自动配置有两个核心模块:

  1. 自动配置模块(autoconfigure):主要负责读取配置相关的内容,并尝试启动服务;
  2. 启动模块(starter):提供具体的服务能力以及所有相关的依赖。

通常这两个模块是分开的。比如使用Caffeine缓存,缓存自动配置在一个独立的包中,Caffine缓存支持又是一个独立的包。如果不想把配置和能力分开,这两个模块也可以放在一起。

创建

接下来尝试创建一个自启动配置组件:hello-spring-boot-starter。这个组件功能很简单,就是在服务启动后自动打印一行“Hello xxx!”。

命名

springboot官方的自动配置包和自启动包都是以“spring-boot-”开头的。但是springboot不建议第三方开发者这样命名,应该是担心和官方支持出现冲突——即使现在没有冲突,未必以后官方不会推出相同的服务。即使使用了不同的groupId也仍然不建议这么做。

官方的建议是将具体的名称放“spring-boot”在前面。比如,我们要创建一个名为hello的自动配置组件,那么自动配置模块包可以命名为“hello-spring-boot-autoconfigure”,自启动模块包可以命名为“hello-spring-boot-starter”。如果要把这两个模块合并起来,那么包名是“hello-spring-boot-starter”。

配置项

如果自定义的自动配置组件提供了配置项,那么需要为配置项提供一个独立的名称空间。注意,尽量不要和spring-boot默认提供的名称空间(servermanagementspring等等)产生冲突。建议使用组件的关键字作为名称空间,比如我的组件名称是hello,那么名称空间就是hello。组件只有一个配置项name,最终yaml格式的配置就是:

然后需要为这些配置项创建一个配置描述类来表示记录配置信息,如:

配置描述类中需要包含全部配置项,以确保其生效。

下面是一些SpringBoot内部的配置类定义的准则:

  • 不以“the”或“a”开头
  • boolean类型的配置项,以“weather”或“enable”开头
  • 对于简单集合类型,尽量使用逗号分隔的字符串形式
  • 对于毫秒级的时间,使用 java.time.Duration替换 long类型
  • 如果时间不是毫秒级的,需要在meta-data中提供必要的提示,如:”If a duration suffix is not specified, seconds will be used”
  • 提供默认值要谨慎,如果默认值不是在运行时必需的就不要设置

为了能让idea等开发工具识别我们提供的配置项,还需要提供一个meta-data文件META-INF/spring-configuration-metadata.json

SpringBoot提供了annotationProcessor来辅助生成meta-data文件。我们只需要添加如下依赖即可:

不过annotationProcessor对集合类型支持得不是很好,使用的时候要慎重。

此外,annotationProcessor还能生成一个配置项元数据文件META-INF/spring-autoconfigure-metadata.properties。当存在这个文件的时候,就可以了用来对配置项进行初步的过滤,有助于减少启动耗时。

配置类

自动配置组件的配置类就是一个标准的配置类,所以它也需要使用 @Configuration注解。下面是一个配置类的示例:

示例代码中通过 @EnableConfigurationProperties注解引入了配置描述类。还提供了相应的构造器以便注入配置信息。

此外这里还装模作样的使用了条件注解 @ConditionalOnClassSystem.class是JRE的标配,因此这行注解实际上是没有任何作用的,在这里只是做个演示。条件注解通常多出现在自动配置中,以保证在满足设定条件后自动配置才能生效。关于条件注解前段时间写过一篇文:《SpringBoot条件注解》。这里就不重复啰嗦了。

因为自动配置组件要求放在独立的包中,而且包路径不能和应用包路径重合,所以需要提供一些帮助才能让SpringBoot识别我们提供的自动配置信息——这里是META-INF/spring.factories文件。SpringBoot会检查jar包中是否存在META-INF/spring.factories文件,并尝试读取解析文件中配置的类信息。关于读取解析spring.factories文件的过程在之前也有介绍过:《SpringBoot启动过程之getSpringFactoriesInstances》。

下面是一个spring.factories文件的示例:

应该可以看出spring.factories实际上就是一个典型的 .properties文件。

注意:SpringBoot自动配置组件只能通过这种形式加载。在定义组件包路径的时候就需要注意包路径不能是Spring componentScan的目标。同时,在自定义组件类中也不能使用componentScan来获取其它的组件。如有必要,可以使用 @Import注解代替(可以参考SpringBoot探索01- @Import注解)。

如果多个配置类之间存在先后顺序的话,可以使用 @AutoConfigureAfter@AutoConfigureBefore注解来确定顺序。比如,如果定义的是web相关的配置类,那么这个配置类可能就需要在 WebMvcAutoConfiguration之后生效。

如果想保证多个配置类的加载顺序,又不想让配置类之间存在显式的关联,那么可以使用 @AutoConfigureOrder注解。这个注解和普通的 @Order注解的作用是一样的,但是只能用于自动配置类。

启动类

关于启动类的作用,根据名称就可以猜出来:主要是负责组件服务的启动。前面配置类的示例代码中就有几行启动类相关的内容:

其中的 HelloStarter就是一个启动类。在配置类中创建注入了 HelloStarter的实例。具体的服务逻辑还是需要在启动类 HelloStarter中完成。

很多时候,启动模块和配置模块是分别放在独立的包中的,不过这里实现的功能比较简单,且无其它的依赖,所以就干脆放在一个jar中了。

看下 HelloStarter的实现:

只是在 HelloStarter实例注入完成后执行了一行输出语句。可以说是极为简单了。

测试

自动配置可能会被多种因素影响:

  • 用户自定义配置(Bean定义和自定义环境参数)
  • 条件分析(是否存在某个类或某个依赖)
  • 其它约束

执行具体测试的时候就需要为每种情形定义一个 ApplicationContext。这种情况下,使用 ApplicationContextRunner事情会变得很简单。

ApplicationContextRunner主要被用来搜集基础的、通用的配置信息。通常是作为成员变量定义在测试类中,如下例:

如果定义了多个配置类,不用在测试中刻意控制声明的顺序,SpringBoot会保证它们的触发顺序和正常启动时一致。

每个测试都可以使用contextRunner执行一类测试案例。在下面的示例代码中定义了一个新的配置类HelloConfiguration ,但是在新的配置类中创建的 HelloStarter Bean并不能覆盖自动配置中创建的同类的Bean:

因为没有提供配置信息,所以自动配置中创建的 HelloStarter Bean的name值是null。

在测试中使用了Assert4J来进行值的比较。

还可以自定义配置参数,如下:

contextRunne还可以展示 ConditionEvaluationReport,即条件注解检查过程日志。日志的级别可以设置为INFODEBUG,下面的测试代码使用了 ConditionEvaluationReportLoggingListener来打印条件注解检查过程日志:

借助于SpringBoot提供的 FilteredClassLoader,我们还能够验证在某个类或某个jar不存在的情况下自动配置如何处理。在下面的代码中,我们在类加载器中排除掉了 HelloStarter.class,这样自动配置就不会生效:

另外,如果我们需要的是Servlet或Reactive web应用Context,可以使用 WebApplicationContextRunner或者 ReactiveWebApplicationContextRunner

其它

这里的测试代码已经上传到了GitHub,见: GitHub/zhyea

不过这个自启动组件实现的功能太过简单,如果想深入了解下,可以参考SpringBoot官方提供的自启动配置。我自己还写过一个简易的kafka自启动组件,如果有兴趣也可以参考下。

还有一个自动配置演示项目也不错,在git:spring-boot-master-auto-configuration

参考

End!


发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据