题目
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 的整体逻辑: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230604115056792.png) 上面的 starter 依赖的 jar 和我们自己手动配置的时候依赖的 jar 并没有什么不同,所以我们可以认为 starter 其实是把这一些繁琐的配置操作交给了自己,而把简单交给了用户。除了帮助用户去除了繁琐的构建操作,在“约定大于配置”的理念下,ConfigurationProperties 还帮助用户减少了无谓的配置操作。并且因为 `application.properties` 文件的存在,即使需要自定义配置,所有的配置也只需要在一个文件中进行,使用起来非常方便。 ## 自定义 starter 示例代码 通过 Spring Initializr 创建两个 Spring Boot 模块,一个 hello-spring-boot-starter 和一个 hello-example。前者是自定义 starter,后者用于使用这个自定义的 starter: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/af51544331749c9a7a51960bbecf0641.png) 在 `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 文件。 ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/c60cbb24efe37813adf5f90dd5b27150.png) 将 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`: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/5dced8ddc5130dc8dbaca1460434478f.png) ### 验证 在 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(); } } ```