写在前面
SpringBoot 提供了很多开箱即用的starter依赖,将其称为场景启动器,使得我们在开发业务代码时能够非常方便的、快速的完成框架的引入,为我们省去了之前很多的配置操作,无需过多关注框架的配置,摆脱了各种依赖库的处理和配置各种信息的困扰,甚至有些功能的开启只需要在启动类或配置类上增加一个注解即可完成。SpringBoot会自动发现符合上下文场景的Bean对象,并注册到IOC容器中。
一句话:SpringBoot为了简化开发,将常用场景抽取成了一个个的Starter,供外部使用。
那么我们想要自己实现自己的 starter需要怎么做呢?下面就开始介绍如何实现自定义的starter。
什么是STARTER?
Starter是一个可拔插的插件,提供一系列依赖描述符,应用程序只需要在pom文件中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。
应用场景
在实际的开发过程中,经常会有一些相对独立的、可重用的模块。我们如果将这些模块封装成一个个starter,复用时只需要将其在pom中引用依赖,让SpringBoot为我们完成自动装配,是不是更合理更方便呢?
官方文档
命名规范
①. Spring官方的Starter一般采取spring-boot-starter-XXX的格式,如:spring-boot-starter-web、spring-boot-starter-jdbc; ②. 而非官方的、自定义的Starter,官方建议 artifactId 命名应遵循XXX-spring-boot-starter的格式,如:droolsx-spring-boot-starter、mybatis-spring-boot-starter。实现步骤
第1步创建一个 SpringBoot 项目,并添加下面两个依赖到 pom.xml 文件当中:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
<optional>true</optional>
<version>${spring-boot.version}</version>
</dependency>
<!-- 主要是为了生成spring-configration-metadata.json文件,支持idea跳转 -->
<!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
①. spring-boot-starter,所有场景启动器最底层的依赖;
②. spring-boot-configuration-processor,用来在编译时生成 spring-configuration-metadata.json,当引入该starter后,在 application.yml 中配置该starter的相关属性时,可以用 ctlr + 鼠标左键点击属性名,会跳转到你配置此属性的类中,简而言之:它可以生成配置类的提示文件。
第2步定义配置类映射所有的配置信息,用于接受application.yml中注入的属性,并绑定映射到这个配置类上:
package com.hadoopx.drools.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author ROKCY
* 第2步. 定义配置类映射配置信息
*/
@Data
@ConfigurationProperties(prefix = "spring.drools")
public class DroolsProperties {
// 规则文件和决策表的路径(多个目录使用逗号分割)
private String path;
// 更新缓存的轮询周期 - 单位:秒(默认30秒)
private Long update;
// 模式: stream 或 cloud(默认stream模式)
private String mode;
// 是否开启监听器:on = 开, off = 关闭(默认开启)
private String listener;
// 自动更新:on = 开, off = 关闭(默认开启)
private String autoUpdate;
// 是否开启DRKL的语法检查: on = 开, off = 关闭(默认)
private String verify;
}
第3步定义自动配置类:
package com.hadoopx.drools.config;
import com.hadoopx.drools.KieSchedule;
import com.hadoopx.drools.KieTemplate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Objects;
import static com.hadoopx.drools.common.Constants.LISTENER_CLOSE;
/**
1. @author ROKCY
2. 第3步. 定义自动配置类
*/
@Configuration
@EnableConfigurationProperties(DroolsProperties.class)
public class DroolsConfig {
@Bean
@ConditionalOnMissingBean(name = "kieTemplate")
public KieTemplate kieTemplate(DroolsProperties droolsProperties) {
KieTemplate kieTemplate = new KieTemplate();
kieTemplate.setPath(droolsProperties.getPath());
kieTemplate.setMode(droolsProperties.getMode());
String autoUpdate = droolsProperties.getAutoUpdate();
if (Objects.equals(LISTENER_CLOSE, autoUpdate)) {
// 关闭自动更新
kieTemplate.setUpdate(999999L);
} else {
// 启用自动更新
kieTemplate.setUpdate(droolsProperties.getUpdate());
}
kieTemplate.setListener(droolsProperties.getListener());
kieTemplate.setVerify(droolsProperties.getVerify());
return kieTemplate;
}
@Bean
@ConditionalOnMissingBean(name = "kieSchedule")
public KieSchedule kieSchedule(KieTemplate kieTemplate) {
KieSchedule kieSchedule = new KieSchedule(kieTemplate);
kieSchedule.execute();
return kieSchedule;
}
}
@ConfigurationProperties(prefix = "spring.drools"),它的作用就是从YMl配置文件中绑定属性到对应的Bean(DroolsProperties)上; @EnableConfigurationProperties,负责导入这个已经绑定了属性的Bean到Spring容器中。
注意代码中用到的注解,下面将SpringBoot 中的所有 @Conditional 注解及作用进行描述:
@ConditionalOnBean: 当容器中有指定的Bean的条件下
@ConditionalOnClass: 当类路径下有指定的类的条件下
@ConditionalOnExpression: 基于SpEL表达式作为判断条件
@ConditionalOnJava: 基于JVM版本作为判断条件
@ConditionalOnJndi: 在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean: 当容器中没有指定Bean的情况下
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下
@ConditionalOnNotWebApplication: 当前项目不是Web项目的条件下
@ConditionalOnProperty: 指定的属性是否有指定的值
@ConditionalOnResource: 类路径下是否有指定的资源
@ConditionalOnSingleCandidate: 当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean
@ConditionalOnWebApplication: 当前项目是Web项目的条件下
所以,通过第2、3步,可以将全局配置的属性如:spring.drools.path等,通过@ConfigurationProperties注解,绑定到对应的DroolsProperties配置实体类上,然后再通过@EnableConfigurationProperties注解将这个配置实体类导入到Spring容器中。
第4步让配置类加载生效,使用spring.factories将自动配置类注入到Spring容器中,在resources/META-INF/下创建spring.factories文件,并添加如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hadoopx.drools.config.DroolsConfig
第5步根据实际业务定义starter要实现的功能业务逻辑。
第6步去掉pom.xml文件中的spring-boot-maven-plugin插件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
总结
要完成自定义的starter需要提供:一个配置实体类XXXProperties.java、一个或者多于一个的自动配置类XXXAutoConfiguration.java、一组业务逻辑类,另外注意pom文件的配置。
看源码会发现有些starter是提供了两个jar,一个jar专门定义autoconfiguration,另一个jar仅仅引用上一个jar,这两种方式都可以,如下: