Spring Cloud アプリケーションにおける Nacos コンフィグレーションセンターのアノテーション

> 本記事は[こちらのブログ](https://www.alibabacloud.com/blog/annotations-for-nacos-configuration-center-in-spring-cloud-applications_602093?spm=a2c65.11461433.0.0.4ee35355bCemAJ)を参考にしています。
> 翻訳にはアリババクラウドの[ModelStudio](https://www.alibabacloud.com/en/product/modelstudio)([Qwen](https://www.alibabacloud.com/en/solutions/generative-ai/qwen))を使用しております。

# Spring Cloud アプリケーションにおける Nacos の動的設定リフレッシュのベストプラクティス

Zunfei Liu 作成

## はじめに

Spring Cloud アプリケーションでは、Nacos を非常に低いコストで統合して、動的な設定リフレッシュを実現できます。アプリケーションコード内では、公式の Spring アノテーションである `@Value` と `@ConfigurationProperties` を使用して、Spring 環境コンテキスト内のプロパティ値を参照できます。これに関する詳細情報は、[Best Practices for Dynamic Configuration with Spring Cloud, Nacos, and KMS](https://www.alibabacloud.com/blog/best-practices-for-dynamic-configuration-with-spring-cloud-nacos-and-kms_601998) という記事をご覧ください。

この方法の最大の利点は、コードレベルでの非侵襲性です。しかし、多くの制限もあります。例えば：

- **プロパティソースの競合**: Nacos の設定は、Spring コンテキスト環境のプロパティソースの1つとして機能します。プロパティを取得する際、JVM パラメータや環境変数を通じて注入されたプロパティが、Nacos よりも優先される場合があります。
  
- **同一キーの問題**: 複数の Nacos 設定を `spring.config.import` を介してインポートした場合、同じキーが存在すると、そのうちの1つしか有効になりません。複数のプロパティソース間でキーの重複がないことを確認するか、優先順位によるプロパティの上書き問題に対処する必要があります。

- **オブジェクト型フィールドへの自動インジェクションの欠如**: Nacos の設定はオブジェクト型のフィールドには自動的にインジェクトされません。

- **受動的な設定変更**: 設定の変更は受動的にしか受け取れず、設定変更時に二次処理を行うサポートや、他のビジネスアクションをトリガーする手段はありません。また、プロパティ変更前後の詳細な情報を把握することもできません。

- **@RefreshScope の必要性**: `@Value` アノテーションによって参照される設定に対して動的リフレッシュをサポートするには、SpringBean に `@RefreshScope` を追加する必要があります。設定更新時には、まず Bean が破棄され、その後新しいものが再作成されます。適切に使用しないと、これが生産環境で問題を引き起こす可能性があります。

これらの問題を解決し、アプリケーションが Nacos 設定センターにアクセスしやすくするために、Spring Cloud Alibaba は Nacos 設定センター用の新しいアノテーションセットをリリースしました。

- **@NacosConfig**: SpringBean のフィールドに適用すると、このアノテーションは指定された Nacos 設定をフィールドにインジェクトします。SpringBean クラスに適用すると、指定された Nacos 設定を Bean のプロパティにインジェクトします。FactoryBean メソッドに適用すると、指定された Nacos 設定を Bean のプロパティにインジェクトします。これは `@RefreshScope` アノテーションに依存せずに有効になります。

- **@NacosConfigListener**: このアノテーションは SpringBean 内のメソッドに使用され、Nacos の設定変更後に最新の設定内容を受け取ります。結果はオブジェクト型として受け取ることも可能です。

- **@NacosConfigKeysListener**: このアノテーションも SpringBean 内のメソッドに使用されます。Nacos 設定内の指定されたプロパティキー集合が変更された場合、変更前後のプロパティ値を含む `ConfigChangeEvent` をメソッドパラメータとして受け取ります。

以下では、3つのアノテーションの使い方について詳しく説明します。

## @NacosConfig アノテーションの概要と使用法

`@NacosConfig` アノテーションを使用すると、指定された Nacos 設定を SpringBean のフィールドにインジェクトできます。このアノテーションは、SpringBean の単一フィールド、または SpringBean 自体（BeanClass にアノテーションを追加し、FactoryBean インジェクションモードと組み合わせる場合を含む）に適用できます。インジェクト対象のタイプは、基本型、プロパティ、および `List<T>`、`Set<T>`、`Map<T>` などのコレクションや、任意のカスタム Java Beans をサポートします。設定形式が YAML またはプロパティの場合、特定のキー名を指定して、そのキーに対応するプロパティ値を読み込むこともできます。

### アノテーションフィールドの説明java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface NacosConfig {
    
    String group();

    String dataId();

    String key() default "";

    String defaultValue() default "";
}

- **group**: 導入する設定が属するグループ。
- **dataId**: 導入する設定の DataId。
- **key**: 設定形式が YAML またはプロパティの場合、特定のキー名を指定して、そのキーに対応するプロパティ値を読み込むことができます。
- **defaultValue**: 設定が存在しない場合、または指定されたキーのプロパティが空の場合にロードされるデフォルト値。

Nacos にアクセスするためのグローバルパラメータは、`application.properties` 内の `spring.cloud.nacos.config.server-addr` および `spring.cloud.nacos.config.namespace` で設定されます。`spring.config.import` でインポートされた設定は、Spring の `@Value` が参照するプロパティソースに適用されますが、アノテーションでインポートされた設定とは独立しています。ただし、同じ NacosClient オブジェクトを共有し、同じ Nacos インスタンスおよびネームスペース内の設定にアクセスします。

## 使用例

### 1. 完全な設定内容を String 型フィールドにロードjava
@NacosConfig(dataId = "SampleApp.application.properties", group = "default")
String configContent;

`dataId=SampleApp.application.properties`, `group=default` の完全な設定内容を `configContent` フィールドにインジェクトします。

### 2. 指定されたキーのプロパティを基本型フィールドにロードjava
@NacosConfig(dataId = "SampleApp.application.properties", group = "default", key = "useCache", defaultValue = "false")
boolean booleanValue;

`dataId=SampleApp.application.properties`, `group=default` の設定から `useCache` プロパティの値を `booleanValue` フィールドにインジェクトします。以下の5つの基本型とそのラッパー型をサポートします：`int`, `long`, `float`, `double`, `boolean`。

### 3. JSON 形式の設定を基本型配列にロードjava
@NacosConfig(dataId = "scoreintArray.json", group = "default")
int[] scores;

`dataId=scoreintArray.json`, `group=default` の JSON 形式の設定を `scores` フィールドにインジェクトします。設定形式は正しく逆シリアル化される必要があります。以下の5つの基本型とそのラッパー型の配列をサポートします：`int`, `long`, `float`, `double`, `boolean`。設定ファイルは `.json` で終わる必要があります。

### 4. 設定を Properties フィールドにロードjava
@NacosConfig(dataId = "SampleApp.application.properties", group = "default")
Properties properties = new Properties();

`dataId=SampleApp.application.properties`, `group=default` の設定内容を `properties` フィールドにインジェクトします。内部のプロパティ値は `properties.getProperty` メソッドで取得できます。リモートの Nacos 設定が変更された場合、`properties` オブジェクトが置き換えられ、参照が更新されます。プロパティおよび YAML 形式をサポートします。`dataId` は `.properties`, `.yaml`, `.yml` で終わる必要があります。YAML 形式では、配列またはリスト形式のプロパティを含めることはできません。

### 5. カスタム JavaBean フィールドにロードjava
@NacosConfig(dataId = "myobject.json", group = "default")
MyObject json2Object;

@NacosConfig
# JSON形式の設定をFactoryBeanにロードするjava
public class SampleConfiguration {
  
  @NacosConfig(dataId = myobject1.json, group = default)
  @Bean
  public MyObject bean1(){
    return new MyObject();
  }

  @NacosConfig(dataId = myobject2.json, group = default)
  @Bean
  public MyObject bean2(){
    return new MyObject();
  }
  
}

`dataId = myobject1.json` および `group = default` の設定内容を、`beanName=bean1` の `MyObject` 型の SpringBean にロードします。同様に、`dataId = myobject2.json` および `group = default` の設定内容を、`beanName=bean2` の `MyObject` 型の SpringBean にロードします。この機能は、`@Bean` アノテーションと併用する必要があります。

## @NacosConfigListener アノテーションの使用方法の紹介

`@NacosConfig` アノテーションのターゲットはフィールドです。このアノテーションは直接的にターゲットフィールドのプロパティを変更します。しかし、設定内容に対して二次処理を行う必要がある場合、`@NacosConfig` アノテーションでは対応できません。例えば、プロパティが変更された際にプログラムが特定のビジネスアクションを実行したり、コード内で変更された設定に基づいて二次処理を行う場合などです。このようなケースでは、`@NacosConfigListener` アノテーションを使用してこの要件を満たすことができます。

このアノテーションはSpringBeanメソッドに適用されます。指定された設定が変更されると、その設定内容がメソッドパラメータとして指定されたメソッドにコールバックされます。`@NacosConfig` アノテーションと同様に、メソッドパラメータは基本型、プロパティ、配列、コレクション、カスタムJavaBeansをサポートします。初期化時にコールバックを受け取りたい場合は、`initNotify=true` を設定できます（デフォルトはfalse）。以下のコールバックメソッド名は例であり、自由にカスタマイズ可能です。

### 使用例

1. **String型のパラメータを持つメソッドでネイティブ設定の内容を受け取る**java
@NacosConfigListener(dataId = myobjectArray.json, group = default)
private void fullContentChanged(String content) {
    System.out.println("receive ：" + content);
}

`dataId = myobjectArray.json` かつ `group = default` の設定が変更された際、その完全な内容が `content` パラメータとして `fullContentChanged` メソッドに渡されます。

2. **基本型のパラメータを持つメソッドで指定されたキー値の内容を受け取る**java
@NacosConfigListener(dataId = SampleApp.application.properties, group = default,key=score)
private void scoreChanged(int score) {
    System.out.println("receive ：" + score);
}

`dataId = SampleApp.application.properties` かつ `group = default` の設定において、`key=score` のプロパティ値が変更された場合、そのプロパティ値が `score` パラメータとして `scoreChanged` メソッドに渡されます。これは以下の5つの基本型をサポートします：int、long、float、double、boolean。

3. **基本型の配列パラメータを持つメソッドでJSON形式の設定内容を受け取る**java
@NacosConfigListener(dataId = scoresArray.json, group = default)
private void scoresChanged(int[] scores) {
    System.out.println("receive ：" + scores);
}

`dataId = scoresArray.json` かつ `group = default` のJSON形式の設定が変更された場合、その設定内容は基本型配列オブジェクトに逆シリアル化され、`scores` パラメータとして `scoresChanged` メソッドに渡されます。これには以下の5つの基本型の配列がサポートされます：int、long、float、double、boolean。なお、`dataId` は `.json` で終わる必要があり、設定内容はJSON配列形式である必要があります。

4. **Properties型のパラメータを持つメソッドでプロパティパラメータを受け取る**java
@NacosConfigListener(dataId = SampleApp.application.properties, group = default)
private void propertiesChanged(Properties properties) {
    System.out.println("receive ：" + properties);
}

`dataId = SampleApp.application.properties` かつ `group = default` のプロパティ形式の設定が変更された場合、その設定内容は `Properties` オブジェクトに解析され、`Properties` 型のパラメータとして `propertiesChanged` メソッドに渡されます。

5. **カスタムJavaBeanパラメータ**java
@NacosConfigListener(dataId = myobject.json, group = default)
private void myObjectChanged(MyObject object) {
    System.out.println("receive ：" + object);
}

@NacosConfigListener(dataId = myobjectArray.json, group = default)
private void myObjectArrayChanged(MyObject[] objectArray) {
    System.out.println("receive ：" + objectArray);
}

@NacosConfigListener(dataId = myobjectArray.json, group = default)
private void myObjectListChanged(List<MyObject> objectList) {
    System.out.println("receive ：" + objectList);
}

@NacosConfigListener(dataId = myobjectMap.json, group = default)
private void myObjectMapChanged(Map<String,MyObject> objectMap) {
    System.out.println("receive ：" + objectMap);
}

指定された設定内容が変更された場合、これらのメソッドはオブジェクト、オブジェクト配列、オブジェクトリスト、オブジェクトマップと共にコールバックされます。これにはカスタム配列やコレクションタイプもサポートされており、指定されたジェネリックに基づいて自動的に逆シリアル化されます。

## @NacosConfigKeysListener アノテーションの使用方法の紹介

このアノテーションはプロパティおよびYAML形式の設定をサポートしています。指定されたキーが変更された際に、`ConfigChangeEvent` パラメータを通じて変更前後の内容を受け取ることができます。`interestedKeys` または `interestedKeyPrefixes` を使用して監視するキーのセットやプレフィックスを指定できます。これらの条件に一致するいずれかのキーが変更された場合、コールバックがトリガーされます。

### 使用例java
@NacosConfigKeysListener(dataId = SampleApp.122110test.properties, group = default, interestedKeys = {
        "useLocalCache",}, interestedKeyPrefixes = {"122110."}) 
private void onKeysChangeSingle(ConfigChangeEvent changeEvent) {
    System.out.println("interestedKeyPrefixes:nacos." + changeEvent.getChangeItems());
}

このアノテーションはプロパティおよびYAML形式をサポートしており、`dataId` は `.properties`、`.yaml`、または `.yml` で終わる必要があります。

## まとめ

Spring Cloud Alibabaは、上記のアノテーションに対応した新しいバージョンを全シリーズ（2.2.x、2021.x、2022.x、2023.x）で正式にリリースしました。

- 2023.x シリーズはバージョン 2023.0.3.2 にアップグレードする必要があります。
- 2022.x シリーズはバージョン 2022.0.0.2 にアップグレードする必要があります。
- 2021.x シリーズはバージョン 2021.0.6.2 にアップグレードする必要があります。
- 2.2.x シリーズはバージョン 2.2.11 にアップグレードする必要があります。

2023.x バージョンでは、spring-cloud-alibaba 設定モジュールを再構築し、spring-alibaba-nacos-config モジュールを独立して抽出しました。このモジュールはSpringCloudフレームワークに依存しません。そのため、SpringBoot 3ベースのアプリケーションは、単純に spring-alibaba-nacos-config モジュールを含むだけで、Nacosの設定を参照するために `@Value` アノテーションや、本記事で紹介された3つのアノテーションを使用できるようになります。

### pom.xml への spring-alibaba-nacos-config 依存関係の導入xml
<dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring
# 参考文献

[1] Nacos 公式ウェブサイト<a href=https://nacos.io target=_blank>https://nacos.io</a>  
[2] Nacos Github メインリポジトリ<a href=https://github.com/alibaba/nacos target=_blank>https://github.com/alibaba/nacos</a>  
[3] エコシステムグループリポジトリ<a href=https://github.com/nacos-group target=_blank>https://github.com/nacos-group</a>  
[4] Spring Cloud Alibaba<a href=https://sca.aliyun.com/docs/2023/user-guide/nacos/quick-start/ target=_blank>https://sca.aliyun.com/docs/2023/user-guide/nacos/quick-start/</a>  

# Nacos 多言語エコシステムリポジトリ

[1] Nacos-GO-SDK<a href=https://github.com/nacos-group/nacos-sdk-go target=_blank>https://github.com/nacos-group/nacos-sdk-go</a>  
[2] Nacos-Python-SDK<a href=https://github.com/nacos-group/nacos-sdk-python_disabled target=_blank>https://github.com/nacos-group/nacos-sdk-python_disabled</a>  
[3] Nacos-Rust-SDK<a href=https://github.com/nacos-group/nacos-sdk-rust target=_blank>https://github.com/nacos-group/nacos-sdk-rust</a>  
[4] Nacos C# SDK<a href=https://github.com/nacos-group/nacos-sdk-csharp target=_blank>https://github.com/nacos-group/nacos-sdk-csharp</a>  
[5] Nacos C++ SDK<a href=https://github.com/nacos-group/nacos-sdk-cpp target=_blank>https://github.com/nacos-group/nacos-sdk-cpp</a>  
[6] Nacos PHP-SDK<a href=https://github.com/nacos-group/nacos-sdk-php target=_blank>https://github.com/nacos-group/nacos-sdk-php</a>  
[7] Rust Nacos Server<a href=https://github.com/nacos-group/r-nacos target=_blank>https://github.com/nacos-group/r-nacos</a>  

# 関連記事

<a href=https://www.alibabacloud.com/blog/best-practice-for-using-kms-to-encrypt-sensitive-configurations-in-mse-nacos_601753 target=_blank>Best Practice for Using KMS to Encrypt Sensitive Configurations in MSE Nacos</a>  
<a href=https://www.alibabacloud.com/blog/zero-trust-security-practice-of-nacos_601298 target=_blank>Zero-Trust Security Practice of Nacos</a>  
<a href=https://www.alibabacloud.com/blog/evolution-of-nacos-in-the-cloud-native-architecture_600958 target=_blank>Evolution of Nacos in the Cloud-native Architecture</a>