Daily Dev Q&A

application context

awake123 2026. 1. 16. 19:51

“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가 생성되는 시점

실행 시작

  1. main()
  2. SpringApplication.run(…) 호출
  3. 스프링부트가 환경을 보고 어떤 ApplicationContext를 쓸지 결정
    • 웹 앱이면 보통 ServletWebServerApplicationContext (톰캣 내장 등)

핵심: refresh()에서 컨테이너가 “살아남”

  1. ApplicationContext.refresh() 실행 (여기가 핵심 단계)
    • 설정/클래스 스캔 결과를 “빈 정의(BeanDefinition)”로 등록
    • 그 빈들을 실제 객체로 만들고(싱글톤 위주) 의존성 주입까지 완료
    • 각종 후처리기/프록시(AOP) 준비

서버/서블릿 시작

  1. 내장 톰캣/서버 시작
  2. DispatcherServlet 등록 및 초기화
  3. 요청이 들어오면 DispatcherServlet이 컨트롤러 호출

2) @Autowired가 왜 되는가 (DI 동작 원리)

전제

  • @Component / @Service / @Repository / @Configuration 같은 것들이 스캔되거나,
  • @Bean 메서드로 등록되면
    → ApplicationContext 안에 빈으로 등록됩니다.

주입이 일어나는 타이밍

  • refresh() 과정에서 빈을 생성할 때,
  • 스프링이 “이 빈이 필요로 하는 의존성”을 확인하고 찾아서 넣습니다.

어떻게 찾나 (정확히)

  • 기본은 타입(Type) 기준으로 찾음
  • 후보가 여러 개면:
    • @Primary가 우선
    • 또는 @Qualifier("빈이름")로 지정
    • 또는 주입 지점을 이름/파라미터명 등으로 매칭(상황에 따라)

3) @Transactional이 왜 되는가 (AOP + 프록시)

핵심 한 줄:

  • @Transactional은 “그 메서드 호출을 가로채서” 트랜잭션 시작/커밋/롤백을 자동으로 해주는 AOP예요.

동작 방식 (프록시)

  1. ApplicationContext가 빈을 만들 때,
  2. “이 빈에 트랜잭션 어드바이스가 적용되어야 한다”를 감지하면
  3. 원본 객체 대신 프록시 객체(대리자) 를 빈으로 등록합니다.
  4. 외부에서 그 빈의 메서드를 호출하면
    → 프록시가 먼저 실행되어 트랜잭션을 처리한 뒤
    → 실제 메서드를 호출합니다.

 

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