概要
Spring Bootでは環境別に設定を切り替える仕組みとしてapplication.properties
やapplication.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つの方法があります:
-
@ConfigurationProperties
を付けたクラスに@Component
を付けてBean登録する。(上の例を参照) -
@ConfigurationProperties
を付けたクラスのインスタンスを@Bean
アノテーションでBean登録する。 -
@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
の読み込みクラス)などに使われており、デフォルト値の設定などもこの中で行われています。