--- title: Spring Bootの@ ConfigurationPropertiesで型安全なプロパティ設定 tags: ["Java", "Spring", "Spring Boot"] categories: ["Programming", "Java", "org", "springframework", "boot", "context", "properties"] date: 2016-03-06T06:04:50Z updated: 2016-03-06T06:06:26Z --- `application.properties`や`application.yml`に設定したプロパティ値は`@org.springframework.beans.factory.annotation.Value`アノテーションでインジェクション可能です。 Spring Bootではプロパティを用意して実行時に外部から変更したり、 プロファイルによって値を変えるということが多いためプロパティの管理は重要です。 次のプロパティファイル(`application.properties`)を例に説明します。 ``` properties target.host=api.example.com target.port=8080 ``` `target.host`は`String`で`target.port`は`int`を想定しており、 通常は次のようにインジェクションします。 ``` java @Component public class HelloService { @Value("${target.host}") String host; @Value("${target.port}") int port; // ... public String hello() { String target = "http://" + host + ":" + port; // ... } } ``` この方法でも大きな問題はありませんが、プロパティを使う側が毎度 - プロパティ名を文字列を指定 - プロパティ値の型を指定 する必要があります。 場合によってはプロパティを提供した側の意図とは異なった形で使われる可能性があります。 ### @ConfigurationPropertiesによるプロパティの設定 プロパティを多用するSpring Bootでは、安全にプロパティを扱うための仕組みとして `@org.springframework.boot.context.properties.ConfigurationProperties`アノテーションが用意されています。 次のようにプロパティファイルのプロパティに対応するJava BeanをDIコンテナに登録し、 `@ConfigurationProperties`アノテーションをつけることで、プロパティ値が各フィールドにインジェクションされます。 `prefix`属性をつけることで、プロパティ値のプリフィクスを設定することもできます。 ``` java import org.springframework.boot.context.properties.ConfigurationProperties; @Component @ConfigurationProperties(prefix = "target") public class TargetProperties { private String host; private int port; // Setter/Getterは略すが、必要である } ``` プロパティを使う側は次のように`TargetProperties`クラスをインジェクションして、 各プロパティのGetterを呼べば良いです。 ``` java @Component public class HelloService { @Autowired TargetProperties targetProperties; // ... public String hello() { String target = "http://" + targetProperties.getHost() + ":" + targetProperties.getPort(); // ... } } ``` これにより、前述の課題が解消されます。 > **note** > > `@ConfigurationProperties`は正確に言うと、プロパティクラスを定義するアノテーションではなく、 **DIコンテナに登録されたBeanに対してプロパティを設定するためのアノテーション**です。 この例だと、`@Component`をつけてDIコンテナに登録された`targetProperties`というBeanの `host`プロパティに対してプロパティファイル中の`target.host`の値を設定し、 `port`プロパティに対してプロパティファイル中の`target.port`の値を設定しています。 インジェクション対象のBeanにSetter/Getterが必要なのはこのためです。 > > `@ConfigurationProperties`はインジェクション対象のクラスにつけなくても、次のようにJava Configの`@Bean`メソッドに つけることもできます。 > > ``` java > @Bean > @ConfigurationProperties(prefix = "target") > public TargetProperties targetProperties() { > return new TargetProperties(); > } > ``` ### Bean Validationによるプロパティ値のチェック プロパティにはBean Validationのアノテーションで制約を設定することができます。 ``` java @Component @ConfigurationProperties(prefix = "target") public class TargetProperties { @NotEmpty private String host; @Min(1) @Max(65535) private int port; // Setter/Getterは略すが、必要である } ``` 制約に違反するプロパティ値が設定されている場合は起動時(DIコンテナ登録時)に`org.springframework.validation.BindException`がスローされます。 ### IDEによるプロパティの補完 `@ConfigurationProperties`を用いて定義したプロパティはIDE(STS or IntelliJ IDEA)で補完が効きます。 ただし、補完させるためにプロパティのメタ情報を生成する必要があります。 メタ情報が存在しない場合は、次の図のように警告が出ます。 警告マークをクリックし「Add spring-boot-configuration-processor to pom.xml」をクリックすると、 プロジェクトのpom.xmlに次の依存関係が追加されます。(もちろん手動で追加しても良いです) ``` xml org.springframework.boot spring-boot-configuration-processor true ``` > **note** > > `spring-boot-configuration-processor`にはPluggable Annotation Processing API (JSR 269)を実装したクラスが含まれており、 `@ConfigurationProperties`アノテーションが追加Javaソースからメタ情報のJSONを出力します。 > > IDEでビルドすると`target/META-INF/spring-configuration-metadata.json`に以下の内容のJSONファイルが出力されます。 > > ``` json > { > "groups": [{ > "name": "target", > "type": "com.example.TargetProperties", > "sourceType": "com.example.TargetProperties" > }], > "properties": [ > { > "name": "target.host", > "type": "java.lang.String", > "sourceType": "com.example.TargetProperties" > }, > { > "name": "target.port", > "type": "java.lang.Integer", > "sourceType": "com.example.TargetProperties" > } > ], > "hints": [] > } > ``` > > Mavenでビルド(`mvn compile`)しても`spring-configuration-metadata.json`は生成されます。 メタ情報が存在する場合は、`application.properties`で`Ctrl` + `Space`を押すと次のように、 候補が表示されます。 Spring Bootでプロパティを外部化する場合は積極的に`@ConfigurationProperties`を使用するのが良いでしょう。