93
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Bootの機能紹介(@ConfigurationProperties)

Last updated at Posted at 2018-06-23

概要

Spring Bootでは環境別に設定を切り替える仕組みとしてapplication.propertiesapplication.ymlファイルを使えます。

Webアプリのポート番号やDB接続情報の設定で使うことが多いと思います。

これらで設定された値をアプリケーション内で利用する方法の1つをメモをかねて紹介します。

@Valueアノテーション

これは、SpringFrameworkに備わった機能で、Spring Bootでなくても使えることもあり、よく使われていると思います。

次のようなapplication.propertiesを用意して

app.name=exampleApplication
app.version=1.0.0.RELEASE

次のコンポーネントで利用できます。

@Component
public class SomeComponent {

    private static final Logger logger = LoggerFactory.getLogger(com.example.SomeComponent.class);

    @Value("${app.name}")
    private String applicationName;

    @Value("${app.version}")
    private String applicationVersion;

    public void logName() {
        logger.info("app-name = {}:{}", this.applicationName, this.applicationVersion);
    }

}

ちなみに、SpringFrameworkの機能は@Valueでのプロパティの読み込みまでで、application.propertiesファイルの設定値を自動的に読み込むのはSpring Bootの機能です。

@ConfigurationPropertiesアノテーション

ところで、適切にキー名が考慮されていれば次のように、キー名とフィールド名が同じ、ということも良くあるのではないでしょうか。そのようなときにはSpring Bootのプロパティ読み込みの仕組みである@ConfigurationPropertiesによる読み込みも利用できます。

次のコードは@Valueを使った(比較的)一般的なコードです。

@Component
public class SomeComponent {

    @Value("${app.some-config.url}")
    private String url;

    @Value("${app.some-config.username}")
    private String username;

    @Value("${app.some-config.password}")
    private String password

    // ...

}

このような場合は@ConfigurationPropertiesを使って次のように書けます。

@Component
@ConfigurationProperties(prefix = "app.some-config")
public class SomeComponent {

    private String url;

    private String username;

    private String password

    public String getUrl() {
        return this.url;
    }
    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return this.username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    // ...

}

なんだか、Getter/Setterのせいで後の方がコードは長くなってしまいましたが、これらはLombokを使ったり、IDEの自動生成機能でも生成できるので手間は後の例の方が少ないのではないでしょうか。

@ConfigurationPropertiesは、設定値の階層構造に着目しており、上記の例に加えてapp.some-config.paths.func1のようなキーを読み込みたい場合は次のようにする必要があります。

@Component
@ConfigurationProperties(prefix = "app.some-config")
public class SomeConponent {

    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}
public class FunctionPathsProperties {
    @Getter
    @Setter
    private String func1;
}

(長くなるのでLombokを使っています)

使用方法

@ConfigurationPropertiesアノテーションの付けられたBeanがSpringDIに登録されるとSetterを通じてプロパティの値が設定されます。従って、@Valueでは不要なGetter/Setterが必要になります。(一定の条件を満たすときはSetterが省略できますがGetterは省略できません)

また、設定値を保持するクラスをBeanとしてSpringDIに認識させる必要があります。
これは、次の3つの方法があります:

  1. @ConfigurationPropertiesを付けたクラスに@Componentを付けてBean登録する(上の例を参照)
  2. @ConfigurationPropertiesを付けたクラスのインスタンスを@BeanアノテーションでBean登録する
  3. @ConfigurationPropertiesを付けたクラスを、@Configurationクラスに@EnableConfigurationPropertiesアノテーションで指定してBeanに登録する

上記 2.の場合は次の通り

/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    private SomeProps some;

    // ...

}

/**
 * java config
 */
@Configuration
public class OtherConfig {

    @Bean
    public SomeProps someConfig() {
        return new SomePropes();
    }

    // ...

}

また、3. の場合は次の通り

/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    private SomeProps some;

    // ...

}

/**
 * java config
 */
@Configuration
@EnableConfigurationProperties({SomeProps.class})
public class OtherConfig {
    // ...
}

ところでこの例だと2.のパターンは3.の方が明らかに簡単なので使い道がなさそうです。
しかし、じつは2.のパターンのときは@BeanでBean登録する際に@Beanを付けたのとおなじメソッドに@ConfigurationPropertiesを付けることでprefixを上書きし、Beanをby-nameでAutowire、出来るようにしておくことで、「同じ構造の異なるprefixをもつプロパティ」を1つの@ConfigurationPropertiesクラスで読み込むことが出来ます:

/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス1(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    @Qualifier("someProps")
    private SomeProps some;

    // ...

}

/**
 * 設定値の利用クラス2(コンポーネント)
 */
@Component
public class AnotherComponent {

    @Autowired
    @Qualifier("anotherProps")
    private SomeProps another;

    // ...

}

/**
 * java config
 */
@Configuration
public class OtherConfig {

    @Bean(name = "someProps")
    @ConfigurationProperties(prefix = "app.some-config")
    public SomeProps someConfig() {
        return new SomePropes();
    }

    // 構造は同じだが別の値
    @Bean(name = "anotherProps")
    @ConfigurationProperties(prefix = "app.another-config")
    public SomeProps anotherConfig() {
        return new SomePropes();
    }

    // ...

}

Relaxed Binding

この記事を書くために公式リファレンスを確認していて見つけた機能です。

プロパティのキー名とフィールド名がある程度違っていても一定のルールで調整してくれる(いわゆる「よきに計らってくれる」)機能です。

これは、プロパティファイルでは複数語のキーを設定する場合はハイフンで区切ったり(kebab-case)やアンダースコアで区切る(snake_case)事が多いですが、フィールド名は大文字小文字で区切りを表現する(camelCase)事が多い(-に至っては言語仕様で使えない!)ことも実装の動機になっていると思われます。

次に挙げるプロパティキー(代表的なもののみ挙げています)は、@ConfigurationPropertiesで読み込むときは同じプロパティとしてdriverClassNameフィールドに読み込まれます:

  • driverClassName
  • driver-class-name
  • driver_class_name
  • DRIVER_CLASS_NAME

なおこの機能は公式のリファレンスにもあるように@Valueだと一部制限があります1。ただ、@Valueアノテーションに与える参照式でkebab-caseのみ使うように気を付ければ、同じように使えるようです。

公式ドキュメントの例をまとめると、以下のようになるようです:

プロパティdemo.item-price プロパティdemo.itemPrice 環境変数DEMO_ITEMPRICE
@Value("demo.item-price") :white_check_mark: :white_check_mark: :white_check_mark:
@Value("demo.itemPrice") :x: :white_check_mark: :x:

まとめ

@Valueによる読み込みも、SpringEL式が使えるなどの利点がありますが、階層構造を持ったひとまとまりのプロパティ群があるときは@ConfigurationPropertiesの利用も検討できると良いでしょう。

今回は適当な例が挙げられませんでしたが、本家のSpring BootではDataSourceProperties(spring.datasourceの読み込みクラス)などに使われており、デフォルト値の設定などもこの中で行われています。

参考文献

Spring Boot公式リファレンス


継続的に一定の方に見ていただいているようで、とてもうれしく、励みになっています。ありがとうございます。

最新のSpring Boot 3.4.3のドキュメントを確認して、基本的には同じ内容で動作することを確認して、ドキュメントのリンク先などを修正しました。

使えなくなった機能などはなく、@Valueアノテーションで、一部制限付きながらRelaxed bindingが利用できるようになったようでした。

  1. この記事の最初の執筆時点の最新版であるSpring Boot 2.0.3の時点において、@ValueではRelaxed bindingは利用できませんでした。いつの時点からかは詳しくわかりませんが、現行の3.4.3では本文の通り、一部制限付きながら利用できるようになりました。

93
76
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
93
76

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?