Spring Boot: YAML List property 주입 시 Could not resolve placeholder 에러
Spring Boot에서 List type으로 property를 주입받는 방법은 여러 가지 있으나, 이 포스트에서는 yaml 문법으로 정의된 list type property에 대한 삽질을 중점적으로 설명한다. yaml에 정의된 list type property는 Value 어노테이션으로 주입받을 수 없고, ConfigurationProperties 어노테이션을 통해서만 주입받을 수 있다.
테스트 환경
문제 재현에 사용한 버전은 현재 latest stable 버전을 사용했고, Kotlin이다. (Java도 동일하게 발생할 것이다)
- Spring Boot: 2.7.4 (Spring Framework 5.3.23)
- Kotlin: 1.6.21
Spring Boot property를 주입 관련 에러
Spring Boot 실행 시에 위와 같은 에러를 만나서 이 글을 찾아온 것이라면, 잘 찾아온 것이다. property 자체를 찾지 못할 때도 위와 같은 에러를 출력하지만, yaml list type property 주입에 실패했을 때도 동일한 에러가 출력된다.
처음에는 여러 가지 의심을 해봤는데, 아래와 같다
- property 이름에
-
가 있어서 안되나? → 아니다 - depth가 있어서 안되나? → 아니다
문제는 yaml list type property의 주입 이슈였다.
application properties를 정의하는 2가지 format
properties 파일 (java properties) 혹은 yml(yaml) 파일 (YAML) 2가지 방식으로 정의할 수 있다.
depth가 깊어질수록 YAML 문법이 properties보다 간결하다는 이점이 있는데, properties 파일이 커져서 검색할 때는 더 어렵다는 단점도 있다. 즉, 각각 장단점이 있다.
두 방식은 List property를 정의할 때 차이가 있다.
# application.properties
myvalue=aaa,bbb,ccc
properties의 경우 comma-separated로 작성해야 하지만, YAML spec은 list type을 표현하기 위한 별도 syntax가 있다.
# application.yml
myvalue:
- aaa
- bbb
- ccc
참고로 yaml에서 comma-separated로 작성해도 Spring Boot에서 list type으로 가져올 수 있다
# application.yml
myvalue: aaa,bbb,ccc
Value 어노테이션
properties를 정의하기 위한 위 방식들을 나열한 데에는 이유가 있다. Value 어노테이션을 통해 property를 주입받는 코드다.
# application.properties
myvalue=aaa,bbb,ccc# MyService.kt
@Service
class MyService(
@Value("\${myvalue}")
private val myConfig: MyConfig,
)
위 코드는 잘 동작한다. 하지만 문제는 yaml에서 list 형식을 사용했을 때였다.
# application.yml
myvalue:
- aaa
- bbb
- ccc# MyService.kt
@Service
class MyService(
@Value("\${myvalue}")
private val myConfig: MyConfig,
)
아래와 같은 에러가 발생했다.
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'myvalue' in value "${myvalue}"
에러 메시지가 모호해서 삽질을 한참 했는데, 진짜 원인은 아래 이슈였다.
Bulk closing outdated, unresolved issues. Please, reopen if still relevant.
2014년에 생성된 이슈인데 해결되어서 closing된 것이 아니고, 해결되지 않고 outdated되어서 닫힌 거였다 (…) 그래서 나는 대안을 찾아야했다.
Alternative
위 GitHub 이슈에도 나와있듯이 대안은 있다. 코드로 남겨두겠다.
Workaround: Value + SpEL with YAML List
class MyService(
@Value("#{'\${my-props}'.split(',')}")
private val props: List<String>,
)
yml 대신 properties 파일을 사용하는 방법
위 예제를 참고하면 된다.
yml 파일에서 comma-separated 값을 사용하는 방법
comma-separated 형식은 값이 많아질 경우 가독성이 안 좋다. (properties 파일도 동일)
# applikocation.yml
myvalue: aaa, bbb, ccc# MyService.kt
@Service
class MyService(
@Value("\${myvalue}")
private val myConfig: MyConfig,
)
Value 어노테이션 대신 ConfigurationProperties를 사용하는 방법
# application.yml
myconfig:
myvalue:
- aaa
- bbb
- ccc# MyService.kt
@Service
class MyService(
private val myConfig: MyConfig,
) {
@Configuration
@ConfigurationProperties("myconfig")
class MyConfig(
val myvalue: List<String>,
)
}
ConfigurationProperties를 사용하는 방법에 대해서는 아래 링크에 잘 설명되어 있다.
https://www.baeldung.com/configuration-properties-in-spring-boot