手把手教你定制标准 Spring Boot starter

写在前面
咱们每次构建一个 Spring 应用程序时,咱们都不但愿从头开始实现具备「横切关注点」的内容;相反,咱们但愿一次性实现这些功能,并根据须要将它们包含到任何咱们要构建的应用程序中
横切关注点
横切关注点: 指的是一些具备横越多个模块的行为 (来自维基百科的介绍)
说白了就是多个项目或模块均可以用到的内容,好比一个 SDKweb

在Spring Boot中,用于表示提供这种横切关注点的模块的术语是 starter,经过依赖 starter 能够轻松使用其包含的一些功能特性,不管你的工做中是否会构建本身的 starter,你都要具备构建 「starter」的思想,本文将结合 Spring Boot 官方标准构建一个简单的 starter面试

自定义 starter
在咱们深刻了解如何自定义 starter 以前,为了更好的理解咱们每一步在干什么,以及 starter 是如何起做用的,咱们先从宏观角度来看 starter 的结构组成究竟是什么样的spring

一般一个完整的 starter 须要包含下面两个组件:json

  1. Auto-Configure Module
  2. Starter Module

若是你看下面这两个组件的解释有些抽象,大概了解一下,阅读完该文章回看这里就会豁然开朗了mybatis

Auto-Configure Module
Auto-Configure Module (自动配置模块) 是包含自动配置类的 Maven 或 Gradle 模块。经过这种方式,咱们能够构建能够自动贡献于应用程序上下文的模块,以及添加某个特性或提供对某个外部库的访问并发

Starter Module
Spring Boot Starter 是一个 Maven 或 Gradle 模块,其惟一目的是提供 "启动" 某个特性所需的全部依赖项。能够包含一个或多个 Auto-Configure Module (自动配置模块)的依赖项,以及可能须要的任何其余依赖项。这样,在Spring 启动应用程序中,咱们只须要添加这个 starter 依赖就可使用其特性
: Spring 官方参考手册建议将自动配置分离,并将每一个自动配置启动到一个独立的 Maven 或 Gradle 模块中,从而将自动配置和依赖项管理分离开来。若是你没有创建一个供成千上万用户使用的开源库,也能够将两者合并到一个 module 中
You may combine the auto-configuration code and the dependency management in a single module if you do not need to separate those two concernsapp

命名
来自 Spring 官方的 starter 都是 以 spring-boot-starter 开头,好比:maven

  • spring-boot-starter-web
  • spring-boot-starter-aop
    若是咱们自定义 starter 功能名称叫acme,那么咱们的命名是这样的:
  • acme-spring-boot-starter
  • acme-spring-boot-autoconfigure
    若是 starter 中用到了配置 keys,也要注意不要使用 Spring Boot 使用的命名空间,好比(server,management,spring)

Parent Module 建立
先来全局看一下项目结构:
一级目录结构:
.ide

├── pom.xml
├── rgyb-spring-boot-autoconfigure
├── rgyb-spring-boot-sample
└── rgyb-spring-boot-starter

二级目录结构:
.spring-boot

├── pom.xml
├── rgyb-spring-boot-autoconfigure
│   ├── pom.xml
│   └── src
├── rgyb-spring-boot-sample
│   ├── pom.xml
│   └── src
└── rgyb-spring-boot-starter
    ├── pom.xml
    └── src

建立一个空的父亲 Maven Module,主要提供依赖管理,这样 SubModule 不用单独维护依赖版本号,来看 pom.xml 内容:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

    <!--  添加其余全局依赖管理到这里,submodule默认不引入这些依赖,须要显式的指定  -->
</dependencyManagement>

Auto-Configure Module 构建
新建类 GreetingAutoConfigurationbr/>@Configuration
public class GreetingAutoConfiguration {

@Bean
public GreetingService greetingService(GreetingProperties greetingProperties){
    return new GreetingService(greetingProperties.getMembers());
}

}
咱们用 @Configuration 注解标记类 GreetingAutoConfiguration,做为 starter 的入口点。这个配置包含了咱们须要提供starter特性的全部 @Bean 定义,在本例中,为了简单阐述问题,咱们只将 GreetingService Bean 添加到应用程序上下文

GreetingService 内容以下:

@AllArgsConstructor
public class GreetingService {

    private List<String> members = new ArrayList<>();

    public void sayHello(){
        members.forEach(s -> System.out.println("hello " + s));
    }
}

在 resources 目录下新建文件 META-INF/spring.factories (若是目录 META-INF 不存在须要手工建立),向文件写入内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
top.dayarch.autoconfigure.GreetingAutoConfiguration
Spring 启动时会在其 classpath 中全部的 spring.factoreis文件,并加载里面的声明配置,GreetingAutoConfiguration 类就绪后,咱们的 Spring Boot Starter 就有了一个自动激活的入口点

到这里这个 "不彻底的 starter" 已经可使用了。但由于它是自动激活的,为了个让其灵活可用,咱们须要让其按照咱们的意愿来激活使用,因此咱们须要条件注解来帮忙

条件配置
为类添加两个条件注解:

@Configuration
@ConditionalOnProperty(value = "rgyb.greeting.enable", havingValue = "true")
@ConditionalOnClass(DummyEmail.class)
public class GreetingAutoConfiguration {
    ...
}

经过使用 @ConditionalOnProperty 注解,咱们告诉 Spring,只有属性 rgyb.greeting.enable值被设置为 true 时,才将 GreetingAutoConfiguration (以及它声明的全部 bean ) 包含到应用程序上下文中
经过使用 @ConditionalOnClass 注解,咱们告诉Spring 只有类 DummyEmail.class 存在于 classpath 时,才将 GreetingAutoConfiguration (以及它声明的全部 bean ) 包含到应用程序上下文中
多个条件是 and/与的关系,既只有知足所有条件时,才会加载 GreetingAutoConfiguration

若是你对条件注解的使用还不是很明确,能够查看我以前的文章: @Conditional注解,灵活配置 Spring Boot

配置属性管理
上面使用了 @ConditionalOnProperty 注解,实际 starter 中可能有很是多的属性,因此咱们须要将这些属性集中管理:

@Data
@ConfigurationProperties(prefix = "rgyb.greeting")
public class GreetingProperties {

    /**
     * GreetingProperties 开关
     */
    boolean enable = false;

    /**
     * 须要打招呼的成员列表
     */
    List<String> members = new ArrayList<>();
}

咱们知道这些属性是要在 application.yml 中使用的,当咱们须要使用这些属性时,为了让 IDE 给出更友好的提示,咱们须要在 pom.xml 中添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

这样当咱们 mvn compile 时,会在生成一个名为 spring-configuration-metadata.json JSON 文件,文件内容以下:

手把手教你定制标准 Spring Boot starter

生成的内容在接下来的内容中用到,且看

提高启动时间
对于类路径上的每一个自动配置类,Spring Boot 必须计算 @Conditional… 条件值,用于决定是否加载自动配置及其所需的全部类,根据 Spring 启动应用程序中 starter 的大小和数量,这多是一个很是昂贵的操做,而且会影响启动时间,为了提高启动时间,咱们须要在 pom.xml 中添加另一个依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>

这个注解会生成一个名为 spring-autoconfigure-metadata.properties Property 文件,其内容以下:

手把手教你定制标准 Spring Boot starter

这样,Spring Boot 在启动期间读取这些元数据,能够过滤出不知足条件的配置,而没必要实际检查这些类,提高启动速度

到这里关于 Auto-Configure Module 就构建完了,咱们须要继续完成 Starter Module 的构建

Starter Module 构建
Starter Module 的构建很简单了,你能够认为它就是一个空 module,除了依赖 Auto-Configure Module,其惟一做用就是为了使用 starter 功能特性提供全部必须依赖,因此咱们为 starter module 的 pom.xml 文件添加以下内容:

<dependencies>
    <dependency>
        <groupId>top.dayarch.learnings</groupId>
        <artifactId>rgyb-spring-boot-autoconfigure</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>

    <!-- 在此处添加其余必要依赖,保证starter可用 -->
</dependencies>

一样在 resources 目录下新建文件 META-INF/spring.providers , 其内容以下:
providers: rgyb-spring-boot-autoconfigure
该文件主要做用是说明 starter module 的依赖信息,多个依赖以逗号分隔就好,该文件不会影响 starter 的使用,无关紧要

Starter Module 就能够这么简单,将两个 module 分别 mvn install 到本地 Maven Repository,接下来咱们建立 sample module 引入这个 starter 依赖时就会从本地 Maven Repository 中拉取

建立 Sample Module
咱们能够经过 Spring Initializr 正常初始化一个 Spring Boot 项目 (rgyb-spring-boot-sample),引入咱们刚刚建立的 starter 依赖,在 sample pom.xml 中添加依赖:

<dependency>
    <groupId>top.dayarch.learnings</groupId>
    <artifactId>rgyb-spring-boot-starter</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

接下来配置 application.yml 属性

rgyb:
  greeting:
    enable: true
    members:
      - 李雷
      - 韩梅梅

在咱们配置 YAML 的时候,会出现下图的提示,这样会更友好,固然为了规范,属性描述最好也用英文描述,这里为了说明问题用了中文描述:

手把手教你定制标准 Spring Boot starter

编写测试类
咱们编写测试用例:

@Autowired(required = false)
private GreetingService greetingService;

@Test
public void testGreeting() {
    greetingService.sayHello();
}

测试结果以下:

hello 李雷
hello 韩梅梅

总结
到这里完整的 starter 开发就结束了,但愿你们了解其构建过程,目录结构及命名等标准,这样有相应的业务需求时均可以开发本身的 starter 被其余人应用起来

starter 开发好了,别人能够手动添加依赖引入 starter 的相关功能,那咱们如何像 Spring Initializr 同样,经过下来菜单选择咱们的 starter 呢,这样直接初始化好整个项目,接下来的文章咱们会模仿 Spring Initializr 自定义咱们自的 Initializr

知识点说明
Dependency optinal
为何 Auto-Configure Module 的 dependency 都是 optional = true 呢?

这涉及到 Maven 传递性依赖的问题,详情请看 Maven 依赖传递性透彻理解

spring.factories
Spring Boot 是如何加载这个文件并找到咱们的配置类的

下图是 Spring Boot 应用程序启动的调用栈的一部分,我添加了断点:
手把手教你定制标准 Spring Boot starter

打开 SpringFactoriesLoader 类,映入眼帘的就是这个内容:
手把手教你定制标准 Spring Boot starter

这两张图应该足够说明问题了,是 SPI 的一种加载方式,更细节的内容请你们本身去发现吧

实际案例
这里推荐查看 mybatis-spring-boot-starter 这个非 Spring 官方的案例,从中咱们:

  • 模仿其目录结构
  • 模仿其设计理念
  • 模仿其编码规范
    手把手教你定制标准 Spring Boot starter

另外,本文的案例我已上传,公众号回复「demo」,打开连接,查看 customstarter 目录下内容便可

灵魂追问

  1. 在生成 spring-autoconfigure-metadata.properties 文件时,为何 @ConditionalOnProperty 的内容没有被写进去
  2. 若是咱们要将依赖上传至 remote central repository,你知道怎样搭建本身的 maven repository 吗?
  3. 你的灯还亮着吗?

提早发现更多精彩,请访问: https://dayarch.top

提升效率工具

手把手教你定制标准 Spring Boot starter

手把手教你定制标准 Spring Boot starter
手把手教你定制标准 Spring Boot starter

  • 读取Excel还用POI?试试这款开源工具
  • Maven optional 关键字透彻图解
  • 如何避免死锁,咱们有套路可循
  • 面试并发volatile关键字时,咱们应该具有哪些谈资?
  • 如何设计好的RESTful API

手把手教你定制标准 Spring Boot starter

tan日拱一兵转发在看也很赞喜欢做者

相关文章
相关标签/搜索