题目
SpringBoot 中的 starter 是什么?如何自定义一个 starter
答案
@[toc]
---
## Spring Boot 中的 starter 是什么?
starter 是一种**对各种依赖的 synthesize(合成)**,它有效的降低了项目开发过程的复杂程度,对于简化开发操作有着非常好的效果。
starter 的工作原理如下:
1. Spring Boot 在启动时扫描项目所依赖的 JAR 包,寻找包含 **`/META-INF/spring.factories`** 文件的 JAR 包
2. 读取 `spring.factories` 文件获取配置的自动配置类 `AutoConfiguration`
3. 然后将自动配置类中提供的 @Bean 加载到 Spring 容器中
4. 这样使用者就可以直接注入这些已注册的 Bean 了
举个例子来说明:
1)**传统的做法**:在没有 starter 之前,假如我想要在 Spring 中使用 jpa,那我可能需要做以下操作:
- 在 Maven 中引入使用的数据库的依赖(即 JDBC 的 jar)
- 引入 jpa 的依赖
- 在 xxx.xml 中配置一些属性信息
- 反复的调试直到可以正常运行
这里操作在我们每次新建一个需要用到 jpa 的项目的时候都需要重复的做一次。这样做有几个问题:
- 如果过程比较繁琐,这样一步步操作会增加出错的可能性
- 不停地 copy&paste 不符合 Don’t repeat yourself 精神
- 在第一次配置的时候(尤其如果开发者比较小白),需要花费掉大量的时间
2)**使用 Spring Boot starter 提升效率**:starter 的主要目的就是为了解决上面的这些问题。
starter 的理念:**starter 会把所有用到的依赖都给包含进来,避免了开发者自己去引入依赖所带来的麻烦**。需要注意的是不同的 starter 是为了解决不同的依赖,所以它们内部的实现可能会有很大的差异,例如 jpa 的 starter 和 Redis 的 starter 可能实现就不一样,这是因为 starter 的本质在于合成(synthesize),这是一层在逻辑层面的抽象,也许这种理念有点类似于Docker,因为它们都是在做一个“包装”的操作
starter 的实现:虽然不同的 starter 实现起来各有差异,但是他们基本上都会使用到两个相同的内容:
- 属性配置类(ConfigurationProperty):使用 `@ConfigurationProperties` 注解标识的类 XxxProperties,可以设置属性的默认配置(当我们在项目中引入这个 starter 的时候,可以在 `application.propertites` 文件中覆写默认值。即使需要覆写多个 starter 的属性默认值,所有的配置也只需要在一个 application.propertites 文件中进行,使用起来非常方便)
- 自动配置类(AutoConfiguration):使用 `@Configuration` 注解标识的类 XxxConfigurationProperties,提供内容给用户使用,用于完成 Bean 创建等工作。
starter 的整体逻辑:

上面的 starter 依赖的 jar 和我们自己手动配置的时候依赖的 jar 并没有什么不同,所以我们可以认为 starter 其实是把这一些繁琐的配置操作交给了自己,而把简单交给了用户。除了帮助用户去除了繁琐的构建操作,在“约定大于配置”的理念下,ConfigurationProperties 还帮助用户减少了无谓的配置操作。并且因为 `application.properties` 文件的存在,即使需要自定义配置,所有的配置也只需要在一个文件中进行,使用起来非常方便。
## 自定义 starter 示例代码
通过 Spring Initializr 创建两个 Spring Boot 模块,一个 hello-spring-boot-starter 和一个 hello-example。前者是自定义 starter,后者用于使用这个自定义的 starter:

在 `hello-spring-boot-starter` 中导入依赖:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
```
### 创建属性配置类
如果我们需要从 application.yaml 或 application.properties 中拿到一些使用者配置的数据,那么我们就需要定义一个属性配置类。这个类主要作用是将 application.yaml 或 application.properties 中的配置信息映射成实体类,比如我们这里指定 `prefix = "hello.test"` 这样,我们就能将配置文件中以 `hello.test` 为前缀的配置项拿到了。
```java
@ConfigurationProperties(prefix ="hello.test")
public class HelloProperties {
private boolean enable;
private String name;
private String url;
}
```
### 创建 Service
这个类一般就是用来定义一些供外部调用的方法
```java
public class HelloService {
@Autowired
private HelloProperties helloProperties;
public String sayHello() {
return "hello "+ helloProperties.getName() + " welcome to my homepage:" + helloProperties.getUrl();
}
}
```
### 创建自动配置类
如下:
```java
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnProperty(prefix = "hello.test", name = "enable", matchIfMissing = true)
public class HelloAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService() {
return new HelloService();
}
}
```
HelloAutoConfiguration 使用了以下 5 个注解:
- `@Configuration`:表示该类是一个配置类;
- `@EnableConfigurationProperties(xxxProperties)`:该注解的作用是为 xxxProperties 开启属性配置功能,并将这个类以组件的形式注入到容器中;
- `@ConditionalOnProperty(prefix = "xxx", name= "x", matchIfMissing = true)` : 当指定的配置项等于你想要的时候,配置类生效;
- `@ConditionalOnMissingBean(xxx.class)`:该注解表示当容器中没有 xxx 类时,该方法才生效;
- `@Bean`:该注解用于将方法的返回值以 Bean 对象的形式添加到容器中。
### 创建 spring.factories 文件
Spring Factories 机制是 Spring Boot 中的一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。因此我们自定义 starter 时,需要在项目类路径下创建一个 spring.factories 文件。
在 hello-spring-boot-starter 的路径下(resources )中创建一个 META-INF 文件夹,并在 META-INF 文件夹中创建一个 spring.factories 文件。

将 Spring Boot 的 EnableAutoConfiguration 接口与自定义 starter 的自动配置类 HelloAutoConfiguration 组成一组键值对添加到 spring.factories 文件中,以方便 Spring Boot 在启动时,获取到自定义 starter 的自动配置:
```
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.hello.starter.config.HelloAutoConfiguration
```
### 构建 starter
执行 `man clean install`:

### 验证
在 hello-example 模块中添加我们自定义的 hello-spring-boot-starter 依赖:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
```
在 application.yaml 或 application.properties 中配置如下:
```properties
hello.test.enable=true
hello.test.name=小牛肉
hello.test.url=https://itmtx.cn
```
使用自定义 starter 提供的 `@HelloService`:
```java
@RestController
public class HelloController {
// 注入自定义 starter 提供的 `@HelloService`:
@Autowired
private HelloService helloService;
@GetMapping("/test")
public String test(){
return helloService.sayHello();
}
}
```
