baeldung 2017-04-19
1. 概述
Spring Boot 核心开发团队为大多数流行的开源项目提供了 starter(启动器),但我们并不局限于这些官方 starter。
我们也可以编写自己的自定义 starter。如果我们在组织内部有一个通用库,并且计划在 Spring Boot 项目中使用它,那么为其编写一个 starter 将是一个很好的实践。
这些 starter 能让开发者避免冗长的配置,快速启动开发工作。然而,由于很多操作都在后台自动完成,有时很难理解:为什么仅仅添加一个注解或在 pom.xml 中引入一个依赖,就能启用如此多的功能。
在本文中,我们将揭开 Spring Boot 的“魔法”面纱,深入理解其背后的工作原理。然后,我们将利用这些概念,为自己的自定义库创建一个 starter。
2. 揭秘 Spring Boot 的自动配置机制
2.1 自动配置类
从 Spring Boot 2.7 版本开始,应用启动时会从文件 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 上是否存在相关的依赖类。例如,如果 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 方法
}
属性名由前缀(prefix)加上字段名构成。因此,要设置 MongoDB 的主机地址,只需在属性文件中写入:
spring.data.mongodb.host = localhost
同样,该类中其他字段的值也可以通过属性文件进行设置。
3. 创建自定义 Starter
基于第 2 节介绍的概念,要创建一个自定义 starter,我们需要编写以下组件:
- 一个用于我们库的自动配置类,以及一个用于自定义配置的属性类。
- 一个 starter 的
pom.xml文件,用于引入库本身和自动配置模块的依赖。
为了演示,我们创建了一个简单的问候语库(greeter library),它可以根据一天中不同时段的配置参数输出相应的问候消息。我们还将创建一个示例 Spring Boot 应用程序,以展示自动配置模块和 starter 的使用方式。
3.1 自动配置模块
我们将自动配置模块命名为 greeter-spring-boot-autoconfigure。该模块包含两个主要类:
GreeterProperties:用于通过application.properties文件设置自定义属性。GreeterAutoConfiguration:用于为 greeter 库创建 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 应用上下文中注册 GreetingConfig 和 Greeter Bean。
@ConditionalOnMissingBean注解确保只有在容器中尚未存在这些 Bean 时才会创建它们。这使得开发者可以通过在自己的@Configuration类中定义同名 Bean 来完全覆盖自动配置的行为。
3.2 创建 pom.xml
接下来我们创建 starter 的 pom.xml 文件,用于引入自动配置模块和 greeter 库的依赖。
按照命名规范,所有非 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 的示例 Spring Boot 应用,来使用这个 starter。只需在 pom.xml 中添加如下依赖:
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>greeter-spring-boot-starter</artifactId>
<version>${greeter-starter.version}</version>
</dependency>
Spring Boot 会自动完成所有配置,Greeter Bean 将可以直接注入并使用。
我们还可以通过在 application.properties 文件中使用 baeldung.greeter 前缀来自定义 GreeterProperties 的默认值:
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 与自动配置机制如何协同工作,从而大幅减少手动配置的工作量。