전자정부프레임워크(Spring)에서 properties를 yaml로 적용하기 개발/전자정부프레임워크2020. 7. 15. 11:31
최근, 전자정부 프레임워크에서 사용하고 있는 properties를 yaml로 바꿔야 하는 상황이 생겼다.
이미 서비스 중인 프로젝트였기 때문에 최대한 소스 수정은 피하고 적용하는 법을 찾아야만 했다.
또한, 로컬 서버와 테스트 서버, 실서버의 properties는
톰캣의 시스템 변수 설정(-Dspring.profiles.active)으로 분리되어 있었기 때문에
yaml로 변경해도 서버에 따른 분리가 필요했다.
단순히 properties를 yaml로 바꾸는 변경 자체는 굉장히 쉬었으나 소스는 properties처럼 사용하면서
yaml안에서도 spring.profiles.active를 변경 가능하도록 하느랴 신경을 많이 쓴 코드이다.
1. application.yml 작성한다.
spring:
profiles:
active: local
---
spring:
profiles: local
jdbc:
url: jdbc:mysql://${hostname}:${port}/${database}
username: ${username}
password: ${password}
---
spring:
profiles: dev
jdbc:
url: jdbc:mysql://${hostname}:${port}/${database}
username: ${username}
password: ${password}
---
spring:
profiles: prod
jdbc:
url: jdbc:mysql://${hostname}:${port}/${database}
username: ${username}
password: ${password}
2. pom.xml에 dependency를 추가한다.
<!-- yaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.17</version>
</dependency>
3. ArrayDocumentMatcher.java를 추가한다.
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.util.StringUtils;
/**
* Matches a document containing a given key and where the value of that key is an array
* containing one of the given values, or where one of the values matches one of the given
* values (interpreted as regexes).
*
* @author Dave Syer
* @deprecated as of 1.4.1 in favor of exact String-based matching
*/
@Deprecated
public class ArrayDocumentMatcher implements DocumentMatcher {
private final String key;
private final String[] patterns;
public ArrayDocumentMatcher(final String key, final String... patterns) {
this.key = key;
this.patterns = patterns;
}
@Override
public MatchStatus matches(Properties properties) {
if (!properties.containsKey(this.key)) {
return MatchStatus.ABSTAIN;
}
Set<String> values = StringUtils
.commaDelimitedListToSet(properties.getProperty(this.key));
if (values.isEmpty()) {
values = Collections.singleton("");
}
for (String pattern : this.patterns) {
for (String value : values) {
if (value.matches(pattern)) {
return MatchStatus.FOUND;
}
}
}
return MatchStatus.NOT_FOUND;
}
}
4. SpringProfileDocumentMatcher.java를 추가한다.
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
public class SpringProfileDocumentMatcher implements DocumentMatcher, EnvironmentAware, ApplicationContextAware {
private ApplicationContext ctx;
private static final String[] DEFAULT_PROFILES = new String[] {"local"};
private String[] activeProfiles = new String[0];
public SpringProfileDocumentMatcher() {}
@Override
public void setEnvironment(Environment environment) {
if (environment != null) {
addActiveProfiles(environment.getActiveProfiles());
}
}
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<String>(Arrays.asList(this.activeProfiles));
Collections.addAll(set, profiles);
this.activeProfiles = set.toArray(new String[set.size()]);
}
@Bean(name = "yamlProperties")
public YamlPropertiesFactoryBean yamlPropertiesFactoryBean() throws IOException {
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(ctx.getResources("classpath:application.yml"));
return yamlPropertiesFactoryBean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Override
public MatchStatus matches(Properties properties) {
String[] profiles = this.activeProfiles;
if (profiles.length == 0) {
try {
String activeProfile = yamlPropertiesFactoryBean().getObject().getProperty("spring.profiles.active");
if (activeProfile != null && activeProfile != "") {
profiles = new String[] { activeProfile };
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (profiles.length == 0) {
profiles = DEFAULT_PROFILES;
}
}
return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties);
}
}
5. context.xml을 수정한다.
<!-- Old Properties Files -->
<!-- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:properties/config-#{systemProperties['spring.profiles.active']}.properties</value>
</list>
</property>
</bean> -->
<context:annotation-config/>
<bean id="yamlProperties" class="org.springframework.beans.factory.config.YamlPropertiesFactoryBean">
<property name="resources" value="classpath:application.yml"/>
<property name="documentMatchers">
<bean class="YourPackageName.SpringProfileDocumentMatcher" />
</property>
</bean>
<context:property-placeholder properties-ref="yamlProperties"/>
위 코드를 적용하면 톰캣의 시스템 변수 설정(-Dspring.profiles.active)를 적용하는 것이 1순위,
그 변수가 설정되지 않았으면 yaml 파일 안의 spring.profiles.active 값이 2순위,
그것마저 없다면 SpringProfileDocumentMatcher.java의 DEFAULT_PROFILES 값으로 적용된다.
참고 및 출처 :
- https://stackoverflow.com/questions/33525951/spring-yaml-profile-configuration
- https://yangbongsoo.gitbook.io/study/spring/spring_develop_issue1