跳到主要内容

使用注解处理器生成您自己的元数据

DeepSeek V3 中英对照 Generating Your Own Metadata by Using the Annotation Processor

你可以通过使用 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>
xml

使用 Gradle 时,依赖项应在 annotationProcessor 配置中声明,如下例所示:

dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
gradle

如果你正在使用 additional-spring-configuration-metadata.json 文件,compileJava 任务应该配置为依赖于 processResources 任务,如下例所示:

tasks.named('compileJava') {
inputs.files(tasks.named('processResources'))
}
gradle

此依赖项确保在编译期间运行注解处理器时,额外的元数据是可用的。

备注

如果你在项目中使用 AspectJ,你需要确保注解处理器只运行一次。有几种方法可以实现这一点。使用 Maven 时,你可以显式配置 maven-apt-plugin,并仅在那里添加注解处理器的依赖。你也可以让 AspectJ 插件运行所有处理,并在 maven-compiler-plugin 配置中禁用注解处理,如下所示:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
</configuration>
</plugin>
xml
备注

如果你在项目中使用 Lombok,需要确保它的注解处理器在 spring-boot-configuration-processor 之前运行。如果使用 Maven,可以通过在 Maven 编译器插件的 annotationProcessors 属性中按顺序列出注解处理器来实现。如果使用 Gradle,可以在 annotationProcessor 配置中按顺序声明依赖。

自动元数据生成

处理器会识别所有被 @ConfigurationProperties 注解的类和方法。

备注

不支持使用 @ConfigurationProperties 元注解的自定义注解。

如果类具有一个带参数的构造函数,除非该构造函数使用 @Autowired 注解,否则会为每个构造函数参数创建一个属性。如果类具有显式使用 @ConstructorBinding 注解的构造函数,则会为该构造函数的每个参数创建一个属性。否则,属性将通过标准 getter 和 setter 方法的存在来发现,并对集合和映射类型进行特殊处理(即使只存在 getter 方法也能检测到)。注解处理器还支持使用 @Data@Value@Getter@Setter 这些 Lombok 注解。

考虑以下示例:

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "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;
}
// fold:off

}
java

这里暴露了三个属性,其中 my.server.name 没有默认值,而 my.server.ipmy.server.port 的默认值分别为 "127.0.0.1"9797。字段上的 Javadoc 用于填充 description 属性。例如,my.server.ip 的描述是 "IP address to listen to."。description 属性只有在类型作为正在编译的源代码可用时才会被填充。当类型仅作为依赖项中的编译类可用时,它不会被填充。对于这种情况,应提供手动元数据

备注

你应该仅在与 @ConfigurationProperties 字段的 Javadoc 中使用纯文本,因为在添加到 JSON 之前它们不会被处理。

如果你将 @ConfigurationProperties 与 record 类一起使用,那么应该通过类级别的 Javadoc 标签 @param 来提供 record 组件的描述(因为 record 类中没有显式的实例字段来放置常规的字段级别 Javadoc)。

注解处理器应用了一系列启发式方法来从源代码模型中提取默认值。默认值只能在类型作为正在编译的源代码可用时被提取。如果类型仅作为依赖项中的已编译类可用,则不会提取默认值。此外,默认值必须静态提供。特别是,不要引用在另一个类中定义的常量。此外,注解处理器无法自动检测 Collections 的默认值。

对于无法检测到默认值的情况,应提供手动元数据。请考虑以下示例:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "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

}

}
java

为了记录上述类中属性的默认值,您可以将以下内容添加到模块的手动元数据中

{"properties": [
{
"name": "my.messaging.addresses",
"defaultValue": ["a", "b"]
},
{
"name": "my.messaging.container-type",
"defaultValue": "simple"
}
]}
json
备注

文档化现有属性的附加元数据时,仅需要属性的 name

嵌套属性

注解处理器自动将内部类视为嵌套属性。与其在命名空间的根目录下记录 ipport,我们可以为其创建一个子命名空间。请参考更新后的示例:

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "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;
}
// @fold:off // getters/setters ...

}

}
java

前面的示例为 my.server.namemy.server.host.ipmy.server.host.port 属性生成了元数据信息。你可以在字段或 getter 方法上使用 @NestedConfigurationProperty 注解,以指示应将常规(非内部)类视为嵌套类。

提示

这对集合和映射类型没有影响,因为这些类型会被自动识别,并为它们中的每一个生成一个元数据属性。

添加额外元数据

Spring Boot 的配置文件处理非常灵活,通常情况下,可能存在一些属性并未绑定到 @ConfigurationProperties bean 上。你可能还需要调整现有键的某些属性。为了支持这些情况并让你能够提供自定义的“提示”,注解处理器会自动将 META-INF/additional-spring-configuration-metadata.json 文件中的条目合并到主元数据文件中。

如果你引用了一个被自动检测到的属性,那么如果指定了描述、默认值和弃用信息,这些内容将被覆盖。如果当前模块中未识别出手动属性声明,则该属性将作为新属性添加。

additional-spring-configuration-metadata.json 文件的格式与常规的 spring-configuration-metadata.json 完全相同。额外的属性文件是可选的。如果你没有任何额外的属性,请不要添加该文件。