spring
spring-boot
springframework
SpringBoot

Spring Bootの機能紹介(@ConfigurationProperties)


概要

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を使っています)


使用方法

@ConficurationPropertiesが付けられた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では使えません。


まとめ

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

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


参考文献

Spring Boot公式リファレンス