본문 바로가기

3. 기술 공부/Java (Spring, Spring Boot)

[Spring/Spring boot] Property 파일 제대로 설정하기

Goal

이번 포스팅에서는 Spring, Spring boot 기반 애플리케이션에서 `PropertySourcesPlaceholderConfigurer`를 기반으로 Property 파일을 설정하는 것의 문제점을 살펴보고, 해당 문제점을 해결할 수 있는 다른 방법에 대해 설명한다.

Problem

기존에 필자가 관리하던 프로젝트에서는 `*.properties` 파일을 `PropertySourcesPlaceholderConfigurer` bean을 생성함으로써 설정하고 있었다. 이렇게만 설정해주어도 @Value로 Property 값을 바인딩하거나, Env에서 값을 사용하는 경우에 큰 이슈가 발생하지 않았다. 

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(ConfigurableEnvironment env,
                                                                         List<ResourcePropertySource> customPropertySources) {
	var propertySources = environment.getPropertySources();
	customPropertySources.forEach(resource -> propertySources.addFirst(resource)); //Env에 Setting
    
	var placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();
	placeholderConfigurer.setPropertySources(env.getPropertySources()); //placeholderConfigurer에 Setting
    
	return placeholderConfigurer;
}

 

하지만  Spring boot 애플리케이션에서 위의 설정 방법으로 `@ConditionalOnProperty` 어노테이션을 사용하는 경우 property 값을 제대로 인식하지 않는 이슈가 발생하였다. `@ConditionalOnProperty` 어노테이션에 대한 평가가 `PropertySourcesPlaceholderConfigurer` BeanFactoryPostProcessor가 실행되기 이전에 발생하기 때문에, 설정한 `*.properties` 파일에 있는 property 값들이 로드가 되지 않은 채로 평가가 되어 오동작하게 된 것이다.

 

Solution

그럼 어떻게 `@ConditionalOnProperty`가 평가되기 이전에 Property 설정을 할 수 있을까?

 

 

`@ConditionalOnProperty`보다 이전에 context에 property를 업로드할 수 있는 방법은 2가지가 있다.

 

첫번째, `EnvironmentPostProcessor`를 구현한다. (Spring boot 애플리케이션에서만 사용 가능)

해당 interface를 아래와 같이 구현하고, `/resources/META-INF/spring.factories` 파일에 구현된 클래스의 경로를 입력해주면  `EnvironmentPostProcessor`가 `SpringApplication`의 prepareEnvironment 단계에서 로드된다.

 @Order(Ordered.HIGHEST_PRECEDENCE)
 public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
	//customPropertySources 구하는 코드 생략
	var propertySources = environment.getPropertySources();
  	customPropertySources.forEach(resource -> propertySources.addFirst(resource));
}
#/resources/META-INF/spring.factories

org.springframework.boot.env.EnvironmentPostProcessor={classpath}

 

두번째, `ApplicationContextInitializer`를 구현한다. (Spring/Spring boot 애플리케이션 모두 사용 가능)

`EnvironmentPostProcessor`는 Spring boot에서만 지원하기 때문에 Spring 애플리케이션의 경우에는 아래와 같이 `ApplicationContextInitializer`를 구현하면 된다. `/resources/META-INF/spring.factories` 파일에 구현된 클래스의 경로를 입력해주면 `ApplicationContextInitializer`가 `SpringApplication`의 prepareContext 단계에서 로드된다.

public class CustomContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        var propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        getCustomPropertySources().forEach(resource -> propertySources.addFirst(resource));
        configurableApplicationContext.addBeanFactoryPostProcessor(getCustomPlaceholderConfigurer(propertySources));
    }

    private PropertySourcesPlaceholderConfigurer getCustomPlaceholderConfigurer(PropertySources propertySources) {
        PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        placeholderConfigurer.setPropertySources(propertySources);

        return placeholderConfigurer;
    }

    private List<ResourcePropertySource> getCustomPropertySources() {
        //코드 생략
    }

}
#/resources/META-INF/spring.factories

org.springframework.context.ApplicationContextInitializer={classpath}

Spring boot가 아닌 경우에는 `ApplicationContextInitializer`를 application context에 직접 등록해주면 된다.

 

두 방법 모두 아래와 같이 context가 refresh 되기 전에 실행이 되기 때문에 두 interface를 구현해주면 context에 미리 property를 세팅할 수 있다.

public class SpringApplication {
	//코드 생략
	public ConfigurableApplicationContext run(String... args) {
	//코드 생략
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); // --> EnvironmentPostProcessor 실행
		this.configureIgnoreBeanInfo(environment);
		Banner printedBanner = this.printBanner(environment);
		context = this.createApplicationContext();
		exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
		this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); // --> ApplicationContextInitializer 실행
		this.refreshContext(context); // --> @ConditionalOnProperty 실행
		this.afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
		}
		//코드 생략
	}
 }

 

이번 포스팅에서는 `PropertySourcesPlaceholderConfigurer`로 Property 파일을 세팅했을 때 발생했던 이슈와, 어떻게 해당 이슈를 피할 수 있었는지에 대해 간단히 정리해보았다. 방법을 찾아보면서 Property를 세팅하기 위해 사용하는 `PropertySourcesPlaceholderConfigurer`를 `@OnConditionalProperty` 어노테이션과 함께 사용했을 때 흔히들 같은 이슈를 겪는다는 것을 알 수 있었다.  비슷한 이슈로 어려움을 겪는 분들께 조금이라도 도움이 되기를..