手写spring-boot-starter实战

前言

我们都知道,Spring Boot最大的特点就是自动装配,简化依赖,可以让我们快速的搭建项目。使用Spring Boot之后,我们要想在项目中使用一些其他框架,只需要引入对应的Starter依赖就可以了。

那么你在实际开发中是否也开发过一些基础功能,这些功能需要在你们的Spring Boot项目中使用呢?

而这些功能可能在其他项目中可能也需要用到,如果我们把相同的功能在另一个项目中重新再写一遍的话肯定不是一个好方法。

我们可以将我们的功能做成一个对应的Starter模块,在项目中要使用时直接作为Maven依赖添加进去就OK了,这样一来,不仅不用重复开发,并且在功能做升级时,也能有更好的版本管理。

构建一个spring-boot-starter

接下来我们来构建一个spring-boot-starter,为了方便描述,假设我们的这个Starter的主要功能是用来方便的发送和接收异步事件。注意我们本文的目的主要是讲如何构建Starter,所以对于发送接收异步事件的实现细节不会展开描述。

基础概念

在开始构建之前,我们需要先同步几个基本的概念。

Application Context

只要你对Spring有所了解,那么一定知道Application Context,这是一个用于管理Spring bean的容器,它包含我们项目中所有的Controller、Service、Repository、Component等所有的对象。

@Configuration

@Configuration 是Spring中的一个注解,使用该注解标注的类相当于是Spring的xml配置文件,其主要目的是为Application Context提供一些Bean对象。你可以理解为 @Configuration 标注的类就是一个Bean对象的工厂。

Auto-Configuration

在Spring Boot中有一个 @EnableAutoConfiguration 注解,它通过读取 spring.factories 文件里的 EnableAutoConfiguration 下面指定的类,来初始化指定类下面的所有加了 @Bean 的方法,并初始化这Bean。并且可以指定条件,按照具体的条件决定是否要自动装配Bean对象。

创建工程

接下来我们开始我们Starter的构建,我们给它一个名字叫 spring-boot-starter-eventmessage

假设我们需要在一个微服务环境中,要实现一个允许各个服务之间进行异步通信。我们的 spring-boot-starter-eventmessage 主要提供以下功能:

  • 首先,我们需要有一个 EventPublisher 对象,这个对象用于将事件发送到消息中心;
  • 然后需要有一个 EventListener 类,用于从消息中心订阅特定的事件;

因为我们的Starter要在多个微服务应用中使用,所以我们的Starter要能够使用Maven构建,方便其他应用引入。

首先我们来创建一个maven项目 spring-boot-starter-eventmessage

pom.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.heiz123</groupId>
    <artifactId>spring-boot-starter-eventmessage</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.4.2</version>
        </dependency>
    </dependencies>

</project>

让starter自动装配

接下来我们需要对我们的Starter功能提供一个入口,我们需要提供一个 @Configuration 类:

@Configuration
public class EventAutoConfiguration {

    @Bean
    public EventPublisher eventPublisher(List<EventListener> listeners) {
        return new EventPublisher(listeners);
    }

EventAutoConfiguration 包括我们提供Starter功能所需的所有@Bean定义。因为我们的Starter只需要提供一个事件发布的功能,所以这里主需要往 ApplicationContext 中添加一个 EventPublisher 对象。

我们的 EventPublisher 需要知道所有的 EventListener ,这样它才能将事件传递给它们,所以我们让Spring注入 ApplicationContext 中所有可用的 EventListener 的列表。

接下来到了重要环节,如何让我们的Starter能够自动配置呢,需要在 META-INF/ 目录中创建一个 spring.factories 文件。

在该文件中配置我们的 Configuration 类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.heiz123.event.EventAutoConfiguration

Spring Boot会搜索classpath下的所有 spring.Factories 文件,并加载其中声明的配置。

有了 EventAutoConfiguration 类之后,我们现在就有了一个可以自动装配spring-boot-starter的入口点。

让Starter支持可选

在实际开发场景中,可能我们对某一功能需要关闭,等到测试环境和线上环境时才会打开,那么我们的starter应该支持功能的关闭或者禁用。

我们可以使用Spring Boot的 Conditional 注解使我们的 Configuration 成为可选的:

@Configuration
@ConditionalOnProperty(value = "eventstarter.enabled", havingValue = "true")
@ConditionalOnClass(name = "com.heiz123.RabbitConnector")
@Slf4j
public class EventAutoConfiguration {

    @Bean
    public EventPublisher eventPublisher(List<EventListener> listeners) {
        log.info("event-message 自动装配中...");
        return new EventPublisher(listeners);
    }
}

@ConditionalOnProperty 注解的作用是只有 eventstarter.enabled 属性设置为 true ,才会将 EventAutoConfiguration 添加到 ApplicationContext 中。这样在使用了我们starter的应用中就可以通过 eventstarter.enabled 属性来动态控制我们的starter是否启用。

@ConditionalOnClass 注解的作用是仅在 classpath 上有 com.heiz123.RabbitConnector 类时才激活自动配置。

让Starter可配置

在我们的 EventAutoConfiguration 中,需要注入应用中所有的 EventListener 到我们的 EventPublisher 中,但是有可能使用我们Starter的应用并不想让所有的 EventListener 都处理,可能只对一部分Event关注,那么我们就应该支持使用方能够便捷地进行配置。

比如在application.properties文件或者application.yml文件中可以进行如下配置:

eventstarter.listener.enabled-events:foo,bar

yml文件:

eventstarter:
  listener:
    enabled-events:
      - foo
      - bar

为了在我们的Starter的代码中能轻松访问这些属性,我们可以提供一个 @ConfigurationProperties 类:

@ConfigurationProperties(prefix = "eventstarter.listener")
class EventListenerProperties {

    /**
     * 会被传递给EventListener中的所有Event的类型。
     * 其他Event将会被忽略
     */
    private List<String> enabledEvents = Collections.emptyList();

    public List<String> getEnabledEvents() {
        return enabledEvents;
    }

    public void setEnabledEvents(List<String> enabledEvents) {
        this.enabledEvents = enabledEvents;
    }
}

然后我们通过使用 @EnableConfigurationProperties 注解我们的入口点配置来启用 EventListenerProperties 类:

@Configuration
@EnableConfigurationProperties(EventListenerProperties.class)
@Slf4j
public class EventAutoConfiguration {

    @Bean
    public EventPublisher eventPublisher(List<EventListener> listeners) {
        log.info("event-message 自动装配中...");
        return new EventPublisher(listeners);
    }
}

最后,我们可以在需要的地方注入 EventListenerProperties 对象,例如在抽象的 EventListener 类中过滤掉不关注的事件:

public abstract class EventListener {

    private final EventListenerProperties properties;

    public EventListener(EventListenerProperties properties) {
        this.properties = properties;
    }

    public void receive(Event event) {
        // 如果事件是Eanbled并且订阅了。
        if (isEnabled(event) && isSubscribed(event)) {
            onEvent(event);
        }
    }

    private boolean isSubscribed(Event event) {
        return event.getType().equals(getSubscribedEventType());
    }

    private boolean isEnabled(Event event) {
        return properties.getEnabledEvents().contains(event.getType());
    }

    protected abstract String getSubscribedEventType();

    protected abstract void onEvent(Event event);
}

到这里我们的Starter基本就可以提供给别人使用了。

开始使用

实践是检验真理的唯一标准。我们来在一个新项目中引入一下我们的 spring-boot-starter-eventmessage

首先创建一个spring-boot项目,我们就叫 test-starter

然后我们只需要在Maven中添加Starter的依赖坐标信息。

        <dependency>
            <groupId>com.heiz123</groupId>
            <artifactId>spring-boot-starter-eventmessage</artifactId>
            <version>1.0.0</version>
        </dependency>

接下来启动我们的SpringBoot项目。启动类如下:

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        try {
            SpringApplication.run(Main.class, args);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

运行结果:

说明我们已成功集成 spring-boot-starter-eventmessage

总结

我们可以将某些特性功能包装到Starter程序中,方便在任何其他Spring Boot应用中很方便快捷地使用。要构建一个Starter只需要简单的几个步骤:

提供自动配置类Configuration,让它可以被禁用和启用,对于一些可变的信息提供可配置的参数。

以上就是本期的全部内容,如果对你有帮助,记得给我点赞三连!


作者:小黑说Java
链接:来!手写一个spring-boot-starter - 掘金