web-dev-qa-db-ja.com

不変の@ConfigurationProperties

Spring Bootの_@ConfigurationProperties_アノテーションを使用して不変(最終)フィールドを持つことは可能ですか?以下の例

_@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}
_

これまでに試したアプローチ:

  1. 2つのコンストラクターでMyPropsクラスの_@Bean_を作成する
    • 空とneededProperty引数付きの2つのコンストラクターを提供する
    • Beanはnew MyProps()で作成されます
    • フィールドはnullになります。
  2. _@ComponentScan_および_@Component_を使用してMyProps Beanを提供する。
    • BeanInstantiationException-> NoSuchMethodException: MyProps.<init>()の結果

私がそれを機能させる唯一の方法は、各非最終フィールドにゲッター/セッターを提供することです。

39
RJo

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() { .. }
}
7
davidxxx

私はこの問題を頻繁に解決する必要があり、クラスで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つだけ)。これにより、他の場所での変更の数を減らすことができます。

それが役に立てば幸い

13
Tom

最後に、不変オブジェクトが必要な場合は、次のようなセッターを「ハック」することもできます。

@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
1
user2688838

私の考えは、内部クラスを介してプロパティグループをカプセル化し、ゲッターのみを使用してインターフェイスを公開することです。

プロパティファイル:

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();
    }
}
1
dshvets1

@Valueアノテーションを使用してフィールド値を設定できます。これらはフィールドに直接配置でき、セッターを必要としません。

@Component
public final class MyProps {

  @Value("${example.neededProperty}")
  private final String neededProperty;

  public String getNeededProperty() { .. }
}

このアプローチの欠点は次のとおりです。

  • 各フィールドで完全修飾プロパティ名を指定する必要があります。
  • 検証が機能しない(cf. this question
0
oberlies