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를 어떻게 등록하고,
어떤 우선순위로 작동하는지를 살펴보고자 한다.
'Spring' 카테고리의 다른 글
| SpringApplication.run() 이후의 동작을 따라가 보기 – 설정 파일은 언제, 어떻게 로딩될까? - 1 (0) | 2025.07.02 |
|---|