🗺️
Spring Bootでネストされている設定値をMapで楽に安全に扱いたい
やりたいこと
こういう設定ファイルを
application.yml
app:
sample:
hoge:
host: hoge.dev
port: 80
piyo:
host: piyo.dev
port: 8080
こんな感じで扱えるように、Map
形式でうまい感じに取り込みたい。
String hogeHost = config.getSampleMap.get("hoge").getHost();
-
host
とport
をメンバにもつようなクラスをMap
に持ちたい - 単に
@ConstructorBinding
ではダメそう
普通のやり方
これでも全然OKそうだけど?
SampleConfig.java
@Configuration
@ConfigurationProperties("app")
@Getter
public class SampleConfig {
private final Map<String, Sample> sample = new HashMap<>();
@ConstructorBinding
@Getter
@RequiredArgsConstructor
public static class Sample {
private final String host;
private final int port;
}
}
ただし、他からMap
への変更(put
とか)を許してしまうのでやりたくない。他に渡すときにMap
をラップすればいいじゃん?と思って、
SampleConfig.java
@Configuration
@ConfigurationProperties("app")
@ConstructorBinding
public class SampleConfig {
private final Map<String, Sample> sample = new HashMap<>();
public Map<String, Sample> getSample() {
return MapUtils.unmodifiableMap(sample);
}
@ConstructorBinding
@Getter
@RequiredArgsConstructor
public static class Sample {
private final String host;
private final int port;
}
}
このように変更すると、起動時にエラーになってしまう。
Caused by: java.lang.IllegalStateException: No setter found for property: sample
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:104)
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:83)
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:59)
at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:473)
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:587)
at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:573)
at org.springframework.boot.context.properties.bind.Binder$Context.access$300(Binder.java:534)
at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:471)
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:411)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:340)
... 24 more
実現方法
インタフェース越しに値をとってくるようにするとできそう。
SampleConfig.java
public interface SampleConfig {
public Map<String, Sample> getUnmodifiedSample();
@ConstructorBinding
@Getter
@RequiredArgsConstructor
public static class Sample {
private final String host;
private final int port;
}
}
SampleConfigImpl.java
@Configuration
@ConfigurationProperties("app")
@Getter
public class SampleConfigImpl implements SampleConfig {
private final Map<String, Sample> sample = new HashMap<>();
public Map<String, Sample> getUnmodifiedSample() {
return MapUtils.unmodifiableMap(sample);
}
}
Discussion