“application context”는 스프링(Spring)에서 애플리케이션이 동작하는 데 필요한 모든 객체(Bean)와 설정을 담고 관리하는 컨테이너
ApplicationContext의 역할
- 빈(Bean) 생성/관리: @Component, @Service, @Repository, @Configuration 등으로 등록된 객체를 만들고 주입해줌(DI)
- 설정 로딩: application.yml/properties, @Configuration 설정, 프로파일(dev, prod) 등 반영
- 라이프사이클 관리: 초기화/종료 콜백, 스코프(singleton, request 등) 관리
- 부가 기능 제공: 이벤트 발행/구독, 국제화(i18n), 리소스 로딩 등
BeanFactory와의 차이점
- BeanFactory는 “빈 관리”의 최소 기능만.
- ApplicationContext는 **BeanFactory + 각종 편의 기능(이벤트, i18n, 리소스 등)**을 포함한 “확장판”.
웹에서 자주 나오는 “컨텍스트” 2가지
- Root ApplicationContext: 서비스/레포지토리 등 “공통 빈” 중심
- Servlet(Web) ApplicationContext: 컨트롤러/웹 관련 빈 중심 (DispatcherServlet이 갖는 컨텍스트)
ApplicationContext가 생성되는 시점
실행 시작
- main()
- SpringApplication.run(…) 호출
- 스프링부트가 환경을 보고 어떤 ApplicationContext를 쓸지 결정
- 웹 앱이면 보통 ServletWebServerApplicationContext (톰캣 내장 등)
핵심: refresh()에서 컨테이너가 “살아남”
- ApplicationContext.refresh() 실행 (여기가 핵심 단계)
- 설정/클래스 스캔 결과를 “빈 정의(BeanDefinition)”로 등록
- 그 빈들을 실제 객체로 만들고(싱글톤 위주) 의존성 주입까지 완료
- 각종 후처리기/프록시(AOP) 준비
서버/서블릿 시작
- 내장 톰캣/서버 시작
- DispatcherServlet 등록 및 초기화
- 요청이 들어오면 DispatcherServlet이 컨트롤러 호출
2) @Autowired가 왜 되는가 (DI 동작 원리)
전제
- @Component / @Service / @Repository / @Configuration 같은 것들이 스캔되거나,
- @Bean 메서드로 등록되면
→ ApplicationContext 안에 빈으로 등록됩니다.
주입이 일어나는 타이밍
- refresh() 과정에서 빈을 생성할 때,
- 스프링이 “이 빈이 필요로 하는 의존성”을 확인하고 찾아서 넣습니다.
어떻게 찾나 (정확히)
- 기본은 타입(Type) 기준으로 찾음
- 후보가 여러 개면:
- @Primary가 우선
- 또는 @Qualifier("빈이름")로 지정
- 또는 주입 지점을 이름/파라미터명 등으로 매칭(상황에 따라)
3) @Transactional이 왜 되는가 (AOP + 프록시)
핵심 한 줄:
- @Transactional은 “그 메서드 호출을 가로채서” 트랜잭션 시작/커밋/롤백을 자동으로 해주는 AOP예요.
동작 방식 (프록시)
- ApplicationContext가 빈을 만들 때,
- “이 빈에 트랜잭션 어드바이스가 적용되어야 한다”를 감지하면
- 원본 객체 대신 프록시 객체(대리자) 를 빈으로 등록합니다.
- 외부에서 그 빈의 메서드를 호출하면
→ 프록시가 먼저 실행되어 트랜잭션을 처리한 뒤
→ 실제 메서드를 호출합니다.
JPA/MyBatis와 연결
- @Transactional이 붙으면 스프링은 내부에서 PlatformTransactionManager를 통해 트랜잭션을 관리
- JPA면 보통 JpaTransactionManager, MyBatis/ JDBC면 DataSourceTransactionManager 계열
- 같은 트랜잭션 안에서 JPA와 MyBatis를 섞으면 같은 DataSource를 공유하는지 같은 것들이 중요 포인트가 됩니다.
1) @Autowired가 나오는 이유: 컨테이너가 DI를 해주기 때문
- @Autowired는 “객체를 스프링이 만들어서 관리할 테니, 필요한 의존성을 찾아 넣어줘”라는 의미.
- 그 ‘찾아 넣는 주체’가 ApplicationContext예요.
- 그래서 ApplicationContext를 설명하면 보통
- “빈 등록”
- “빈 생성”
- “의존성 주입(DI)”
이 흐름이 나오고, DI의 대표 어노테이션이 @Autowired라 같이 등장합니다.
컨테이너 밖에서는?
- 내가 new Service()로 직접 만들면 스프링이 관여를 못 해서 @Autowired는 주입이 안 됨(null/에러).
2) @Transactional이 나오는 이유: 컨테이너가 AOP 프록시를 붙여주기 때문
- @Transactional은 메서드 호출을 가로채서
- 트랜잭션 시작 → 실제 메서드 실행 → 커밋/롤백
을 자동으로 해주는 기능인데,
- 트랜잭션 시작 → 실제 메서드 실행 → 커밋/롤백
- 이 “가로채기”가 AOP 프록시로 구현되고,
- 그 프록시를 붙여서 빈으로 등록해주는 게 ApplicationContext의 역할이에요.
컨테이너 밖에서는?
- new로 만든 객체는 프록시가 붙지 않아서 @Transactional이 동작하지 않음(그냥 주석처럼 됨).
ApplicationContext를 사용하지 않는다면
1) DI(@Autowired) 동작 안 함
- 내가 new로 직접 객체 만들면 스프링이 관여 못 해서
- @Autowired 주입이 안 되거나(필드 주입이면 null), 생성자에 직접 다 넣어줘야 합니다.
OrderService s = new OrderService(); // new로 만들면
// s 안의 @Autowired repo는 주입 안 됨
2) AOP 기반 기능이 거의 다 안 됨 (@Transactional 포함)
- @Transactional, @Async, @Cacheable, @Retryable 같은 건
컨테이너가 프록시를 만들어 줄 때만 제대로 동작합니다. - 컨테이너 없이 new로 만들면 프록시가 없어서 “어노테이션이 있어도 효과 없음”이 됩니다.
3) 설정/환경 기능도 못 씀
- @Value, @ConfigurationProperties, 프로파일(dev/prod), 자동 설정(autoconfig) 등
- 전부 컨테이너가 설정을 읽고 빈에 반영해주는 구조라 사용이 매우 제한됩니다.
4) 웹(MVC) 자체가 사실상 불가능/매우 불편
- 스프링 MVC는 DispatcherServlet이 컨트롤러 빈을 컨테이너에서 찾아 호출하는 구조라,
- 컨테이너가 없으면 스프링 MVC를 쓰는 의미가 크게 줄고, 사실상 “순수 서블릿/필터”로 돌아갑니다.
사용하지 못할 경우 대안
- 객체 생성: 직접 new
- 의존성 연결: 생성자에 직접 넣기(수동 DI)
- 트랜잭션: 직접 Connection 열고 commit/rollback
- 설정 값 로딩: 직접 properties 읽어서 전달
- 공통 관심사(로깅/보안/검증): 직접 코드로 감싸기(데코레이터/프록시 패턴을 수동 구현)
즉, 가능은 하지만 스프링이 제공하는 생산성이 거의 사라지고 코드량/실수/중복이 크게 늘어납니다.
현실적으로 안쓰는 경우는?
- 아주 작은 유틸/배치/CLI에서 스프링 없이 자바로만 가볍게 만들 때
- 스프링을 쓰더라도, 일부 아주 단순한 모듈은 순수 자바로 테스트할 때(단위 테스트에서 목 객체로 대체)
- 반대로 “스프링부트 웹 서비스”라면 컨테이너 없이 운영하는 건 보통 선택지가 아닙니다.
꼬리 질문
Q1. refresh()에서 “프록시(AOP)”가 준비된다고 했는데, 누가/어떻게 프록시를 붙이나요?
A. 컨테이너가 빈을 생성한 뒤 BeanPostProcessor(대표적으로 AOP 관련 후처리기)가 빈을 검사해서, @Transactional 같은 어드바이스 적용 대상이면 원본 빈 대신 프록시 빈을 등록합니다. 그래서 외부 호출이 프록시를 먼저 타면서 트랜잭션이 시작/종료됩니다.
Q2. @Autowired는 “타입 기준”이라 했는데, 같은 타입 빈이 2개면 어떻게 되나요?
A. 기본은 NoUniqueBeanDefinitionException이 납니다. 해결은 보통 3가지:
- @Primary로 기본 빈 지정
- @Qualifier("빈이름")로 명시
- 주입 지점의 이름/파라미터명과 빈 이름을 맞춰서 해결(상황에 따라)
Q3. JPA와 MyBatis를 같은 트랜잭션에서 섞을 수 있다고 했는데 조건이 뭐예요?
A. 핵심 조건은 같은 DataSource(같은 트랜잭션 매니저 관할) 를 공유해야 합니다. @Transactional이 같은 트랜잭션 경계에서 동작하려면 JPA(EntityManager)와 MyBatis(SqlSession)가 결국 같은 커넥션/트랜잭션에 묶여야 합니다. 설정이 분리되어 있으면 “같은 @Transactional인데도” 실제론 서로 다른 트랜잭션이 될 수 있어요.
Q4. ApplicationContext는 인터페이스인가요? 구현체는 뭐가 있어요?
A. ApplicationContext는 인터페이스이고, 상황별 구현체가 있어요.
- 일반 애플리케이션: AnnotationConfigApplicationContext 등
- 스프링부트 웹: 보통 ServletWebServerApplicationContext
웹/환경에 맞게 컨테이너 구현이 달라지지만 “빈 관리/DI/AOP 적용” 같은 핵심 역할은 동일합니다.
Q5. 빈 스코프가 singleton, request 등이라고 했는데 실제 차이는?
A.
- singleton: 컨테이너당 1개 인스턴스(기본값)
- prototype: 요청할 때마다 새로 생성(컨테이너가 “생성까지만” 관리, 이후 생명주기 관여 적음)
- request/session: 웹 요청/세션 단위로 1개(웹 컨텍스트에서 의미가 큼)
스코프가 다르면 메모리/동시성/상태관리 방식이 달라져서 설계에 영향이 큽니다.
Q6. 빈 생명주기(초기화/종료 콜백)는 어떤 방식으로 걸 수 있나요?
A. 대표적으로 3가지가 많아요.
- @PostConstruct, @PreDestroy
- InitializingBean, DisposableBean 인터페이스 구현
- @Bean(initMethod=..., destroyMethod=...)
스프링부트에서는 보통 @PostConstruct/@PreDestroy가 가장 깔끔하게 쓰입니다.
Q7. 빈(Bean)과 그냥 객체(new로 만든 객체)는 뭐가 달라요?
A. 빈은 스프링 컨테이너가 생성/관리하는 객체라서 DI, AOP(@Transactional 등), 설정 주입(@Value), 라이프사이클 관리 같은 혜택을 받습니다. 반면 new로 만든 객체는 스프링이 모르는 객체라 이런 기능이 거의 적용되지 않아요.
Q8. 왜 굳이 컨테이너를 쓰나요? 그냥 new로 만들어도 되잖아요.
A. new로도 가능하지만 규모가 커지면
- 의존성 연결이 복잡해지고
- 공통 기능(트랜잭션/로깅/보안)을 일일이 반복 구현해야 하고
- 테스트/교체가 어려워집니다.
컨테이너를 쓰면 객체 생성/조립/공통 기능을 표준 방식으로 처리해서 중복과 실수를 줄이고 유지보수성이 좋아집니다.
Q9. ApplicationContext는 어디서 “가져와서” 쓰나요? 내가 직접 new 해서 써요?
A. 보통 웹/부트 애플리케이션에서는 직접 꺼내서 쓰는 일이 거의 없고, 스프링부트가 실행 중에 컨텍스트를 만들고 빈들을 주입해줘요. 정말 필요할 때만
- 생성자 주입으로 ApplicationContext를 받거나
- ApplicationContextAware를 쓰거나
- 테스트에서 @SpringBootTest로 컨텍스트를 띄우는 식으로 접근합니다.
하지만 “컨테이너를 코드에서 여기저기 꺼내 쓰는 패턴”은 남용하면 의존성이 꼬여서 보통 권장되지 않습니다.
'Daily Dev Q&A' 카테고리의 다른 글
| http와 https의 차이 (0) | 2026.01.25 |
|---|---|
| JVM (0) | 2026.01.18 |
| WAS와 웹서버 (0) | 2026.01.11 |
| Garbage Collection (0) | 2026.01.07 |
| 자바 가상 머신 (0) | 2026.01.07 |