前言
Starter 是 Spring Boot 非常重要的一个硬核功能。
通过 Starter 我们可以快速的引入一个功能或模块,而无须关心模块依赖的其它组件。关于配置,Spring Boot 采用"约定大于配置"的设计理念,Starter 一般都会提供默认配置,只有当我们有特殊需求的时候,才需要在application.yaml
里进行单独配置以覆盖掉默认配置。
例如,我们开发一个 Web 应用,需要用到 Spring MVC、Tomcat 等组件,我们只需要依赖spring-boot-starter-web
即可,Starter 已经包含了开发一个 Web 应用所需的所有组件和依赖包,同时提供了 Web 服务的默认配置,比如端口是 8080。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
当我们有特殊需求时,比如要修改服务端口,才需要额外配置application.yaml
yaml
server:
port: 8090
理解Starter
Starter 是什么???
Starter 是一个 Spring Boot 项目,它包含了一个功能或模块依赖的所有组件。一般来说,Starter 项目本身不包含任何代码,只管理依赖。
Starter 要解决什么问题???
没有 Starter 的时候,我们要引入一个功能或模块,首先要了解这个功能或模块依赖了哪些组件,还要管理依赖的版本。依赖完以后,还要配置所需的 bean,以及其它一些配置文件的编写。整个过程极其繁琐,门槛高。
Starter 解决了依赖和配置的问题,它把所需的依赖打成一个包,得益于 Spring Boot 自动装配,同时解决了 bean 的配置问题。引入 Starter 就可以实现零配置或少量配置来使用相应的功能。
Starter 是怎么实现的???
Starter 依赖 Spring Boot 自动装配的特性,它首先包含模块依赖的所有第三方库,再通过若干个XXXAutoConfiguration
的自动配置类来声明模块所需的 Bean,Spring Boot 程序启动时会自动加载这些配置类,把对应的 Bean 注册到容器,以实现某个功能。
Starter实战
通过编写一个我们自己的 Starter 来进一步理解其原理。
需求:编写一个可以操作 Redis 的客户端 Starter。
项目结构
basic
redis-client
- client
- redis-spring-boot
- redis-spring-boot-starter
- redis-spring-boot-autoconfigure
模块介绍
模块 | 说明 |
---|---|
redis-client | 父项目,管理依赖 |
client | Redis 客户端核心模块 |
redis-spring-boot | Redis 客户端和 Spring Boot 整合模块 |
redis-spring-boot-starter | Redis 客户端启动器 |
redis-spring-boot-autoconfigure | Redis 客户端自动装配模块(核心) |
redis-client
基础模块,没有代码,主要管理依赖。因为要操作 Redis,这里引入 jedis。
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.redis</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>
</dependencyManagement>
client
client 是 Redis 客户端核心模块,提供了操作 Redis 的客户端实现。它应该被设计成可以脱离 Spring 独立运行的,所以这个项目不应该依赖 Spring 任何东西。
首先抽象一个客户端接口,出于简单考虑,这里只提供 get、set 方法
java
public interface RedisClient {
void set(String key, String value);
String get(String key);
}
同时提供一个默认实现,这里只是对 jedis 做一层包装
java
public class DefaultRedisClient implements RedisClient {
private final Jedis jedis;
public DefaultRedisClient(Jedis jedis) {
this.jedis = jedis;
}
@Override
public void set(String key, String value) {
jedis.set(key, value);
}
@Override
public String get(String key) {
return jedis.get(key);
}
}
再提供一个工厂类,用来创建客户端实例
java
public class RedisClientFactory {
public static RedisClient create(String host, int port, String user, String password, int database) {
JedisClientConfig config = DefaultJedisClientConfig.builder().user(user).password(password).database(database).build();
Jedis jedis = new Jedis(host, port, config);
return new DefaultRedisClient(jedis);
}
}
至此,client 模块就完成了,而且是可以直接跑的,无需 Spring。
redis-spring-boot
为了开发者更加方便的在 Spring Boot 环境下使用我们的 Redis Client,我们要和 Spring Boot 做一个整合,为此再单独新建一个 redis-spring-boot 模块。它是一个父模块,主要管理依赖
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.18</version>
</dependency>
<dependency>
<groupId>io.redis</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
redis-spring-boot-starter
我们的 Starter 模块,也就是开发者要引入的唯一一个模块。模块的命名上大家要注意,spring-boot-starter-XXX
这种前缀是 Spring Boot 官方保留的名字,我们不要用,建议用XXX-spring-boot-starter
风格命名。
Starter 模块一般没有代码,只管理依赖的组件,这里主要是引入我们的自动装配模块
xml
<dependencies>
<dependency>
<groupId>io.redis</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
redis-spring-boot-autoconfigure
Redis Client 与 Spring Boot 整合的自动装配模块,也是我们这个示例中的核心模块。先看依赖:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.redis</groupId>
<artifactId>client</artifactId>
</dependency>
</dependencies>
RedisClientAutoConfiguration:自动配置类,Spring Boot 启动时会自动加载。它的加载是有条件的,首先得是 ClassPath 下包含 RedisClient.class,没有这个类我们就没法自动装配;其次是redis.enabled
不配置或者必须为 true 才会启用,否则是禁用的。
java
@Configuration
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(
prefix = "redis",
name = {"enabled"},
matchIfMissing = true
)
@EnableConfigurationProperties(RedisClientProperties.class)
public class RedisClientAutoConfiguration {
@Bean
public RedisClient redisClient(RedisClientProperties properties) {
return RedisClientFactory.create(properties.getHost(), properties.getPort(),
properties.getUser(), properties.getPassword(), properties.getDatabase());
}
}
自动配置类还开启了自动配置属性,类是 RedisClientProperties。按照"约定大于配置"的理念,我们提供了默认配置,开发者也可以选择覆盖默认配置
java
@ConfigurationProperties(
prefix = "redis",
ignoreUnknownFields = true
)
public class RedisClientProperties {
private String host = "localhost";
private int port = 6379;
private String user;
private String password;
private int database = 0;
// 忽略 getter、setter
}
最后,要想办法让 Spring Boot 加载我们的配置类。按照 Spring Boot 自动装配的规则,我们编写META-INF/spring.factories
文件
basic
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.redis.client.spring.boot.autoconfigure.RedisClientAutoConfiguration
至此,我们的 Starter 就开发完毕了,把它安装到 Maven 仓库就可以给其它小伙伴用了。
测试一下
新建一个测试项目,引入依赖
xml
<dependencies>
<dependency>
<groupId>io.redis</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
编写一个启动类,注入 RedisClient,尝试读取一下数据,是成功的
java
@SpringBootApplication
public class Application {
@Autowired
RedisClient redisClient;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
RedisClient redisClient = context.getBean(Application.class).redisClient;
redisClient.set("haha", "xixi");
System.err.println(redisClient.get("haha"));
}
}
尾巴
Starter 是 Spring Boot 框架中的一个重要概念,它提供了一种便捷的方式来配置和使用特定功能的 Spring Boot 应用程序。通常一个 Starter 是一个包含了一组相关依赖和配置的项目,目的是为了简化应用程序的开发和部署过程。
Starter 的设计目标是通过隐藏繁琐的配置细节和依赖声明,使开发者能够更专注于业务逻辑的实现。当你想要使用某个功能或集成某个组件时,只需引入相应的 Starter 依赖,Spring Boot 就会自动配置相关的组件,并提供默认的配置选项,大大减少了手动配置的工作量。
Starter 依赖于 Spring Boot 自动装配的特性,理解了自动装配你就理解了 Starter。