ConfigurationProperties와 RefreshScope

스프링 프레임워크에서 @ConfigurationProperties@RefreshScope 어노테이션의 사용에 대해 알아보자.

ConfigurationProperties

@ConfigurationProperties 어노테이션이 사용된 빈은 외부화된 프로퍼티를 빈 내부로 주입받게된다.

RefreshScope

@RefreshScope 어노테이션이 사용된 빈은 lazy proxy로서 빈이 사용될 때 초기화 된다.

혼용

@ConfigurationProperties가 사용된 빈에 @RefreshScope를 중복 사용할 필요가 있을까?
결론부터 말하자면 필요없다.

간단하게 요약하면
@RefreshScope에 해당하는 빈의 재생성은 RefreshEvent에 trigger되고, @ConfigurationProperties에 해당하는 빈의 재생성은 EnvironmentChangeEvent에 trigger되는데 RefreshEventEnvironmentChangeEvent를 publish하기 때문에 함께 사용한다면 @RefreshScope가 우선된다.

이러한 용례는 동시성 이슈 처리를 위해 RefreshEvent에서 선제적으로 추가 작업이 필요할 경우 사용될 수 있다.

코드

ConfigurationPropertiesRebinder

아래 코드에서 @ConfigurationProperties이 사용된 빈이 EnvironmentChangeEvent의 발생에 따라 rebinding되는 것을 볼 수 있다.

package org.springframework.cloud.context.properties;
// ...
@Component
public class ConfigurationPropertiesRebinder
        implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
    // ...
    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (this.applicationContext.equals(event.getSource())
            // Backwards compatible
            || event.getKeys().equals(event.getSource())) {
                rebind();
        }
    }

    public void rebind() {
        // ...
        for (String name : this.beans.getBeanNames()) {
            rebind(name);
        }
    }

    public boolean rebind(String name) {
        // ...
            this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
            this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
        // ...
    }
    // ...
}

RefreshListener

아래 코드에서 RefreshRemoteApplicationEvent 발생 시 ContextRefresher의 refresh 메서드를 호출하는 것을 볼 수 있다.

package org.springframework.cloud.bus.event;
// ...
public class RefreshListener implements ApplicationListener<RefreshRemoteApplicationEvent> {
    // ...
    @Override
    public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
        // ...
            Set<String> keys = this.contextRefresher.refresh();
        // ...
    }
    // ...
}

ContextRefresher

아래 코드에서 ContextRefresher의 refresh 메서드 호출 시 EnvironmentChangeEvent를 발행하는 것을 볼 수 있다.

package org.springframework.cloud.context.refresh;
// ...
public abstract class ContextRefresher {
    // ...
    public synchronized Set<String> refresh() {
        Set<String> keys = refreshEnvironment();
        // ...
    }

    public synchronized Set<String> refreshEnvironment() {
        // ...
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        // ...
    }
    // ...
}

참고

https://gist.github.com/dsyer/a43fe5f74427b371519af68c5c4904c7
https://soshace.com/spring-cloud-config-refresh-strategies/