도입
스프링을 배우다 보면 Bean, @Component, DI(의존성 주입), AOP 같은 개념이 계속 등장합니다. 그런데 이 모든 기능이 실제로 어디에서 관리되고 연결되는지까지 이해하지 못하면, 개념이 서로 따로 노는 느낌을 받기 쉽습니다.
그 중심에 있는 것이 바로 ApplicationContext입니다. ApplicationContext는 단순히 “Bean을 담는 통”이 아니라, Bean 등록, 의존성 주입, 생명주기 관리, 이벤트 발행, 메시지 처리, 리소스 조회, 환경 정보 해석까지 담당하는 스프링의 실질적인 운영 주체입니다.
정의
ApplicationContext는 스프링에서 가장 대표적으로 사용하는 컨테이너 인터페이스입니다. 스프링은 객체를 직접 new로 만들어 조립하는 대신, 컨테이너가 객체를 생성하고 필요한 의존성을 연결하도록 설계되어 있습니다. 이때 그 컨테이너의 중심 역할을 수행하는 것이 ApplicationContext입니다.
기술적으로 보면 ApplicationContext는 단순한 객체 저장소가 아닙니다. 내부적으로는 BeanFactory의 기능을 포함하면서, 그 위에 이벤트 발행, 국제화 메시지 처리, 리소스 로딩, 환경(Profile/Property) 처리 같은 애플리케이션 레벨 기능을 더 제공하는 상위 인터페이스입니다.
- ApplicationContext는 스프링의 대표 컨테이너다.
- BeanFactory의 기능을 포함하면서 더 많은 애플리케이션 지원 기능을 제공한다.
- 스프링에서 Bean, DI, 생명주기, 이벤트, 환경 설정은 대부분 ApplicationContext를 중심으로 동작한다.
필요성
자바만으로 애플리케이션을 만들면 객체를 직접 생성하고, 각 객체가 어떤 객체를 필요로 하는지 개발자가 하나하나 연결해야 합니다. 객체 수가 적을 때는 괜찮지만, 서비스가 커질수록 생성 순서, 의존 관계, 초기화 시점, 설정값 주입, 테스트 대체 객체 관리가 매우 복잡해집니다.
ApplicationContext는 이런 문제를 해결하기 위해 존재합니다. 애플리케이션이 시작될 때 설정을 읽고, Bean 정의를 만들고, 필요한 객체를 생성한 뒤, 서로 연결하고, 초기화하고, 사용할 준비를 끝낸 다음, 종료 시점에는 정리까지 수행합니다.
- 객체 생성 책임을 분리할 수 있다.
- 의존성 주입을 자동화할 수 있다.
- 설정값과 환경(Profile, Property)을 통합 관리할 수 있다.
- 애플리케이션 시작/종료 생명주기를 체계적으로 제어할 수 있다.
- AOP, 이벤트, 메시지, 리소스 처리까지 연결할 수 있다.
핵심 역할
| 기능 | 설명 | 실무 의미 |
|---|---|---|
| Bean 관리 | Bean 정의 등록, 생성, 조회, 생명주기 관리 | 객체 조립을 컨테이너가 담당한다 |
| 의존성 주입 | 생성자/세터/필드 기반 의존성 연결 | 클래스 간 결합도를 낮출 수 있다 |
| 환경 정보 처리 | Property, Profile, Environment 해석 | 운영/개발/테스트 환경 분리가 쉬워진다 |
| 이벤트 발행 | ApplicationEvent 발행 및 리스너 호출 | 시작 완료, 도메인 이벤트 등을 느슨하게 연결할 수 있다 |
| 메시지 처리 | 국제화 메시지 조회(MessageSource) | 다국어 메시지 구성이 가능하다 |
| 리소스 조회 | classpath/file/url 리소스 접근 지원 | 외부 파일과 내부 리소스를 통합적으로 다룰 수 있다 |
| 후처리 확장 | BeanFactoryPostProcessor, BeanPostProcessor 적용 | AOP, 자동 주입, 프록시 생성 등이 가능해진다 |
동작 원리
ApplicationContext는 애플리케이션 시작 시 내부적으로 일정한 순서를 따라 동작합니다. 이 흐름을 이해하면 스프링이 왜 특정 오류를 내는지, 왜 어떤 Bean은 생성되고 어떤 Bean은 생성되지 않는지 훨씬 잘 보이게 됩니다.
- 설정 소스 읽기: Java Config, XML, 컴포넌트 스캔 대상 등을 해석한다.
- BeanDefinition 등록: 어떤 Bean을 어떤 방식으로 만들지 설계도 정보를 저장한다.
- BeanFactoryPostProcessor 실행: Bean 정의를 수정하거나 확장한다.
- BeanPostProcessor 등록: Bean 생성 이후 개입할 후처리기들을 준비한다.
- 싱글톤 Bean 생성: 기본 스코프인 singleton Bean을 미리 생성하는 경우가 많다.
- 의존성 주입: 필요한 다른 Bean을 연결한다.
- 초기화 콜백 수행: @PostConstruct, InitializingBean 등이 실행될 수 있다.
- 컨텍스트 준비 완료: 이벤트를 발행하고 실제 서비스 요청을 받을 준비를 마친다.
Bean과의 관계
이전에 정리한 것처럼 Bean은 스프링 컨테이너가 관리하는 객체입니다. 여기서 중요한 점은, 그 “관리하는 주체”가 바로 ApplicationContext라는 것입니다.
다시 말해 Bean은 결과물이고, ApplicationContext는 그 결과물을 등록·생성·주입·조회·소멸까지 다루는 관리자입니다. 따라서 Bean을 이해하려면 ApplicationContext를 알아야 하고, ApplicationContext를 이해하려면 Bean이 어떤 방식으로 등록되고 소비되는지도 함께 봐야 합니다.
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
위 코드에서 OrderService는 Bean이고, 그것을 보관하고 꺼내주는 주체는 ApplicationContext입니다.
@Component와의 관계
@Component는 클래스에 붙여 “이 클래스를 스캔해서 Bean 후보로 삼아도 된다”는 신호를 주는 어노테이션입니다. 하지만 어노테이션만 붙는다고 끝나는 것은 아닙니다. 실제로 컴포넌트를 찾아내고 BeanDefinition으로 등록하고 인스턴스를 만들고 관리하는 작업은 ApplicationContext가 수행합니다.
@Component
public class DiscountPolicy { }
이 클래스는 스캔 대상이 될 수는 있지만, 실제로 Bean이 되는 시점은 ApplicationContext가 시작되면서 해당 클래스 정보를 읽고 등록했을 때입니다.
BeanFactory와 차이
스프링 컨테이너를 설명할 때 종종 BeanFactory와 ApplicationContext가 함께 등장합니다. 이 둘의 관계를 정확히 이해하면 ApplicationContext가 왜 사실상 표준처럼 쓰이는지 알 수 있습니다.
| 항목 | BeanFactory | ApplicationContext |
|---|---|---|
| 역할 | 최소한의 Bean 관리 기능 | Bean 관리 + 애플리케이션 운영 기능 |
| Bean 조회 | 가능 | 가능 |
| 이벤트 발행 | 없음 | 지원 |
| 메시지 처리 | 없음 | 지원 |
| 환경/프로파일 | 제한적 | 지원 |
| 실무 사용 빈도 | 낮음 | 매우 높음 |
실무에서는 거의 항상 ApplicationContext를 사용한다고 봐도 됩니다. BeanFactory는 개념적으로는 중요하지만, 실제 애플리케이션 구성에서는 ApplicationContext가 표준에 가깝습니다.
대표 구현체
- AnnotationConfigApplicationContext: Java Config 기반 비웹 환경에서 자주 사용
- ClassPathXmlApplicationContext: XML 설정 기반 컨텍스트
- GenericApplicationContext: 범용적으로 확장 가능한 컨텍스트
- WebApplicationContext: 웹 환경에서 사용하는 컨텍스트 개념
스프링 부트에서는 개발자가 구현체를 직접 선택하는 경우가 많지 않지만, 내부적으로는 애플리케이션 타입(일반 앱 / 서블릿 웹 앱 / 리액티브 앱)에 따라 적절한 컨텍스트 구현체가 선택됩니다.
사용 예시
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
try (AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class)) {
OrderService orderService = context.getBean(OrderService.class);
}
위 방식은 학습용이나 비웹 애플리케이션에서 자주 보입니다. 반면 스프링 부트에서는 보통 직접 생성하지 않고, 부트가 ApplicationContext를 만들어 실행합니다.
스프링 부트와 관계
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(App.class, args);
}
}
여기서 SpringApplication.run(...)은 단순히 main 메서드를 실행하는 것이 아니라, 내부적으로 ApplicationContext를 만들고, 컴포넌트 스캔을 수행하고, 자동 설정을 적용하고, Bean을 생성 및 초기화하여 애플리케이션을 기동합니다.
즉, 스프링 부트의 “편리함”은 결국 ApplicationContext 초기화 과정을 자동화해주는 데서 나옵니다.
생명주기와 종료
ApplicationContext는 애플리케이션 시작 시 Bean을 준비할 뿐 아니라, 종료 시점에도 등록된 Bean의 정리 작업을 수행합니다. 그래서 데이터베이스 커넥션, 스레드 풀, 파일 핸들, 네트워크 자원처럼 종료 처리가 필요한 구성요소를 안정적으로 마무리할 수 있습니다.
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class SocketManager {
@PreDestroy
public void shutdown() {
System.out.println("자원 정리");
}
}
이처럼 ApplicationContext가 닫힐 때 @PreDestroy 같은 종료 콜백도 함께 호출될 수 있습니다.
부가기능
ApplicationContext는 애플리케이션 내부 이벤트를 발행하고 리스너가 이를 받아 처리할 수 있게 해줍니다. 기능 간 결합을 줄이면서도 확장 포인트를 만들 수 있다는 장점이 있습니다.
국제화(i18n) 메시지 조회 기능도 제공합니다. 언어별 메시지 파일을 두고 상황에 따라 다른 메시지를 보여줄 수 있습니다.
classpath:, file:, url: 형태의 리소스를 통일된 방식으로 다룰 수 있습니다.
개발, 테스트, 운영 환경에 따라 다른 설정을 적용할 때도 ApplicationContext가 관여합니다. Environment, @Profile, PropertySource 같은 기능이 여기에 연결됩니다.
실무 팁
- 비즈니스 코드에서
context.getBean(...)을 남발하면 컨테이너 의존성이 강해진다. - 가능하면 ApplicationContext를 직접 꺼내기보다 생성자 주입을 사용하는 것이 좋다.
- ApplicationContext는 인프라/프레임워크 성격의 코드에서만 제한적으로 직접 다루는 편이 낫다.
- 테스트에서도 Bean 조회보다는 필요한 Bean만 주입받는 구조가 더 깔끔하다.
디버깅
요약
- ✅ ApplicationContext는 스프링의 대표 컨테이너다.
- ✅ BeanFactory보다 더 많은 애플리케이션 기능을 제공한다.
- ✅ Bean 등록, 의존성 주입, 생명주기 관리의 중심에 있다.
- ✅ @Component는 Bean 후보를 표시하고, ApplicationContext가 실제 등록과 관리를 수행한다.
- ✅ 스프링 부트는 내부적으로 ApplicationContext를 자동 생성하고 초기화한다.