Spring Boot Starter 开发指南

Spring Boot Starter是什么?

依赖管理是任何复杂项目的关键部分。以手动的方式来实现依赖管理不太现实,你得花更多时间,同时你在项目的其他重要方面能付出的时间就会变得越少。

Spring Boot starter 就是为了解决这个问题而诞生的。Starter POM 是一组方便的依赖描述符,您可以将其包含在应用程序中。您可以获得所需的所有 Spring 和相关技术的一站式服务,无需通过示例代码搜索和复制粘贴依赖。

揭开Spring Boot自动装配的神秘面纱

Auto Configuration 类

当Spring Boot启动时,它会在类路径中查找名为spring.factories的文件。该文件位于META-INF目录中。让我们看一下spring-boot-autoconfigure项目中这个文件的片段:

1
2
3
4
5
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

此文件定义了一些Spring Boot将尝试运行的自动装配类。例如以上的代码片段,Spring Boot将尝试运行RabbitMQ,Cassandra,MongoDB和Hibernate的所有配置类。这些类是否实际运行将取决于类路径上是否存在依赖类。例如,如果在类路径中找到MongoDB的类,则将运行MongoAutoConfiguration,并初始化所有与mongo相关的bean。此条件初始化由@ConditionalOnClass注释启用。让我们看一下MongoAutoConfiguration类的代码片段,看看它的用法:

1
2
3
4
5
6
7
@Configuration
@ConditionalOnClass(MongoClient.class)
@EnableConfigurationProperties(MongoProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory")
public class MongoAutoConfiguration {
// configuration code
}

如果存在MongoClient类,将运行该自动装配类初始化MongoClient相关bean。

application.properties自定义配置

Spring Boot使用一些预先配置的默认值初始化bean。要覆盖这些默认值,我们通常会在application.properties文件中使用某个特定名称声明它们。Spring Boot容器会自动获取这些属性。
MongoAutoConfiguration的代码片段中,@EnableConfigurationProperties(MongoProperties.class)表示,使用MongoProperties类来声明自定义属性:

1
2
3
4
5
6
7
@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {

private String host;

// other fields with standard getters and setters
}

@ConfigurationProperties(prefix = "spring.data.mongodb")定义了配置前缀,我们可以在application.properties这样来使用它:

1
spring.data.mongodb.host = localhost

这样,初始化的时候,localhost将被注入到host属性中

自定义Spring Boot Starter

Spring Boot自动装配虽然神奇,但是编写起来却异常简单,我们只需要按部就班的执行以下两个流程:

  1. 编写属性容器*Properties,并编写对应的*AutoConfiguration自动装配类
  2. 一个pom文件,用于定义引入库和自动装配类的依赖项

概念解析

用于*Properties的注解

  • @ConfigurationProperties(prefix = "spring.data.mongodb") :用于指定配置前缀

用于*AutoConfiguration的注解

  • @Configuration:标记为配置类,由Spring容器初始化并接管
  • @EnableConfigurationProperties:注入配置属性容器
  • @ConditionalOnBean:条件装配

重点说下条件装配,以@ConditionalOnBean为例,当Spring容器中存在指定Bean的时候装配

1
2
3
4
5
6
7
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean{
//properties
}

@Conditional(OnBeanCondition.class)指定了实现条件装配的逻辑代码

OnBeanCondition声明如下:

1
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition{}

所以,我们自己也可以继承SpringBootCondition并实现ConfigurationCondition来自定义条件装配注解。

比较常用的几个条件装配注解:

  • @ConditionalOnBean:当Spring容器中存在指定Bean时装配
  • @ConditionalOnClass:当存在指定Class时装配
  • @ConditionalOnMissingBean:当Spring容器中不存在指定Bean时装配
  • @ConditionalOnMissingClass:当不存在指定Class时装配

小试牛刀

我们将自动配置模块称为greeter-spring-boot-autoconfigure。该模块将有两个主要类,即GreeterProperties,它将通过application.properties文件和GreeterAutoConfiguartion设置自定义属性,并为greeter库创建bean。

准备,创建假想的一个第三方工程:Greet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Greeter {
private GreetingConfig greetingConfig;

public Greeter(GreetingConfig greetingConfig) {
this.greetingConfig = greetingConfig;
}

public String greet(LocalDateTime localDateTime) {

String name = greetingConfig.getProperty(USER_NAME);
int hourOfDay = localDateTime.getHour();

if (hourOfDay >= 5 && hourOfDay < 12) {
return String.format("Hello %s, %s", name, greetingConfig.get(MORNING_MESSAGE));
} else if (hourOfDay >= 12 && hourOfDay < 17) {
return String.format("Hello %s, %s", name, greetingConfig.get(AFTERNOON_MESSAGE));
} else if (hourOfDay >= 17 && hourOfDay < 20) {
return String.format("Hello %s, %s", name, greetingConfig.get(EVENING_MESSAGE));
} else {
return String.format("Hello %s, %s", name, greetingConfig.get(NIGHT_MESSAGE));
}
}

public String greet() {
return greet(LocalDateTime.now());
}
}

public class GreeterConfigParams {
public static final String USER_NAME = "user.name";
public static final String MORNING_MESSAGE = "morning.message";
public static final String AFTERNOON_MESSAGE = "afternoon.message";
public static final String EVENING_MESSAGE = "evening.message";
public static final String NIGHT_MESSAGE = "night.message";
}

public class GreetingConfig extends Properties {
private static final long serialVersionUID = 5662570853707247891L;
}

编写Properties和AutoConfiguration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@ConfigurationProperties(prefix = "gcdd1993.greeter")
public class GreeterProperties {
private String userName;
private String morningMessage;
private String afternoonMessage;
private String eveningMessage;
private String nightMessage;
// getter and 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);
if (greeterProperties.getMorningMessage() != null) {
greetingConfig.put(MORNING_MESSAGE, greeterProperties.getMorningMessage());
}
if (greeterProperties.getAfternoonMessage() != null) {
greetingConfig.put(AFTERNOON_MESSAGE, greeterProperties.getAfternoonMessage());
}
if (greeterProperties.getEveningMessage() != null) {
greetingConfig.put(EVENING_MESSAGE, greeterProperties.getEveningMessage());
}
if (greeterProperties.getNightMessage() != null) {
greetingConfig.put(NIGHT_MESSAGE, greeterProperties.getNightMessage());
}
return greetingConfig;
}

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

然后在src/main/resources/META-INF目录下创建spring.factories文件

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gcdd.autoconfigure.GreeterAutoConfiguration

测试一下:

  1. 创建配置文件application.properties
1
2
gcdd1993.greeter.userName=gcdd1993
gcdd1993.greeter.eveningMessage=good evening
  1. 使用Greeter bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
}
}

执行main方法,将会输出一行:

1
Hello gcdd1993, good evening

为配置类添加提示

我们知道,在Idea中,编写配置文件的时候,有智能提示

其实这不是Idea搞的鬼,是由META-INF/spring-configuration-metadata.json文件配置好的,Idea只是负责解析这个文件,提供我们智能化的提示信息。

想要达到这个目的很简单,添加依赖org.springframework.boot:spring-boot-configuration-processor就行了。

Maven

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>

Gradle

1
compile group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: '2.1.6.RELEASE'

总结

以上就是Spring Boot Starter的全部内容了,如果要发布到maven仓库,供别人使用,可以使用mvn install打包发布至maven仓库。

👉 本文代码地址