- module로 나누어진 프로젝트에만 손댈 수 있는 상황
- module에서는 우리 API를 호출해서 응답을 처리하는 로직이 들어있다.
- stage 별로 호출 도메인이 달라 설정파일(properties or yml) 파일로 설정이 필요한 부분이었는데, application.yml에 설정하기에는 해당 module을 import하는 application마다 yml을 수정해주어야 하는 상황
- module에서 custom yml(예제에서는 extra.yml로 설정해서 사용)에 module에 필요한 properties 지정이 필요했다.
- 단순히
extra.yml을 읽는 방법extra.yml한 파일에 프로파일별 분리하여 적용하기extra.yml을spring.profile.active값에 따라extra.yml을 default로 읽고, 추가로extra-profile.yml을 읽어 처리
예제 git (master branch)
https://github.com/weightle55/propertyset/blob/master/src/main/java/com/example/propertyset/configuration/ExtPropertiesLoader.java
# extra.yml
ext:
test:
value: localVal
// ExtPropertiesLoader
@Configuration
public class ExtPropertiesLoader implements EnvironmentAware {
private static final String EXTRA_YML_PATH = "extra.yml";
private ConfigurableEnvironment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
// Resource 설정
Resource resource = new ClassPathResource(EXTRA_YML_PATH);
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
//Property를 읽고 environment 객체에 추가
PropertySource<?> yamlProperties = loader.load("extra", resource).get(0);
this.environment.getPropertySources().addLast(yamlProperties);
} catch (IOException e) {
throw new RuntimeException(EXTRA_YML_PATH + " load error", e);
}
}
}
@Service
public class TestService {
@Value("${ext.test.value}")
private String testValue;
public String getTestValue() {
return testValue;
}
}
서비스에서는 단순하게 Binding된 testValue 값을 리턴
ext.test.value에 저장된 local Value가 정상적으로 출력
예제 git (master feature/read-yml-with-one-file)
https://github.com/weightle55/propertyset/blob/feature/read-yml-with-one-file/src/main/java/com/example/propertyset/configuration/ExtPropertiesLoader.java
# ext.yml
ext:
test:
value: localVal
---
on-profile: dev
ext:
test:
value: devVal
---
on-profile: release
ext:
test:
value: releaseVal
// 참고 - 틀린 클래스이다.
@Configuration
public class ExtPropertiesLoader implements EnvironmentAware {
private static final String EXTRA_YML_PATH = "extra.yml";
private ConfigurableEnvironment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
loadYamlProperties(EXTRA_YML_PATH);
}
private void loadYamlProperties(String resourcePath) {
try {
Resource resource = new ClassPathResource(resourcePath);
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
// 여러 섹션이 있는 YAML 파일을 로드
List<PropertySource<?>> yamlProperties = loader.load(resourcePath, resource);
//profile 가져오기
List<String> activeProfiles = Arrays.stream(environment.getActiveProfiles()).toList();
for (PropertySource<?> propertySource : yamlProperties) {
//기본 섹션 추가
if (propertySource.getProperty("on-profile") == null) {
this.environment.getPropertySources().addLast(propertySource);
}
else if (activeProfiles.contains(propertySource.getProperty("on-profile"))) {
this.environment.getPropertySources().addLast(propertySource);
}
}
for (String profile : activeProfiles) {
System.out.println(profile);
}
} catch (IOException e) {
throw new RuntimeException(resourcePath + " load error", e);
}
}
}

나는 보통 application.yml로 사용될 때는 ---로 분할된 application.yml 파일에서 같은 키값(에제에서 ext.test.value) 가 있으면, 새 값으로 덮인다고 생각했다.
그런데, 디버거를 확인해보니

default와 dev properties가 각각 들어갔다.
application.yml에 추가할 때는 같은 키 값은 아래의 값으로 덮인다.
해당 부분은 Spring 에서 appliation.yml을 읽을 때, 프로파일 별로 중복 키값을 처리해주는 로직이 있으나, YamlPropertySourceLoader 로 읽을 때는 직접 구현해 주어야 한다.
// ExtPropertiesLoader.java
@Configuration
public class ExtPropertiesLoader implements EnvironmentAware {
private static final String EXTRA_YML_PATH = "extra.yml";
private ConfigurableEnvironment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
loadYamlProperties(EXTRA_YML_PATH);
}
private void loadYamlProperties(String resourcePath) {
try {
Resource resource = new ClassPathResource(resourcePath);
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
// 여러 섹션이 있는 YAML 파일을 로드
List<PropertySource<?>> yamlProperties = loader.load(resourcePath, resource);
//profile 가져오기
List<String> activeProfiles = Arrays.stream(environment.getActiveProfiles()).toList();
// 값을 덮기 위해 Properties를 저장할 Map 생성
Map<String, Object> mergedProperties = new HashMap<>();
for (PropertySource<?> propertySource : yamlProperties) {
//기본 섹션 추가
if (propertySource.getProperty("on-profile") == null) {
mergedProperties.putAll((Map<String,Object>) propertySource.getSource());
}
// profiles에 "on-profile"과 일치하는 값이 있을 때만 읽어서 맵에 추가
else if (activeProfiles.contains(propertySource.getProperty("on-profile"))) {
mergedProperties.putAll((Map<String,Object>) propertySource.getSource());
}
}
this.environment.getPropertySources().addLast(new MapPropertySource("extraProperties",mergedProperties));
} catch (IOException e) {
throw new RuntimeException(resourcePath + " load error", e);
}
}
}
Map<String, Object> mergedProperties를 생성하여, default에 포함된 키들을 먼저 넣고, 해당 프로파일의 키가 덮히도록 변경했다.
spring.profiles.active에 설정된 대로 잘 나왔다.

참고 git (branch - feature/dividing-files)
https://github.com/weightle55/propertyset/blob/feature/dividing-files/src/main/java/com/example/propertyset/configuration/ExtPropertiesLoader.java
default : extra.yml -> 기본으로 읽는 yml
dev : extra-dev.yml -> spring.profiles.active=dev 면 읽는 yml
release : extra-release.yml -> spring.profiles.active=release 면 읽는 yml
# ext.yml
ext:
test:
value: localVal
# ext-dev.yml
ext:
test:
value: devVal
# ext-release.yml
ext:
test:
value: releaseVal
// ExtPropertiesLoader
@Configuration
public class ExtPropertiesLoader implements EnvironmentAware {
private static final String YML_FILE_NAME = "extra";
private ConfigurableEnvironment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
loadYamlFileByProfile();
}
private void loadYamlFileByProfile() {
try {
//defaultResource 가져오기
Resource defaultResource = new ClassPathResource(YML_FILE_NAME + ".yml");
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
// default yml 읽기
List<PropertySource<?>> yamlProperties = loader.load("extra", defaultResource);
Map<String, Object> mergedProperties = new HashMap<>();
// extra.yml 파일이 있다면 초기화
if (!yamlProperties.isEmpty()) {
mergedProperties.putAll((Map<String, Object>) yamlProperties.get(0).getSource());
}
//Active Profile 가져오기
String[] activeProfiles = environment.getActiveProfiles();
for (String profile : activeProfiles) {
//extra-profile.yml 읽기
Resource profileResource = new ClassPathResource(YML_FILE_NAME + "-" + profile + ".yml");
List<PropertySource<?>> profileYamlProperties = loader.load("extra", profileResource);
// profile 설정이 되어있고, profile file이 있으면, property Map의 같은 키값 덮기
if(!profileYamlProperties.isEmpty()) {
mergedProperties.putAll((Map<String, Object>) profileYamlProperties.get(0).getSource());
}
}
this.environment.getPropertySources().addLast(new MapPropertySource("extraProperties", mergedProperties));
} catch (IOException e) {
throw new RuntimeException(YML_FILE_NAME + " load error", e);
}
}
}
mergedProperties.putAll((Map<String, Object>) yamlProperties.get(0).getSource());
for (String profile : activeProfiles) {
//extra-profile.yml 읽기
Resource profileResource = new ClassPathResource(YML_FILE_NAME + "-" + profile + ".yml");
List<PropertySource<?>> profileYamlProperties = loader.load("extra", profileResource);
// profile 설정이 되어있고, profile file이 있으면, property Map의 같은 키값 덮기
if(!profileYamlProperties.isEmpty()) {
mergedProperties.putAll((Map<String, Object>) profileYamlProperties.get(0).getSource());
}
}


