使用注解处理器生成自定义元数据

你可以通过使用 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,可以显式配置 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>

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

自动元数据生成

处理器会自动识别所有带有 @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.ipmy.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 字段为必填。

嵌套属性

注解处理器会自动将内部类视为嵌套属性。 与其在命名空间根部记录 ipport,不如为其创建子命名空间。 例如:

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.namemy.server.host.ipmy.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 部分移除。

附加属性文件为可选项。 如果没有附加属性,则无需添加该文件。