@making's memo

All Categories All Tags Premium (Beta)


Spring Bootの@ ConfigurationPropertiesで型安全なプロパティ設定

Edit History

本記事は最終更新日から1年以上経過しており、内容が古い可能性があります。注意して読んでください。

application.propertiesapplication.ymlに設定したプロパティ値は@org.springframework.beans.factory.annotation.Valueアノテーションでインジェクション可能です。

Spring Bootではプロパティを用意して実行時に外部から変更したり、 プロファイルによって値を変えるということが多いためプロパティの管理は重要です。

次のプロパティファイル(application.properties)を例に説明します。

target.host=api.example.com
target.port=8080

target.hostStringtarget.portintを想定しており、 通常は次のようにインジェクションします。

@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属性をつけることで、プロパティ値のプリフィクスを設定することもできます。

import org.springframework.boot.context.properties.ConfigurationProperties;

@Component
@ConfigurationProperties(prefix = "target")
public class TargetProperties {
    private String host;
    private int port;
    // Setter/Getterは略すが、必要である
}

プロパティを使う側は次のようにTargetPropertiesクラスをインジェクションして、 各プロパティのGetterを呼べば良いです。

@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メソッドに つけることもできます。

@Bean
@ConfigurationProperties(prefix = "target")
public TargetProperties targetProperties() {
    return new TargetProperties();
}

Bean Validationによるプロパティ値のチェック

プロパティにはBean Validationのアノテーションで制約を設定することができます。

@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に次の依存関係が追加されます。(もちろん手動で追加しても良いです)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

note

spring-boot-configuration-processorにはPluggable Annotation Processing API (JSR 269)を実装したクラスが含まれており、 @ConfigurationPropertiesアノテーションが追加Javaソースからメタ情報のJSONを出力します。

IDEでビルドするとtarget/META-INF/spring-configuration-metadata.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.propertiesCtrl + Spaceを押すと次のように、 候補が表示されます。

Spring Bootでプロパティを外部化する場合は積極的に@ConfigurationPropertiesを使用するのが良いでしょう。

このエントリーをはてなブックマークに追加