1. 概述

Spring Boot 核心团队为大多数热门开源项目提供了 starter,但我们并不局限于此。我们也可以编写自己的自定义 starter。如果组织内部有需要在 Spring Boot 环境中使用的库,为它编写一个 starter 是很好的实践。

这些 starter 能帮助开发者避免冗长的配置,快速启动开发。但背后隐藏的机制有时让人困惑:为什么一个注解或仅在 pom.xml 中添加依赖就能启用这么多功能?

本文将揭开 Spring Boot 的神秘面纱,深入理解其幕后机制。然后运用这些知识,为自定义库创建一个 starter。

2. 揭秘 Spring Boot 自动配置

2.1. 自动配置类

从 2.7 版本开始,Spring Boot 启动时会从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中加载自动配置类。以 spring-boot-autoconfigure 项目为例,其文件内容如下:

...
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
...

可见,文件中每行都是一个自动配置类的全限定名,Spring Boot 会尝试加载这些类。根据上述代码片段,Spring Boot 将尝试加载 RabbitMQ、Cassandra、MongoDB 和 Hibernate 的所有配置类。

这些类是否实际执行取决于 classpath 中是否存在依赖类。例如,当检测到 MongoDB 相关类时,MongoAutoConfiguration 就会执行,并初始化所有 MongoDB 相关的 Bean。

这种条件初始化通过 @ConditionalOnClass 注解实现。来看 MongoAutoConfiguration 的代码片段:

@Configuration
@ConditionalOnClass(MongoClient.class)
@EnableConfigurationProperties(MongoProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory")
public class MongoAutoConfiguration {
    // 配置代码
}

当 classpath 中存在 MongoClient 时,该配置类就会执行,用默认配置初始化一个 MongoClient Bean 并注入 Spring 容器。

⚠️ 重要提醒如果 Spring Boot 版本低于 2.7,它会从 META-INF/spring.factories 文件加载自动配置类。spring-boot-autoconfigure 项目中也有类似示例:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

此文件格式为单行逗号分隔列表,可使用反斜杠 \ 换行提升可读性。

2.2. 通过 application.properties 自定义属性

Spring Boot 使用预配置的默认值初始化 Bean。要覆盖这些默认值,通常在 application.properties 中声明特定名称的属性。Spring Boot 容器会自动识别这些属性。

来看具体实现。在 MongoAutoConfiguration 代码片段中,@EnableConfigurationProperties 注解指定了 MongoProperties 类作为属性容器:

@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {

    private String host;

    // 其他字段的标准 getter/setter
}

前缀 spring.data.mongodb 加上字段名构成 application.properties 中的属性名。例如设置 MongoDB 主机:

spring.data.mongodb.host = localhost

类似地,类中其他字段也可通过属性文件配置。

3. 创建自定义 Starter

基于第 2 节的概念,创建自定义 starter 需要以下组件:

  1. 为库编写自动配置类,以及用于自定义配置的属性类
  2. 创建 starter pom,引入库和自动配置模块的依赖

为演示,我们创建了一个简单的问候库,它接收不同时段的问候消息作为配置参数并输出。同时创建示例 Spring Boot 应用展示 starter 的用法。

3.1. 自动配置模块

我们将自动配置模块命名为 greeter-spring-boot-autoconfigure。该模块包含两个核心类:

  • GreeterProperties:通过 application.properties 设置自定义属性
  • GreeterAutoConfiguration:为问候库创建 Bean

来看两个类的代码:

@ConfigurationProperties(prefix = "baeldung.greeter")
public class GreeterProperties {

    private String userName;
    private String morningMessage;
    private String afternoonMessage;
    private String eveningMessage;
    private String nightMessage;

    // 标准 getter/setter
}
@Configuration
@ConditionalOnClass(Greeter.class)
@EnableConfigurationProperties(GreeterProperties.class)
public class GreeterAutoConfiguration {

    @Autowired
    private GreeterProperties greeterProperties;

    @Bean
    @ConditionalOnMissingBean
    public GreetingConfig greeterConfig() {

        String userName = greeterProperties.getUserName() == null
          ? System.getProperty("user.name") 
          : greeterProperties.getUserName();
        
        // 其他字段处理...

        GreetingConfig greetingConfig = new GreetingConfig();
        greetingConfig.put(USER_NAME, userName);
        // ...
        return greetingConfig;
    }

    @Bean
    @ConditionalOnMissingBean
    public Greeter greeter(GreetingConfig greetingConfig) {
        return new Greeter(greetingConfig);
    }
}

还需将 GreeterAutoConfiguration 的全限定名添加到 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.baeldung.greeter.autoconfigure.GreeterAutoConfiguration

兼容性提示:如果使用 Spring Boot 2.7 以下版本,需创建 src/main/resources/META-INF/spring.factories 文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baeldung.greeter.autoconfigure.GreeterAutoConfiguration

应用启动时,若 classpath 中存在 Greeter 类,GreeterAutoConfiguration 就会执行。成功执行后,它将通过 GreeterProperties 读取属性,向 Spring 应用上下文注入 GreetingConfigGreeter Bean。

@ConditionalOnMissingBean 注解确保这些 Bean 仅在不存在时才创建。这允许开发者通过在 @Configuration 类中自定义 Bean 来完全覆盖自动配置。

3.2. 创建 pom.xml

现在创建 starter pom,引入自动配置模块和问候库的依赖。

根据命名规范,所有非 Spring Boot 官方维护的 starter 应以库名开头,后缀 -spring-boot-starter。因此我们将 starter 命名为 greeter-spring-boot-starter

<project ...>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.baeldung</groupId>
    <artifactId>greeter-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baeldung</groupId>
            <artifactId>greeter-spring-boot-autoconfigure</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baeldung</groupId>
            <artifactId>greeter</artifactId>
            <version>${greeter.version}</version>
        </dependency>

    </dependencies>

    <properties>
        <greeter.version>0.0.1-SNAPSHOT</greeter.version>
    </properties>
</project>

3.3. 使用 Starter

创建示例应用 greeter-spring-boot-sample-app,在 pom.xml 中添加 starter 依赖:

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>greeter-spring-boot-starter</artifactId>
    <version>${greeter-starter.version}</version>
</dependency>

Spring Boot 会自动完成所有配置,我们直接获得可注入使用的 Greeter Bean。

通过 application.properties 覆盖 GreeterProperties 的默认值(使用 baeldung.greeter 前缀):

baeldung.greeter.userName=Baeldung
baeldung.greeter.afternoonMessage=Woha\ Afternoon

最后在应用中使用 Greeter Bean:

@SpringBootApplication
public class GreeterSampleApplication implements CommandLineRunner {

    @Autowired
    private Greeter greeter;

    public static void main(String[] args) {
        SpringApplication.run(GreeterSampleApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        String message = greeter.greet();
        System.out.println(message);
    }
}

4. 总结

本教程重点介绍了如何创建自定义 Spring Boot starter,以及这些 starter 与自动配置机制如何协作,在幕后消除大量手动配置。掌握这些原理后,你也能轻松封装内部库,提升团队开发效率。


原始标题:Creating a Custom Starter with Spring Boot | Baeldung

« 上一篇: MyBatis 快速指南