17.SpringBoot学习(十七)——Spring Boot 自定义Starter

1.简介

1.1 概述

A typical Spring Boot starter contains code to auto-configure and customize the infrastructure of a given technology, let’s call that "acme". To make it easily extensible, a number of configuration keys in a dedicated namespace can be exposed to the environment. Finally, a single "starter" dependency is provided to help users get started as easily as possible.

典型的Spring Boot Starter包含用于自动配置和自定义特定技术的基础架构的代码,我们称其为“ acme”。为了使其易于扩展,可以将专用命名空间中的许多配置项公开给环境。最后,提供了一个 starter 依赖项,以帮助用户尽可能轻松地使用它。

1.2 特点

Concretely, a custom starter can contain the following:

  • The autoconfigure module that contains the auto-configuration code for "acme".
  • The starter module that provides a dependency to the autoconfigure module as well as "acme" and any additional dependencies that are typically useful. In a nutshell, adding the starter should provide everything needed to start using that library.

This separation in two modules is in no way necessary. If "acme" has several flavours, options or optional features, then it is better to separate the auto-configuration as you can clearly express the fact some features are optional. Besides, you have the ability to craft a starter that provides an opinion about those optional dependencies. At the same time, others can rely only on the autoconfigure module and craft their own starter with different opinions.

If the auto-configuration is relatively straightforward and does not have optional feature, merging the two modules in the starter is definitely an option.

具体而言,自定义启动器可以包含以下内容:

  • 自动配置模块,其中包含“ acme”的自动配置代码。
  • 启动程序模块,它提供对自动配置模块的依赖以及“ acme”和通常有用的任何其他依赖。简而言之,添加启动程序应提供开始使用该库所需的一切。

完全没有必要将这两个模块分开。如果“ acme”具有多种功能,选项或可选功能,则最好将自动配置分开,因为您可以清楚地表示某些功能是可选的。此外,您还可以制作一个启动器,以提供有关那些可选依赖项的意见。同时,其他人只能依靠自动配置模块并以不同的意见来制作自己的启动器。如果自动配置相对简单并且不具有可选功能,则将两个模块合并在启动器中绝对是一种选择。

image-20200730193439123

2.演示环境

  1. JDK 1.8.0_201
  2. Spring Boot 2.2.0.RELEASE
  3. 构建工具(apache maven 3.6.3)
  4. 开发工具(IntelliJ IDEA )

3.演示代码

3.1 代码说明

包含两个项目,两个项目的作用如下:

  • demo-spring-boot-starter:自定义的starter,有一个Hello接口和几个实现类,通过自动装配类从外部获取配置来选择具体要激活的实例,同时helloTemplate的模板工具类来调用Hello中的hello方法。
  • test-spring-boot-starter:依赖 demo-spring-boot-starter,测试它的功能

3.2 代码结构

demo-spring-boot-starter

image-20200730193759728

test-spring-boot-starter

image-20200730193916069

3.3 maven 依赖

demo-spring-boot-starter

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

test-spring-boot-starter

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.soulballad.usage</groupId>
        <artifactId>demo-spring-boot-starter</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.4 配置文件

demo-spring-boot-starter

META-INF\spring.factories

# 配置自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.soulballad.usage.springboot.autoconfiguration.HelloAutoConfiguration

test-spring-boot-starter

application.properties

com.soulballad.hello.java.enable=true
com.soulballad.hello.detail.name=zhangsan
com.soulballad.hello.detail.age=20

3.5 java代码

demo-spring-boot-starter

Hello.java

public interface Hello {

    String hello();
}

HelloGirl.java

public class HelloGirl implements Hello {
    @Override
    public String hello() {
        return "hello girl";
    }
}

HelloJava.java

public class HelloJava implements Hello{
    @Override
    public String hello() {
        return "hello java";
    }
}

HelloWorld.java

public class HelloWorld implements Hello{
    @Override
    public String hello() {
        return "hello world";
    }
}

HelloProperties.java

@Component
@ConfigurationProperties(prefix = HelloProperties.HELLO_PREFIX)
public class HelloProperties {

    public static final String HELLO_PREFIX = "com.soulballad.hello";

    private Map<String, Object> detail;

    public Map<String, Object> getDetail() {
        return detail;
    }

    public void setDetail(Map<String, Object> detail) {
        this.detail = detail;
    }
}

HelloAutoConfiguration.java

@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Bean
    public HelloTemplate helloTemplate(Hello hello, HelloProperties helloProperties) {
        return new HelloTemplate(hello, helloProperties);
    }

    @Bean
    @Primary
    @ConditionalOnProperty(prefix = HelloProperties.HELLO_PREFIX + ".world", name = "enable", havingValue = "true")
    public Hello helloWorld() {
        return new HelloWorld();
    }

    @Bean
    @ConditionalOnProperty(prefix = HelloProperties.HELLO_PREFIX + ".java", name = "enable", havingValue = "true")
    public Hello helloJava() {
        return new HelloJava();
    }

    @Bean
    @ConditionalOnProperty(prefix = HelloProperties.HELLO_PREFIX + ".girl", name = "enable", havingValue = "true")
    public Hello helloGirl() {
        return new HelloGirl();
    }
}

HelloTemplate.java

public class HelloTemplate {

    private Hello hello;
    private HelloProperties helloProperties;

    public HelloTemplate(Hello hello, HelloProperties helloProperties) {
        this.hello = hello;
        this.helloProperties = helloProperties;
    }

    public String hello() {
        Map<String, Object> objectMap = helloProperties.getDetail();
        String helloName = hello.getClass().getSimpleName();
        String description = "helloName: " + helloName + ", properties: " + objectMap.toString();
        return hello.hello() + ", " + description;
    }
}

test-spring-boot-starter

HelloController.java

@RestController
public class HelloController {

    @Autowired
    private HelloTemplate helloTemplate;

    @GetMapping(value = "/hello")
    public String hello() {
        return helloTemplate.hello();
    }
}

3.6 git 地址

spring-boot/spring-boot-11-custom-starter

4.效果展示

使用maven将 demo-spring-boot-starter 打成 jar 包,然后在 test-spring-boot-starter 中依赖这个 jar包

启动 test-spring-boot-starter 中 TestSpringBootStarterApplication.main 方法,在 test-spring-boot-starter.http 访问下列地址,观察输出信息是否符合预期。

### GET /hello
GET http://localhost:8080/hello

image-20200730195430873

5.源码分析

5.1 自定义 starter 原理分析

demo-spring-boot-starter 中 Hello 接口定义了 hello 方法,它又三个实现类,分别是:HelloGirl、HelloJava、HelloWorld。

它们三个都是在 HelloAutoConfiguration 中进行的声明,声明时通过条件注解 @ConditionalOnProperty 来区分具体要激活的 bean,而条件注解的值通过外部配置传入,所以就将选择权交给了使用者;同时这里还声明了 HelloTemplate,HelloTemplate 需要用到的属性值通过 HelloProperties 从外部配置文件中传入。

image-20200730200120564

HelloAutoConfiguration 配置在 META-INF\spring.factories 中,spring boot 会通过 SpringFactoriesLoader 来自动加载该位置的类,这是一种 SPI 机制,所以依赖了 demo-spring-boot-starter 的项目,会自动加载 HelloAutoConfiguration 这个装配类。

test-spring-boot-starter 中的 application.properties 中配置了 com.soulballad.hello.java.enable=true 配置,所以会自动激活 HelloJava 的 bean;同时配置的HelloProperties 中其他属性也会一并被加载到上下文中。

image-20200730200329099

所以在 HelloController 中调用 helloTemplate 的 hello 方式时,最终会调用到 HelloJava 类。

6.参考

  1. Spring Boot Features/Starter
posted @ 2020-08-02 22:30  Soulballad  阅读(166)  评论(0编辑  收藏  举报