Spring

SpringApplication.run() 이후의 동작을 따라가 보기 – 설정 파일은 언제, 어떻게 로딩될까? - 2

류큐큐 2025. 7. 3. 22:41

1편의 글을 보고 왔다면 스프링 core쪽의 env 객체들을 파악했을것이고 해당 객체들의 역할에 대해 이해했다면 

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    this.configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    this.bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());
    }

    ConfigurationPropertySources.attach(environment);
    return environment;
}



해당 메서드에 대해 이해하기 수월할것이다. 

하나씩 뜯어보면

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    } else {
        ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
        if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
            environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
        }

        return (ConfigurableEnvironment)(environment != null ? environment : new ApplicationEnvironment());
    }
}

 

1편에서본 ConfigurableEnvironment 우리가 조작 가능한 환경에 대해 있다면 반환하고
없다면 그냥 빈객체를 반환한다.

 

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        environment.setConversionService(new ApplicationConversionService());
    }

    this.configurePropertySources(environment, args);
    this.configureProfiles(environment, args);
}

 

 

환경을 구성한다란 말과 함께 String 배열로 받은 인자들을 가지고
설정정보가 저장되어있는 저장소를 구성하고 프로필에 대해서도 구성한다.

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    if (!CollectionUtils.isEmpty(this.defaultProperties)) {
        DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
    }

    if (this.addCommandLineProperties && args.length > 0) {
        String name = "commandLineArgs";
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        } else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }

}

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
}

 

그리고 이렇게 구성한 환경설정객체를 

 

public static void attach(Environment environment) {
    // 1. 타입 체크
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment);

    // 2. 기존 property sources 목록 가져오기
    MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();

    // 3. configurationProperties 이름으로 붙은 PropertySource가 있는지 확인
    PropertySource<?> attached = getAttached(sources);

    // 4. 없다면 SpringConfigurationPropertySources 래핑해서 새로 만듦
    if (!isUsingSources((PropertySource)attached, sources)) {
        attached = new ConfigurationPropertySourcesPropertySource(
            "configurationProperties", 
            new SpringConfigurationPropertySources(sources)
        );
    }

    // 5. 기존 configurationProperties 제거 (중복 제거 목적)
    sources.remove("configurationProperties");

    // 6. 만든 configurationProperties를 가장 앞에 붙임 (우선순위 높임)
    sources.addFirst((PropertySource)attached);
}


ConfigurationPropertySources.attach(environment)는 @ConfigurationProperties 바인딩에 필요한 

특별한 PropertySource(SpringConfigurationPropertySources)를 Environment에 추가하는 작업이다.

이 PropertySource는 기존의 PropertySources를 래핑하여, Binder가 사용할 수 있는 형태로 바꿔며, @Value, @ConfigurationProperties가 제대로 동작하려면 반드시 이 과정이 필요하다.


내부적으로는 "configurationProperties"라는 이름으로 새 PropertySource를 생성하여,

기존 설정보다 우선순위가 높도록 가장 앞단에 추가합니다.

그 후 환경 설정이 완료되었음을 알리는 시점
prepareEnvironment()의 마지막에서는 아래와 같은 코드가 호출된다

listeners.environmentPrepared(bootstrapContext, environment);


SpringApplication이 내부적으로 가지고 있는 리스너들(SpringApplicationRunListener) 에게 “환경 구성이 끝났다”고 알려주는 역할이다.

해당 메서드를 타고 들어가보면 ApplicationEnvironmentPreparedEvent 객체를 이벤트로 발행하는데 그걸 받는 주체는

EnvironmentPostProcessorApplicationListener 이다.

이 클래스는 spring.factories에 다음과 같이 등록되어 있다:

org.springframework.context.ApplicationListener=\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener



그리고 내부에서는 다음과 같은 로직으로 동작한다

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        SpringApplication application = event.getSpringApplication();
        Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();

        while(var4.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
            postProcessor.postProcessEnvironment(environment, application);
        }

    }

 

여기서 우리가 자주 쓰는 application.yml, application.properties를 읽어오는 핵심 클래스가 등장한다.
바로 ConfigDataEnvironmentPostProcessor 이다. 이 클래스는 대표적인 EnvironmentPostProcessor 중 하나로,
우리가 아무 설정도 하지 않아도 동작하는 이유는 spring-boot 내부의 spring.factories에 이미 등록되어 있기 때문이다.

 

org.springframework.boot.env.EnvironmentPostProcessor=\ 
org.springframework.boot.env.ConfigDataEnvironmentPostProcessor

 

application.yml, application.properties 등 설정 파일 탐색프로필 별 설정 처리

PropertySource로 만들어 Environment에 등록

 

전체 흐름 요약

SpringApplication.run()
 └─ prepareEnvironment()
     └─ attach(configurationProperties)   // 설정 바인딩을 위한 환경 구성
     └─ listeners.environmentPrepared()
         └─ EventPublishingRunListener → ApplicationEnvironmentPreparedEvent 발행
             └─ EnvironmentPostProcessorApplicationListener
                 └─ EnvironmentPostProcessor 들 호출
                      └─ ConfigDataEnvironmentPostProcessor
                          └─ application.yml / properties 로딩

 

다음 편에서는 실제로 우리가 커스텀한 EnvironmentPostProcessor를 어떻게 등록하고,

어떤 우선순위로 작동하는지를 살펴보고자 한다.