Spring Bootの_@ConfigurationProperties
_アノテーションを使用して不変(最終)フィールドを持つことは可能ですか?以下の例
_@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}
_
これまでに試したアプローチ:
MyProps
クラスの_@Bean
_を作成するneededProperty
引数付きの2つのコンストラクターを提供するnew MyProps()
で作成されますnull
になります。@ComponentScan
_および_@Component
_を使用してMyProps
Beanを提供する。BeanInstantiationException
-> NoSuchMethodException: MyProps.<init>()
の結果私がそれを機能させる唯一の方法は、各非最終フィールドにゲッター/セッターを提供することです。
Spring Boot 2.2から、@ConfigurationProperties
で装飾された不変クラスを定義することが可能になりました。
ドキュメント は例を示しています。
(セッターの方法ではなく)バインドするフィールドを持つコンストラクタを宣言するだけです。
これで、セッターなしの実際のコードで問題ありません。
@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}
私はこの問題を頻繁に解決する必要があり、クラスでfinal
変数を使用できる少し異なるアプローチを使用しています。
まず、すべての構成を1つの場所(クラス)、たとえばApplicationProperties
に保存します。そのクラスには@ConfigurationProperties
アノテーションと特定のプレフィックス。また、@EnableConfigurationProperties
構成クラス(またはメインクラス)に対する注釈。
次に、ApplicationProperties
をコンストラクター引数として指定し、コンストラクター内のfinal
フィールドへの割り当てを実行します。
例:
メインクラス:
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
public static void main(String... args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
ApplicationProperties
クラス
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
}
そして、最終的なプロパティを持つクラス
@Service
public class SomeImplementation implements SomeInterface {
private final String someProperty;
@Autowired
public SomeImplementation(ApplicationProperties properties) {
this.someProperty = properties.getSomeProperty();
}
// ... other methods / properties
}
私は多くの異なる理由でこのアプローチを好みます。コンストラクターでより多くのプロパティを設定する必要がある場合、常に1つの引数(私の場合はApplicationProperties
)があるため、コンストラクター引数のリストは「巨大」ではありません。さらにfinal
プロパティを追加する必要がある場合、コンストラクターは同じままです(引数は1つだけ)。これにより、他の場所での変更の数を減らすことができます。
それが役に立てば幸い
最後に、不変オブジェクトが必要な場合は、次のようなセッターを「ハック」することもできます。
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (someProperty == null) {
this.someProperty = someProperty;
}
}
}
プロパティが単なる文字列ではない場合、つまり変更可能なオブジェクトである場合は、事態はより複雑になりますが、それはまた別の話です。
さらに良いことに、構成コンテナを作成できます
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private final List<MyConfiguration> configurations = new ArrayList<>();
public List<MyConfiguration> getConfigurations() {
return configurations
}
}
ここで、構成はclasなし
public class MyConfiguration {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (this.someProperty == null) {
this.someProperty = someProperty;
}
}
}
そしてapplication.ymlとして
myapp:
configurations:
- someProperty: one
- someProperty: two
- someProperty: other
私の考えは、内部クラスを介してプロパティグループをカプセル化し、ゲッターのみを使用してインターフェイスを公開することです。
プロパティファイル:
myapp.security.token-duration=30m
myapp.security.expired-tokens-check-interval=5m
myapp.scheduler.pool-size=2
コード:
@Component
@ConfigurationProperties("myapp")
@Validated
public class ApplicationProperties
{
private final Security security = new Security();
private final Scheduler scheduler = new Scheduler();
public interface SecurityProperties
{
Duration getTokenDuration();
Duration getExpiredTokensCheckInterval();
}
public interface SchedulerProperties
{
int getPoolSize();
}
static private class Security implements SecurityProperties
{
@DurationUnit(ChronoUnit.MINUTES)
private Duration tokenDuration = Duration.ofMinutes(30);
@DurationUnit(ChronoUnit.MINUTES)
private Duration expiredTokensCheckInterval = Duration.ofMinutes(10);
@Override
public Duration getTokenDuration()
{
return tokenDuration;
}
@Override
public Duration getExpiredTokensCheckInterval()
{
return expiredTokensCheckInterval;
}
public void setTokenDuration(Duration duration)
{
this.tokenDuration = duration;
}
public void setExpiredTokensCheckInterval(Duration duration)
{
this.expiredTokensCheckInterval = duration;
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("{ ");
sb.append("tokenDuration=").append(tokenDuration);
sb.append(", expiredTokensCheckInterval=").append(expiredTokensCheckInterval);
sb.append(" }");
return sb.toString();
}
}
static private class Scheduler implements SchedulerProperties
{
@Min(1)
@Max(5)
private int poolSize = 1;
@Override
public int getPoolSize()
{
return poolSize;
}
public void setPoolSize(int poolSize)
{
this.poolSize = poolSize;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("poolSize=").append(poolSize);
sb.append(" }");
return sb.toString();
}
}
public SecurityProperties getSecurity() { return security; }
public SchedulerProperties getScheduler() { return scheduler; }
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("security=").append(security);
sb.append(", scheduler=").append(scheduler);
sb.append(" }");
return sb.toString();
}
}
@Value
アノテーションを使用してフィールド値を設定できます。これらはフィールドに直接配置でき、セッターを必要としません。
@Component
public final class MyProps {
@Value("${example.neededProperty}")
private final String neededProperty;
public String getNeededProperty() { .. }
}
このアプローチの欠点は次のとおりです。