YAML이라는 이름은 "YAML은 마크업 언어가 아니다 (YAML Ain't Markup Language)” 라는 재귀적인 이름에서 유래되었다.
원래 YAML의 뜻은 “또 다른 마크업 언어 (Yet Another Markup Language)”였으나, YAML의 핵심은 문서 마크업이 아닌 데이터 중심에 있다는 것을 보여주기 위해 이름을 바꾸었다.
오늘날 XML과 JSON이 데이터 직렬화에 주로 쓰이기 시작하면서, 많은 사람들이 YAML을 '가벼운 마크업 언어'로 사용하려 하고 있다.
기존 properties 파일 대신 yaml 파일 작성
kdt:
version: "1.0"
minimum-order-amount: 1
support-vendors:
- a
- b
- c
- d
description: |
line 1 hello world
line 2 xxxx
line 3
Configuration 빈에서 @PropertySource
변경 properties → yaml
//@PropertySource("application.properties")
@PropertySource("application.yaml")
public class AppConfiguration {
}
yaml 파일 읽어서 확인하는 실행 파일 작성
public class OrderTester {
public static void main(String[] args) {
var applicationContext = new AnnotationConfigApplicationContext(AppConfiguration.class);
var environment = applicationContext.getEnvironment();
var version = environment.getProperty("kdt.version");
var minimumOrderAmount = environment.getProperty("kdt.minimum-order-amount", Integer.class);
var supportVendors = environment.getProperty("kdt.support-vendors", List.class);
var description = environment.getProperty("kdt.description", List.class);
System.out.println("version = " + version);
System.out.println("minimumOrderAmount = " + minimumOrderAmount);
System.out.println("supportVendors = " + supportVendors);
System.out.println("description = " + description);
applicationContext.close();
}
}
실행 후 오류 발생
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderProperties': Unsatisfied dependency expressed through field 'minimumOrderAmount'; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "${kdt.minimum-order-amount}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at org.prgrms.kdt.OrderTester.main(OrderTester.java:21)
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "${kdt.minimum-order-amount}"
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:79)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1328)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
... 15 more
Caused by: java.lang.NumberFormatException: For input string: "${kdt.minimum-order-amount}"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:654)
at java.base/java.lang.Integer.valueOf(Integer.java:999)
at org.springframework.util.NumberUtils.parseNumber(NumberUtils.java:211)
at org.springframework.beans.propertyeditors.CustomNumberEditor.setAsText(CustomNumberEditor.java:115)
at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:429)
at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:402)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:155)
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:73)
... 18 more
스프링부트는 yaml을 기본적으로 지원하지만, 스프링 프레임워크는 기본적으로 지원하지 않는다.
따라서 스프링에서 yaml 파일은 @PropertySource
로 읽을 수 없다.
지원하지 않는 소스는 PropertySourceFactory
를 직접 구현해 읽는다.
public class YamlPropertiesFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String s, EncodedResource encodedResource) throws IOException {
var yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(encodedResource.getResource());
var properties = yamlPropertiesFactoryBean.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
Configuration 빈의 @PropertySource
에도 factory
속성을 추가해 yaml을 읽도록 변경한다
@PropertySource(value = "application.yaml", factory = YamlPropertiesFactory.class)
public class AppConfiguration {
}
이제 실행을 하면 다음과 같은 결과가 나온다.
version = 1.0
minimumOrderAmount = 1
supportVendors = null
description = [line 1 hello world
line 2 xxxx
line 3]
나머지 값들은 잘 나오지만 리스트 형식인 supportVendros
는 실제 값을 못 가져오고 null
로 됐다.
yaml의 리스트 값을 읽기 위해서는 스프링부트에서 제공하는 Configuration Properties
를 사용해야 한다.
@ConfigurationProperties
를 이용하면 yaml에 있는 값을 객체로 매핑할 수 있다.
이전에 만들어 둔 OrderProperties 클래스를 사용해 yaml 값을 매핑한다.
properties 파일을 매핑할 때 쓴 @Value
어노테이션을 사용하지 않아도 된다.
@Configuration Properties
의 prefix
속성을 yaml의 prefix인 kdt
와 맞춰야 한다.
또한 값을 매핑할 필드들에 대해서 getter
, setter
도 필요하다.
@Configuration
@ConfigurationProperties(prefix = "kdt")
public class OrderProperties implements InitializingBean {
private String version;
private int minimumOrderAmount;
private List<String> supportVendors;
private String description;
@Value("${JAVA_HOME}")
private String javaHome;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("OrderProperties.afterPropertiesSet");
System.out.println("version = " + version);
System.out.println("minimumOrderAmount = " + minimumOrderAmount);
System.out.println("supportVendors = " + supportVendors);
System.out.println("description = " + description);
System.out.println("javaHome = " + javaHome);
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public int getMinimumOrderAmount() {
return minimumOrderAmount;
}
public void setMinimumOrderAmount(int minimumOrderAmount) {
this.minimumOrderAmount = minimumOrderAmount;
}
public List<String> getSupportVendors() {
return supportVendors;
}
public void setSupportVendors(List<String> supportVendors) {
this.supportVendors = supportVendors;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@ConfigurationProperties
는 스프링부트에서 지원하므로 사용하기 위해 Configuration 파일에 @EnableConfigurationProperties
어노테이션을 추가해야한다.
@Configuration
@ComponentScan(basePackages = {"org.prgrms.kdt.order", "org.prgrms.kdt.voucher", "org.prgrms.kdt.configuration"})
@PropertySource(value = "application.yaml", factory = YamlPropertiesFactory.class)
@EnableConfigurationProperties
public class AppConfiguration {
}
이제 실행 파일에서 OrderProperties
빈을 가져와 결과를 확인한다.
public class OrderTester {
public static void main(String[] args) {
var applicationContext = new AnnotationConfigApplicationContext(AppConfiguration.class);
var orderProperties = applicationContext.getBean(OrderProperties.class);
System.out.println("version = " + orderProperties.getVersion());
System.out.println("minimumOrderAmount = " + orderProperties.getMinimumOrderAmount());
System.out.println("supportVendors = " + orderProperties.getSupportVendors());
System.out.println("description = " + orderProperties.getDescription());
applicationContext.close();
}
}
이제 결과는 리스트 형식인 supportVendors
를 포함해 다음과 같이 잘 출력 된다.
https://github.com/yanJuicy/kdt-spring-order/tree/342a4108cf3fbd3f597daed543b739f4773188dc