アノテーションプロセッサーを使用して独自のメタデータを生成する

spring-boot-configuration-processor jar を使用すると、@ConfigurationProperties (Javadoc) でアノテーションが付けられた項目から独自の構成メタデータファイルを簡単に生成できます。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 を使用している場合は、アノテーションプロセッサーが 1 回だけ実行されることを確認する必要があります。これを行うにはいくつかの方法があります。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 (Javadoc) でアノテーションが付けられたクラスとメソッドの両方を取得します。

@ConfigurationProperties (Javadoc) でメタアノテーションが付けられたカスタムアノテーションはサポートされていません。

クラスにパラメーター化されたコンストラクターが 1 つある場合、コンストラクターに @Autowired (Javadoc) のアノテーションが付けられていない限り、コンストラクターパラメーターごとに 1 つのプロパティが作成されます。クラスに @ConstructorBinding (Javadoc) のアノテーションが明示的に付けられたコンストラクターがある場合、そのコンストラクターのコンストラクターパラメーターごとに 1 つのプロパティが作成されます。それ以外の場合、プロパティは、標準の 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

}

これにより、my.server.name にはデフォルトがなく、my.server.ip および my.server.port のデフォルトはそれぞれ "127.0.0.1" および 9797 の 3 つのプロパティが公開されます。フィールドの Javadoc は、description 属性を設定するために使用されます。たとえば、my.server.ip の記述は「リッスンする IP アドレス」です。

@ConfigurationProperties (Javadoc) フィールドの Javadoc ではプレーンテキストのみを使用する必要があります。プレーンテキストは JSON に追加される前に処理されないためです。

レコードクラスで @ConfigurationProperties (Javadoc) を使用する場合、レコードコンポーネントの説明はクラスレベルの Javadoc タグ @param を介して提供される必要があります (レコードクラスには、通常のフィールドレベルの 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

	}

}

上記のクラスのプロパティのデフォルト値をドキュメント化するには、モジュールの手動メタデータに次のコンテンツを追加できます。

{"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(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 ...

	}

}

上記の例では、my.server.namemy.server.host.ipmy.server.host.port プロパティのメタデータ情報が生成されます。フィールドまたは getter メソッドで @NestedConfigurationProperty (Javadoc) アノテーションを使用して、通常の (内部ではない) クラスをネストされているかのように扱う必要があることを示すことができます。

これらの型は自動的に識別され、それぞれに対して単一のメタデータプロパティが生成されるため、これはコレクションやマップには影響しません。

メタデータを追加する

Spring Boot の構成ファイルの処理は非常に柔軟で、@ConfigurationProperties (Javadoc) Bean にバインドされていないプロパティが存在することがよくあります。また、既存のキーの一部の属性を調整する必要がある場合もあります。このようなケースをサポートし、カスタムの「ヒント」を提供できるようにするために、アノテーションプロセッサーは META-INF/additional-spring-configuration-metadata.json の項目をメインのメタデータファイルに自動的にマージします。

自動的に検出されたプロパティを参照する場合、説明、デフォルト値、非推奨情報は、指定されていれば上書きされます。手動プロパティ宣言が現在のモジュールで識別されない場合、新しいプロパティとして追加されます。

additional-spring-configuration-metadata.json ファイルの形式は、通常の spring-configuration-metadata.json とまったく同じです。追加のプロパティファイルはオプションです。追加のプロパティがない場合は、ファイルを追加しないでください。