使用注解处理器生成自定义元数据
你可以通过使用 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
注解的类和方法。
不支持自定义注解(即被 @ConfigurationProperties 元注解的注解)。
|
如果类只有一个带参数的构造方法,则每个构造参数会生成一个属性,除非该构造方法带有 @Autowired
注解。
如果类有一个显式带有 @ConstructorBinding
注解的构造方法,则该构造方法的每个参数都会生成一个属性。
否则,属性会通过标准 getter 和 setter 方法自动发现,并对集合和 map 类型做特殊处理(即使只有 getter 也能识别)。
注解处理器还支持 @Data
、@Value
、@Getter
和 @Setter
等 lombok 注解。
例如:
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;
}
}
这会暴露三个属性,其中 my.server.name
没有默认值,my.server.ip
和 my.server.port
的默认值分别为 "127.0.0.1" 和 9797。
字段上的 Javadoc 会用于填充 description
属性。
例如,my.server.ip
的描述为“IP address to listen to.”。
只有当类型作为源代码参与编译时,description
属性才会被填充。
如果类型仅以依赖的已编译类形式存在,则不会填充。
此类场景下应提供 手动元数据。
@ConfigurationProperties 字段 Javadoc 只应使用纯文本,因为这些内容不会被处理,直接添加到 JSON。
|
如果你在 record 类上使用 @ConfigurationProperties
,则 record 组件的描述应通过类级 Javadoc 的 @param
标签提供(record 类没有显式实例字段用于常规字段级 Javadoc)。
注解处理器会通过多种启发式方式从源码模型中提取默认值。
只有当类型作为源代码参与编译时,才能提取默认值。
如果类型仅以依赖的已编译类形式存在,则不会提取。
此外,默认值必须静态提供,不能引用其他类中定义的常量。
注解处理器也无法自动检测 Collections
的默认值。
如果无法检测到默认值,应提供 手动元数据。 例如:
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
}
}
为了为上述类中的属性记录默认值,可以在 模块的手动元数据 中添加如下内容:
{"properties": [
{
"name": "my.messaging.addresses",
"defaultValue": ["a", "b"]
},
{
"name": "my.messaging.container-type",
"defaultValue": "simple"
}
]}
仅需为已存在属性记录附加元数据时,name 字段为必填。
|
嵌套属性
注解处理器会自动将内部类视为嵌套属性。
与其在命名空间根部记录 ip
和 port
,不如为其创建子命名空间。
例如:
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;
}
}
}
上述示例会为 my.server.name
、my.server.host.ip
和 my.server.host.port
属性生成元数据信息。
你可以在字段或 getter 方法上使用 @NestedConfigurationProperty
注解,指示普通(非内部)类也应被视为嵌套。
这对集合和 map 类型无效,这些类型会被自动识别,并为每个生成单独的元数据属性。 |
添加附加元数据
Spring Boot 的配置文件处理非常灵活,通常会存在未绑定到 @ConfigurationProperties
bean 的属性。
你也可能需要调整已存在 key 的某些属性,或完全忽略该 key。
为支持这些场景并允许你提供自定义“提示”,注解处理器会自动将 META-INF/additional-spring-configuration-metadata.json
中的内容合并到主元数据文件中。
如果你引用了自动检测到的属性,描述、默认值和弃用信息会被覆盖(如有指定)。 如果手动声明的属性未在当前模块中识别,则会作为新属性添加。
additional-spring-configuration-metadata.json
文件的格式与常规 spring-configuration-metadata.json
完全一致。
ignored.properties
部分中的项会从生成的 spring-configuration-metadata.json
文件的 properties
部分移除。
附加属性文件为可选项。 如果没有附加属性,则无需添加该文件。