创建自定义的Spring Boot Starter项目和添加项目元数据的约束

参照并简化了官网和官方提供的starter(例如spring-cloud-config-server)的部分代码。

创建自定义的Spring Boot Starter项目和添加项目元数据的约束

创建自己的starter项目

假定一个需求是在代码中引入一个自定义的starter,作用是可以将传入的参数按照application.yml中配置的加密方式加密并返回(这种需求在实际开发中几乎不会存在)。

新建maven项目,命名为encode-spring-boot-starter(以下简称为starter),pom.xml引入自动装配的依赖包spring-boot-autoconfigure:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.github.liuzhuoming23</groupId>
<artifactId>encode-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>encode-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
</dependencies>

</project>

其中lombok为简写get/set,commons-codec为提供字符串编码依赖的工具包。

创建编码静态工厂

新建util.EncodeUtil接口:

package com.github.liuzhuoming.starter.encode.util;

/**
* 编码
*
* @author liuzhuoming
*/
public interface EncodeUtil {

String encode(String str);
}

新建三个实现类,util.Base64Util/util.Md5Util/util.NoneUtil:

package com.github.liuzhuoming.starter.encode.util;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
* base64
*
* @author liuzhuoming
*/
public class Base64Util implements EncodeUtil {

@Override
public String encode(String text) {
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
return Base64.getEncoder().encodeToString(bytes);
}
}
package com.github.liuzhuoming.starter.encode.util;

import org.apache.commons.codec.digest.DigestUtils;

/**
* md5工具类
*
* @author liuzhuoming
*/
public class Md5Util implements EncodeUtil {

@Override
public String encode(String text) {
return DigestUtils.md5Hex(text);
}
}
package com.github.liuzhuoming.starter.encode.util;

/**
* none
*
* @author liuzhuoming
*/
public class NoneUtil implements EncodeUtil {

@Override
public String encode(String str) {
return str;
}
}

创建em.EncodeType枚举类:

package com.github.liuzhuoming.starter.encode.em;

/**
* encode type
*
* @author liuzhuoming
*/
public enum EncodeType {
NONE,
MD5,
BASE64
}

创建factory.EncodeFactory:

package com.github.liuzhuoming.starter.encode.factory;

import com.github.liuzhuoming.starter.encode.em.EncodeType;
import com.github.liuzhuoming.starter.encode.util.Base64Util;
import com.github.liuzhuoming.starter.encode.util.EncodeUtil;
import com.github.liuzhuoming.starter.encode.util.Md5Util;
import com.github.liuzhuoming.starter.encode.util.NoneUtil;

/**
* 编码工厂
*
* @author liuzhuoming
*/
public class EncodeFactory {

private EncodeFactory() {
}

public static EncodeUtil getInstance(EncodeType type) {
switch (type) {
case MD5:
return new Md5Util();
case BASE64:
return new Base64Util();
case NONE:
return new NoneUtil();
default:
throw new RuntimeException(String.format("编码{%s}不存在", type.name()));
}
}
}

创建自动装配类

首先新建.properties配置映射类,properties.EncodeProperties:

package com.github.liuzhuoming.starter.encode.properties;

import com.github.liuzhuoming.starter.encode.em.EncodeType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* properties
*
* @author liuzhuoming
*/
@Data
@ConfigurationProperties(prefix = "encode")
public class EncodeProperties {

private final static EncodeType DEFAULT_ENCODE_TYPE = EncodeType.NONE;
//如果application.yml中没有这个配置,则使用默认配置
private EncodeType type = DEFAULT_ENCODE_TYPE;
}

新建服务类template.EncodeTemplate,作用是完成工厂类的装配和向外提供服务:

package com.github.liuzhuoming.starter.encode.template;

import com.github.liuzhuoming.starter.encode.factory.EncodeFactory;
import com.github.liuzhuoming.starter.encode.properties.EncodeProperties;
import lombok.Data;

/**
* template
*
* @author liuzhuoming
*/
@Data
public class EncodeTemplate {

private EncodeProperties encodeProperties;

public String current(String str) {
return EncodeFactory.getInstance(encodeProperties.getType()).encode(str);
}
}

接下来创建自动装配类conf.EncodeAutoConfiguration:

package com.github.liuzhuoming.starter.encode.conf;

import com.github.liuzhuoming.starter.encode.properties.EncodeProperties;
import com.github.liuzhuoming.starter.encode.template.EncodeTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 自动装配
*
* @author liuzhuoming
*/
@Configuration
@EnableConfigurationProperties(EncodeProperties.class)
//如果class存在则进行自动装配
@ConditionalOnClass(EncodeTemplate.class)
//如果配置参数符合条件则进行自动装配
@ConditionalOnProperty(prefix = "vegetable.encode", value = "enabled", havingValue = "true", matchIfMissing = true)
public class VegetableAutoConfiguration {

@Autowired
private EncodeProperties encodeProperties;

@Bean
//当项目中bean不存在时进行自动装配
@ConditionalOnMissingBean(EncodeTemplate.class)
public EncodeTemplate encodeTemplate() {
EncodeTemplate encodeTemplate = new EncodeTemplate();
encodeTemplate.setEncodeProperties(encodeProperties);
return encodeTemplate;
}
}

创建src/main/resources/META-INF/spring.factories文件,使自定义的自动装配类在系统启动的时候可以被spring加载到:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.liuzhuoming.starter.encode.conf.VegetableAutoConfiguration

其中\代表换行,如果有多个自动装配类需要被读取,则用,分隔。
这样就完成了一个最基本的starter项目。

测试

首先install项目到本地maven仓库,然后新建一个spring web项目名称为encode-spring-boot-starter-samples(以下简称为samples),引入我们自定义的encode-spring-boot-starter依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.github.liuzhuoming23</groupId>
<artifactId>encode-spring-boot-starter-samples</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>encode-spring-boot-starter-samples</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.github.liuzhuoming23</groupId>
<artifactId>encode-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

在application.yml添加自定义配置:

encode:
enabled: true
type: md5

然后创建controller.EncodeController类:

package com.github.liuzhuoming23.starter.encode.sample.controller;

import com.github.liuzhuoming.starter.encode.template.EncodeTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* encode
*
* @author liuzhuoming
*/
@RestController
@RequestMapping("encode")
public class EncodeController {

@Autowired
private EncodeTemplate encodeTemplate;

@GetMapping
public String date(@RequestParam String str) {
return encodeTemplate.current(str);
}
}

之后启动samples项目,并访问http://localhost:8080/encode?str=test,返回结果:
098f6bcd4621d373cade4e832627b4f6
修改application.yml的配置为:

encode:
enabled: true
type: none

之后重启samples项目,并访问http://localhost:8080/encode?str=test,返回结果:
test
说明starter项目创建成功。

配置元数据及参数约束

上面我们成功创建了starter项目,但是application.yml在IntelliJ IDEA里面打开的话,会发现我们自定义的配置显示警告(黄底):
01.png
这是因为没有正确配置additional-spring-configuration-metadata.json的原因(添加spring-boot-configuration-processor依赖后,打包正确的话会在src/main/resources/META-INF/目录下自动生成additional-spring-configuration-metadata.json配置文件)。从文件类型.json可以看出这是一个近几年IDE或者编辑器常用的配置文件类型(例如Postman,VSCode等),在这里作用是辅助IDE识别application.yml里的参数及类型,方便做出提示和约束。
首先在starter项目的pom.xml文件添加spring-boot-configuration-processor依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.github.liuzhuoming23</groupId>
<artifactId>encode-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>encode-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
</dependencies>

</project>

之后重新install我们的starter项目,然后在samples项目里面刷新maven后打开application.yml,会发现原来的警告已经消失了:
02.png
并且修改参数会得到提示,例如Boolean类型必须是false/true,枚举类必须是枚举元素名称等,要是参数值不符合约束,也会显示红色字体表示当前值不在约束的取值范围内,例如:
03.png

示例

封装了pinyin4j的starter示例:https://github.com/liuzhuoming23/pinyin4j-spring-boot-starter.git