Spring에 대한 정리글입니다. 주로 IoC 컨테이너, BeanFactory, 그리고 Spring Boot 애플리케이션의 시작 흐름에 대해 다룹니다.
참고: SpringApplication 클래스는 Spring Boot에서만 사용되며, 일반 Spring에서는 수동으로 초기화가 필요합니다.
BeanFactory를 확장하며 다양한 기능 포함: EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, ResourceLoadergetBeanNamesForType(), getBeansOfType() 등 제공getParentBeanFactory() 메서드를 통해 현재 빈 팩토리의 부모 빈 팩토리를 알 수 있으며 만약, 없을 경우 null 을 반환setParentBeanFactory(parentFactory))ResourcePatternResolver를 통해 여러 리소스를 위치 패턴으로 검색 가능Spring Boot 애플리케이션은 main() 메서드 내에서 SpringApplication.run()을 호출하면서 시작됩니다.
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
ApplicationContext 인스턴스를 생성하며 빈들을 스캔하고, 설정에 따라 각 빈을 생성하고 구성합니다.
그 과정에서 다양한 이벤트가 발생하며, 미리 등록된 리스너들이 해당 이벤트를 수신하여 처리합니다.
application.yml 또는 application.properties 파일, 그리고 커맨드라인 인자에서 환경설정을 읽고 이를 환경 변수로 등록합니다.
이 전체 과정은 내부적으로 BeanFactory와 연결되며, IoC 컨테이너가 실제로 동작하는 핵심 흐름과도 밀접합니다
HierarchicalBeanFactory는 현재 빈 팩토리뿐 아니라 상위 빈 팩토리를 참조할 수 있는 구조를 가지고 있습니다.
빈 조회는 getBean() 메서드를 통해 수행되며, 대부분의 실제 운영 환경에서는 이 메서드를 직접 호출하지 않고, 주입받는 방식(@Autowired 등)을 통해 사용됩니다.
빈을 주입받는 과정에서, 컨테이너는 이름이나 타입에 기반하여 알맞은 빈을 검색합니다.
HierarchicalBeanFactory 가 제공하는 계층적 탐색 구조에 기반합니다 이후, refreshContext() 메서드가 호출되어 IoC 컨테이너의 최종 초기화가 이루어지며, 모든 빈이 등록되고 초기화됩니다.
ListableBeanFactory 는 언제 사용될까 ?예시:
getBeansOfType() 메서드가 활용됩니다. @Conditional 어노테이션과 함께 사용되며, 특정 조건에 맞는 빈을 활성화하거나 비활성화할 때 내부적으로 빈 목록을 확인해야 할 필요가 있습니다.getBeanDefinitionNames() 등의 메서드가 호출되며, 설정 누락이나 충돌 여부를 검증하는 데 사용됩니다.참고 : 왜 객체를 빈으로 등록할까 ?
- 의존성 관리 (IoC)
- 외부 객체에 의존하는 로직을 테스트하기 쉬움 (예: Mock 주입)
- 비즈니스 도메인이 특정 객체에 의존하고 있다면, 의존성 주입을 통해 가짜 객체(Mock)로 대체하여 독립적인 테스트가 가능함
- 스코프 관리
singleton: 동일한 인스턴스를 재사용해야 하는 경우 유용prototype: 요청마다 새로운 인스턴스를 생성해야 할 때 사용- 라이프사이클 관리
- 객체 초기화 및 소멸 단계에서 필요한 작업을 자동으로 처리할 수 있음
@PostConstruct,@PreDestroy, 혹은InitializingBean,DisposableBean인터페이스를 통해 정의 가능
public ConfigurableApplicationContext run(String... args) {
Startup startup = SpringApplication.Startup.create();
if (this.registerShutdownHook) {
shutdownHook.enableShutdownHookAddition();
}
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
Throwable ex;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
ex = var10;
throw this.handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
return context;
} catch (Throwable var9) {
ex = var9;
throw this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
}
}
위 run() 메서드는 Spring Boot 애플리케이션이 시작될 때 내부적으로 호출되는 핵심 메서드입니다.
이 메서드 내부에서는 애플리케이션의 컨텍스트를 구성하고, 필요한 설정을 초기화하며, 사용자 정의 로직을 실행하기 위한 준비 과정을 수행합니다.
이후의 설명에서는 각 단계별 역할과 작동 방식에 대해 소스 기반으로 자세히 설명합니다.
Startup 초기화Startup 객체를 생성합니다.abstract static class Startup {
private Duration timeTakenToStarted;
Startup() {
}
protected abstract long startTime();
protected abstract Long processUptime();
protected abstract String action();
...
}
사용 예시:
public ConfigurableApplicationContext run(String... args) {
Startup startup = SpringApplication.Startup.create();
...
}
run() 메서드의 가장 첫 줄에서 Startup.create() 를 통해 해당 인스턴스를 생성합니다.if (this.registerShutdownHook) {
shutdownHook.enableShutdownHookAddition();
}
registerShutdownHook 플래그가 true일 경우, 애플리케이션 종료 시 실행될 후처리 작업을 등록합니다.
이는 JVM이 종료될 때 실행되는 스레드이며, 스프링 컨텍스트가 종료되지 않은 경우 이를 안전하게 종료하고 관련 리소스를 정리합니다.
내부적으로는 AbstractApplicationContext.registerShutdownHook() 메서드가 호출되어 doClose() 를 수행하게 되며, 이 과정에서 모든 싱글톤 빈의 @PreDestroy 메서드나 DisposableBean 구현체가 호출됩니다.
createBootstrapContext()를 호출하여 초기 설정에 사용할 임시 컨텍스트를 생성합니다.
Spring Boot 애플리케이션의 초기화 과정에서 사용되는 일종의 임시 컨텍스트이며 ApplicationContext 가 완전히 준비되기 전에 초기화 작업에 필요한 리소스와 설정을 관리, 애플리케이션 시작 시 여러 설정 작업을 돕는 역할을 수행합니다.
부트스트랩 컨텍스트는 전체 컨텍스트에 공유되어야 하는 초기 컴포넌트를 저장하고, 이후 실제 컨텍스트 초기화 시 함께 넘겨집니다.
context 변수 선언 (실제 ApplicationContext 가 나중에 할당될 자리)
configureHeadlessProperty() 호출: 서버 환경(GUI 없는 환경)에서 문제 없이 실행될 수 있도록 AWT 환경 설정을 비활성화합니다.
실행 과정에서 발생하는 다양한 이벤트 처리를 위한 리스너 초기화
초기화된 리스너들에게 애플리케이션이 시작되고 있음을 알리기 위해 starting 이벤트를 발생
애플리케이션 실행 시 전달된 커맨드라인 인자를 처리하기 위해 ApplicationArguments 객체를 생성
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
prepareEnvironment() 는 다음과 같은 여러 환경 소스를 통합하여 Environment 객체를 구성합니다 (런타임시에 이루어짐)application.yml 혹은 application.properties-key=value 형식의 커맨드라인 인자ApplicationEnvironmentPreparedEvent 이벤트가 발행되며, 이를 통해 리스너들이 환경 관련 후처리를 진행할 수 있게 됩니다. Banner printedBanner = this.printBanner(environment);
ApplicationContext 생성context = this.createApplicationContext();
ApplicationContextFactory 인터페이스를 통해 생성되며 DEFAULT = new DefaultApplicationContextFactory(); 로 생성 (커스텀 팩토리 추가 가능)webApplicationType 에 맞는 컨텍스트 생성NONE: CLI 또는 백그라운드 작업 (서블릿 X)SERVLET: 서블릿 기반 웹 애플리케이션 (Tomcat, Jetty 등)REACTIVE: WebFlux 기반의 비동기/논블로킹 애플리케이션webApplicationType 은 클래스패스에 있는 의존성을 통해 자동으로 감지되며, 우선순위는 REACTIVE > SERVLET > NONE 순서입니다.Reactor Netty 와 같은 반응형 서버가 있다면 REACTIVE로 설정spring-boot-starter-web 의존성이라면 서블릿 기반 애플리케이션을 만듦 this.prepareContext(...);
ApplicationContext 에 필요한 정보(환경, 리스너, 설정값 등)를 주입하는 단계 refresh) 되지 않은 상태이며, 다음과 같은 작업이 수행됨 Bean 전달참고
환경 설정이 다시 필요한 이유 ?
부트스트랩 컨텍스트에서 이미 일부 환경 설정을 수행했지만 애플리케이션 컨텍스트의 환경을 설정하고 보강하는 작업이 추가로 필요하다.
- 전체 애플리케이션의 컨텍스트의 환경(
Environment) 을 설정하며 부트스트랩에서 설정되지 않은 추가 환경 변수, 프로파일, 설정 파일을 처리함- 리스너 설정은 왜 또 하는건가 ?
prepareContext에서는 리스너가 애플리케이션 실행 과정에서 발생하는 이벤트들을 처리하도록 세팅하는 부분listeners.starting()은 애플리케이션 시작 시의 이벤트만을 처리하는 초기 단계이며 이후 단계에서 리스너들은 다른 종류의 이벤트 (ContextRefreshedEvent등) 을 처리할 준비를 함- 리프레시 상태란 ?
- 애플리케이션 컨텍스트가 완전히 초기화되고 모든 빈이 생성되고 설정이 끝난 상태를 말함
refresh()가 완료되면 모든 빈이 준비되고 이벤트가 발행되며 애플리케이션이 실행될 준비가 완전히 완료된 상태가 됨- 이 과정이 끝나야 애플리케이션이 정상적으로 작동할 수 있음
this.refreshContext(context);
내부적으로 AbstractApplicationContext.refresh()가 호출되며 본격적인 IoC 컨테이너 초기화가 진행됨
주요 단계
@ComponentScan 및 수동 등록 빈 처리BeanPostProcessor, BeanFactoryPostProcessor 실행ContextRefreshedEvent 발행afterRefresh() : 컨텍스트가 완전히 초기화된 후 추가 작업을 수행하기 위해 호출, 기본 구현은 비어 있음Startup.started() : 실행 시간 측정을 종료StartupInfoLogger 를 통해 애플리케이션 시작 로그 출력listeners.started() : 시작 완료 이벤트 발행callRunners() : ApplicationRunner, CommandLineRunner 를 구현한 Bean들을 호출if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
ApplicationReadyEvent 를 발행하여 모든 설정 및 로딩이 완료되었음을 알립니다.