使用注解处理器生成您自己的元数据
您可以使用 spring-boot-configuration-processor jar 包轻松地从带有 @ConfigurationProperties 注解的项中生成您自己的配置元数据文件。该 jar 包包含一个 Java 注解处理器,它会在您的项目编译时被调用。
配置注解处理器
使用 Maven 构建时,配置编译器插件(3.12.0 或更高版本)以将 spring-boot-configuration-processor 添加到注解处理器路径中
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用 Gradle 时,应在 annotationProcessor 配置中声明依赖项,如以下示例所示
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
如果您正在使用 additional-spring-configuration-metadata.json 文件,则应将 compileJava 任务配置为依赖于 processResources 任务,如以下示例所示
tasks.named('compileJava') {
inputs.files(tasks.named('processResources'))
}
此依赖项可确保在编译期间注解处理器运行时,附加元数据可用。
|
如果您在项目中使用 AspectJ,则需要确保注解处理器只运行一次。有几种方法可以做到这一点。使用 Maven,您可以显式配置
|
|
如果您在项目中使用 Lombok,则需要确保其注解处理器在 |
自动元数据生成
处理器会拾取带有 @ConfigurationProperties 注解的类和方法。它还会拾取带有 @ConfigurationPropertiesSource 注解的类
| 不支持使用上述任一注解进行元注解的自定义注解。 |
如果类只有一个带参数的构造函数,则会为每个构造函数参数创建一个属性,除非该构造函数使用 @Autowired 注解。如果类有一个显式使用 @ConstructorBinding 注解的构造函数,则会为该构造函数的每个构造函数参数创建一个属性。否则,属性通过标准 getter 和 setter 的存在来发现,并对集合和映射类型进行特殊处理(即使只存在 getter 也会被检测到)。注解处理器还支持使用 @Data、@Value、@Getter 和 @Setter lombok 注解。
考虑以下示例
-
Java
-
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.server")
public class MyServerProperties {
/**
* Name of the server.
*/
private String name;
/**
* IP address to listen to.
*/
private String ip = "127.0.0.1";
/**
* Port to listener to.
*/
private int port = 9797;
// getters/setters ...
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getIp() {
return this.ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("my.server")
class MyServerProperties(
/**
* Name of the server.
*/
var name: String,
/**
* IP address to listen to.
*/
var ip: String = "127.0.0.1",
/**
* Port to listen to.
*/
var port: Int = 9797)
这会暴露三个属性,其中 my.server.name 没有默认值,my.server.ip 和 my.server.port 默认值分别为 "127.0.0.1" 和 9797。字段上的 Javadoc 用于填充 description 属性。例如,my.server.ip 的描述是“要监听的 IP 地址。”。
您应该只在 @ConfigurationProperties 字段 Javadoc 中使用纯文本,因为它们在添加到 JSON 之前不会被处理。 |
如果您将 @ConfigurationProperties 与记录类一起使用,则记录组件的描述应通过类级别 Javadoc 标签 @param 提供(记录类中没有显式的实例字段可以放置常规的字段级别 Javadoc)。
注解处理器应用了一些启发式方法从源模型中提取默认值。默认值只能在类型作为正在编译的源代码可用时提取。当类型仅作为依赖项中的已编译类可用时,它们不会被提取。此外,默认值必须静态提供。特别是,不要引用在另一个类中定义的常量。此外,注解处理器无法自动检测 Collections 的默认值。
对于无法检测到默认值的情况,应提供 手动元数据。请考虑以下示例
-
Java
-
Kotlin
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.messaging")
public class MyMessagingProperties {
private List<String> addresses = new ArrayList<>(Arrays.asList("a", "b"));
private ContainerType containerType = ContainerType.SIMPLE;
// getters/setters ...
public List<String> getAddresses() {
return this.addresses;
}
public void setAddresses(List<String> addresses) {
this.addresses = addresses;
}
public ContainerType getContainerType() {
return this.containerType;
}
public void setContainerType(ContainerType containerType) {
this.containerType = containerType;
}
public enum ContainerType {
SIMPLE, DIRECT
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.util.Arrays
@ConfigurationProperties("my.messaging")
class MyMessagingProperties(
val addresses: List<String> = ArrayList(Arrays.asList("a", "b")),
var containerType: ContainerType = ContainerType.SIMPLE) {
enum class ContainerType {
SIMPLE, DIRECT
}
}
为了记录上述类中属性的默认值,您可以将以下内容添加到 模块的手动元数据 中
{"properties": [
{
"name": "my.messaging.addresses",
"defaultValue": ["a", "b"]
},
{
"name": "my.messaging.container-type",
"defaultValue": "simple"
}
]}
仅需要属性的 name 来记录现有属性的附加元数据。 |
嵌套属性
注解处理器会自动将内部类视为嵌套属性。与其在命名空间的根部记录 ip 和 port,我们可以为其创建一个子命名空间。考虑更新的示例
-
Java
-
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.server")
public class MyServerProperties {
private String name;
private Host host;
// getters/setters ...
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Host getHost() {
return this.host;
}
public void setHost(Host host) {
this.host = host;
}
public static class Host {
private String ip;
private int port;
// getters/setters ...
public String getIp() {
return this.ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("my.server")
class MyServerProperties(
var name: String,
var host: Host) {
class Host(val ip: String, val port: Int = 0)
}
前面的示例为 my.server.name、my.server.host.ip 和 my.server.host.port 属性生成元数据信息。您可以使用字段或 getter 方法上的 @NestedConfigurationProperty 注解来指示常规(非内部)类应被视为嵌套类。
| 这对集合和映射没有影响,因为这些类型会自动识别,并为它们中的每一个生成一个元数据属性。 |
配置属性源
如果另一个模块中的类型在 @ConfigurationProperties 注解类型中使用,则某些元数据元素无法自动发现。重用上面的示例,如果 Host 位于另一个模块中,则完整的元数据不可用,因为注解处理器无法访问 Host 的源代码。
为了处理此用例,请在包含 Host 类型的模块中添加注解处理器,并使用 @ConfigurationPropertiesSource 注解它
-
Java
-
Kotlin
import org.springframework.boot.context.properties.ConfigurationPropertiesSource;
@ConfigurationPropertiesSource
public class Host {
/**
* IP address to listen to.
*/
private String ip = "127.0.0.1";
/**
* Port to listener to.
*/
private int port = 9797;
// getters/setters ...
public String getIp() {
return this.ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
}
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
@ConfigurationPropertiesScan
class Host {
/**
* IP address to listen to.
*/
var ip: String = "127.0.0.1"
/**
* Port to listener to.
*/
var port = 9797
}
这会在 META-INF/spring/configuration-metadata/com.example.Host.json 中为 Host 生成元数据,并在注解处理器处理此类类型时自动重用。
您还可以注解位于另一个模块中的父类,@ConfigurationProperties 注解类型从该父类扩展而来。
| 如果您需要重用您无法控制的类型的元数据,请创建一个具有上述模式的文件,只要它在类路径上可用,就会被使用。 |
添加附加元数据
Spring Boot 的配置文件处理非常灵活,通常情况下可能存在未绑定到 @ConfigurationProperties bean 的属性。您可能还需要调整现有键的某些属性或完全忽略该键。为了支持这些情况并允许您提供自定义“提示”,注解处理器会自动将 META-INF/additional-spring-configuration-metadata.json 中的项合并到主元数据文件中。
为类型生成源元数据时,您还可以为该类型(例如 com.example.SomeType)在 META-INF/spring/configuration/metadata/com.example.SomeType.json 中创建自定义元数据。
如果您引用已自动检测到的属性,则其描述、默认值和弃用信息(如果指定)将被覆盖。如果手动属性声明在当前模块中未识别,则将其添加为新属性。
附加元数据文件的格式与常规的 spring-configuration-metadata.json 完全相同。“ignored.properties”部分中包含的项将从生成的 spring-configuration-metadata.json 文件的“properties”部分中删除。
附加属性文件是可选的。如果您没有任何附加属性,请不要添加该文件。