Container 부팅과 BeanDefinition

3줄 요약

  • Spring Container는 설정 정보를 읽어 BeanDefinition을 만들고, 그 정의를 바탕으로 Bean을 생성한다.
  • BeanDefinition은 클래스, 이름, Scope, 의존성, 초기화/소멸 정보 같은 메타데이터다.
  • ApplicationContext가 시작될 때 대부분의 singleton Bean은 미리 생성되며, 이 과정에서 의존성 주입과 확장 포인트가 함께 동작한다.

핵심 정리

  • 컨테이너는 먼저 설정 정보를 읽고 Bean을 바로 만드는 것이 아니라 BeanDefinition이라는 정의 정보를 구성한다.
  • BeanDefinition은 객체를 어떻게 만들고 어떤 의존성을 연결할지 알려주는 컨테이너 내부의 설계도다.
  • singleton Bean은 기본적으로 컨테이너 시작 시점에 pre-instantiation된다.
  • 이른 생성 덕분에 설정 오류와 의존성 누락을 애플리케이션 시작 시점에 빨리 발견할 수 있다.
  • Spring Boot를 쓰더라도 내부에는 ApplicationContext 부팅, BeanDefinition 등록, Bean 생성 흐름이 존재한다.

헷갈리는 지점

  • Spring이 설정을 읽자마자 바로 객체를 만든다고 생각하기 쉽다. 결과적으로 객체가 생기기 때문이다.
    • 핵심은 정의 등록 단계와 실제 인스턴스 생성 단계를 구분하는 것이다.
    • 이 구분 덕분에 BeanFactoryPostProcessor 같은 확장 포인트가 BeanDefinition을 수정할 수 있다.
  • ApplicationContext와 BeanFactory를 같은 것으로 보기 쉽다. 둘 다 Bean을 관리하기 때문이다.
    • 핵심은 ApplicationContext가 BeanFactory 기능 위에 이벤트, 리소스, 메시지 같은 애플리케이션 기능을 더 제공한다는 점이다.
    • 실무 Spring Boot 애플리케이션에서는 대부분 ApplicationContext를 기준으로 이해하면 된다.
  • 시작할 때 모든 Bean이 항상 만들어진다고 생각하기 쉽다. singleton 기본 동작이 그렇게 보이기 때문이다.
    • 핵심은 Scope와 lazy 설정에 따라 생성 시점이 달라질 수 있다는 점이다.
    • singleton은 기본적으로 미리 만들지만, lazy Bean은 실제 요청 시점까지 지연될 수 있다.

확인 질문

  • BeanDefinition은 무엇인가?
    • Bean을 어떻게 생성하고 설정하고 연결할지 담은 컨테이너 내부 메타데이터다.
  • singleton Bean이 기본적으로 시작 시점에 생성되는 이유는 무엇인가?
    • 의존성 문제와 설정 오류를 애플리케이션 시작 시점에 빨리 발견하고 준비된 객체 그래프를 제공하기 위해서다.
  • BeanDefinition 단계와 Bean 인스턴스 생성 단계를 나누는 이유는 무엇인가?
    • Bean을 만들기 전에 정의 정보를 검사하거나 수정하는 확장 포인트를 둘 수 있기 때문이다.

Bean 생성과 초기화

3줄 요약

  • Bean은 생성, 의존성 주입, aware callback, 초기화 callback, 사용, 소멸 callback의 흐름을 거친다.
  • @PostConstruct, InitializingBean, custom init method는 초기화 시점에 실행되는 대표 방식이다.
  • 초기화는 의존성 주입이 끝난 뒤 수행되어야 하는 준비 작업을 넣는 자리이지, 핵심 비즈니스 로직을 실행하는 자리가 아니다.

핵심 정리

  • Bean 생성은 생성자 호출만으로 끝나지 않는다.
  • Spring은 객체를 만든 뒤 필요한 의존성을 주입하고, 컨테이너 관련 callback을 호출하며, 초기화 메서드를 실행한다.
  • 초기화 callback은 외부 연결 준비, 캐시 warm-up, 설정 검증처럼 Bean이 사용되기 전 필요한 준비에 적합하다.
  • 초기화 callback은 의존성 주입 이후 실행되지만 AOP proxy가 적용된 최종 노출 객체가 아니라 원본 Bean에 대해 실행될 수 있으므로, 자기 자신의 @Transactional 메서드나 AOP 적용 메서드를 기대하고 호출하는 위치로 보면 위험하다.
  • 소멸 callback은 connection close, thread pool shutdown, resource release 같은 정리에 적합하다.
  • prototype Bean의 소멸은 singleton처럼 컨테이너가 끝까지 관리하지 않고 destruction callback도 자동 호출하지 않으므로 직접 정리 책임을 고려해야 한다.

헷갈리는 지점

  • 생성자와 초기화 메서드를 같은 위치로 보기 쉽다. 둘 다 객체 준비 과정에 있기 때문이다.
    • 핵심은 생성자는 객체의 필수 상태를 만드는 자리이고, 초기화 메서드는 의존성 주입 이후의 준비 작업 자리라는 점이다.
    • 외부 리소스 연결처럼 의존성이 필요하면 초기화 단계가 더 적합할 수 있다.
  • @PostConstruct를 비즈니스 로직 실행 지점으로 사용하기 쉽다. 애플리케이션 시작 시 자동 실행되기 때문이다.
    • 핵심은 초기화 callback은 Bean 준비용이라는 점이다.
    • 주문 처리, 데이터 변경, 외부 API 호출 같은 업무 흐름은 명시적인 애플리케이션 로직으로 분리하는 편이 안전하다.
  • 소멸 callback이 모든 Scope에 똑같이 적용된다고 생각하기 쉽다. Spring이 생명주기를 관리한다고 배웠기 때문이다.
    • 핵심은 singleton과 prototype의 관리 범위가 다르다는 점이다.
    • prototype은 생성과 주입까지는 컨테이너가 돕지만 이후 소멸 관리는 호출자 책임에 가까워진다.

확인 질문

  • 초기화 callback은 언제 실행되는가?
    • Bean 생성과 의존성 주입이 끝난 뒤 Bean이 사용되기 전에 실행된다.
  • @PostConstruct에는 어떤 코드가 적합한가?
    • 설정 검증, 리소스 준비, 캐시 초기화처럼 Bean 사용 전 준비 작업이 적합하다.
  • prototype Bean의 소멸 처리를 주의해야 하는 이유는 무엇인가?
    • 컨테이너가 prototype 인스턴스의 전체 생명주기와 destruction callback을 singleton처럼 끝까지 관리하지 않기 때문이다.

BeanPostProcessor와 확장 포인트

3줄 요약

  • BeanPostProcessor는 Bean 초기화 전후에 개입해 Bean 인스턴스를 감싸거나 바꿀 수 있는 확장 포인트다.
  • AOP Proxy, @Autowired, @PostConstruct 같은 많은 Spring 기능은 컨테이너 확장 포인트 위에서 동작한다.
  • BeanPostProcessor는 강력하지만 모든 Bean 생성 흐름에 영향을 줄 수 있으므로 좁고 명확한 책임으로 사용해야 한다.

핵심 정리

  • BeanFactoryPostProcessor는 Bean 인스턴스가 만들어지기 전 BeanDefinition 단계에 개입한다.
  • BeanPostProcessor는 Bean 인스턴스 생성 후 초기화 전후에 개입한다.
  • AOP 자동 프록시 생성은 BeanPostProcessor 계열이 Bean을 검사하고 필요하면 Proxy로 대체하는 방식과 연결된다.
  • @Autowired@PostConstruct 같은 애너테이션 처리도 단순 문법이 아니라 컨테이너 후처리기들의 작업 결과다.
  • 확장 포인트를 이해하면 Spring의 “마법”이 언제 BeanDefinition을 바꾸고 언제 Bean 인스턴스를 바꾸는지 구분할 수 있다.
  • BeanPostProcessor는 컨테이너 단위로 동작하며, 후처리기 자신이나 후처리기가 너무 일찍 끌어온 Bean은 모든 후처리와 자동 프록시 대상에서 제외될 수 있다.

헷갈리는 지점

  • BeanPostProcessor를 직접 자주 구현해야 하는 기능으로 오해하기 쉽다. 강력한 확장 포인트라는 설명을 듣기 때문이다.
    • 핵심은 직접 구현보다 Spring 내부 기능을 이해하는 관점이 더 자주 필요하다는 점이다.
    • 실무에서는 직접 구현보다 이미 제공되는 확장 포인트와 자동 설정을 정확히 쓰는 경우가 많다.
  • BeanFactoryPostProcessor와 BeanPostProcessor를 혼동하기 쉽다. 이름이 비슷하기 때문이다.
    • 핵심은 전자는 Bean 정의 단계, 후자는 Bean 인스턴스 단계라는 점이다.
    • 정의를 바꾸는가, 생성된 객체를 후처리하는가로 구분한다.
  • AOP Proxy가 처음부터 Target 대신 생성된다고 생각하기 쉽다. 결과적으로 주입 객체가 Proxy이기 때문이다.
    • 핵심은 원본 Bean 생성 흐름 이후 후처리 단계에서 Proxy로 감싸질 수 있다는 점이다.
    • 그래서 Bean 생명주기와 AOP는 분리된 주제가 아니라 연결된 주제다.

확인 질문

  • BeanPostProcessor는 어느 단계에 개입하는가?
    • Bean 인스턴스 생성 후 초기화 전후 단계에 개입한다.
  • BeanFactoryPostProcessor와 BeanPostProcessor의 차이는 무엇인가?
    • BeanFactoryPostProcessor는 BeanDefinition 단계에, BeanPostProcessor는 Bean 인스턴스 단계에 개입한다.
  • AOP와 BeanPostProcessor는 어떻게 연결되는가?
    • 자동 프록시 생성기가 Bean 후처리 단계에서 대상 Bean을 Proxy로 감싸 노출할 수 있다.

Scope와 Lazy 초기화

3줄 요약

  • Scope는 Bean 인스턴스가 언제 생성되고 얼마나 공유되는지 결정한다.
  • 기본 Scope는 singleton이며, 하나의 Spring Container 안에서 하나의 Bean 인스턴스를 공유한다.
  • lazy 초기화는 Bean 생성을 시작 시점이 아니라 실제 요청 시점으로 미루지만, 오류 발견도 늦출 수 있다.

핵심 정리

  • singleton은 Spring Container당 하나의 Bean 인스턴스를 의미한다.
  • prototype은 요청할 때마다 새 인스턴스를 만들지만, 생성 이후의 전체 생명주기를 컨테이너가 계속 관리하지 않고 destruction callback도 호출하지 않는다.
  • web scope에는 request, session, application, websocket 등이 있으며 웹 애플리케이션 컨텍스트에서만 자연스럽게 동작한다.
  • lazy initialization은 시작 시간을 줄일 수 있지만 Bean 생성 오류를 런타임 요청 시점으로 미룰 수 있다.
  • singleton Bean이 prototype Bean을 주입받으면 주입 시점의 prototype 인스턴스 하나만 들고 있을 수 있으므로, 매번 새 prototype이 필요하면 ObjectProvider, lookup method, scoped proxy 같은 조회 지연 전략이 필요하다.

헷갈리는 지점

  • singleton을 JVM 전체에서 하나라고 생각하기 쉽다. 싱글톤이라는 이름 때문이다.
    • 핵심은 Spring singleton은 컨테이너당 하나라는 점이다.
    • ApplicationContext가 다르면 같은 Bean 정의라도 다른 인스턴스가 생길 수 있다.
  • prototype을 쓰면 모든 생명주기 관리가 자동으로 된다고 생각하기 쉽다. Spring이 생성해 주기 때문이다.
    • 핵심은 prototype은 생성 이후 관리 범위가 제한적이라는 점이다.
    • 리소스를 잡는 prototype Bean은 정리 책임을 별도로 설계해야 한다.
  • lazy 초기화를 성능 최적화로만 보기 쉽다. 시작 시간이 줄어드는 효과가 있기 때문이다.
    • 핵심은 오류 발견 시점도 늦어진다는 점이다.
    • 핵심 Bean은 eager 생성으로 빠르게 검증하는 편이 운영 안정성에 유리할 수 있다.

확인 질문

  • Spring singleton은 무엇을 의미하는가?
    • 하나의 Spring Container 안에서 하나의 Bean 인스턴스를 공유한다는 뜻이다.
  • prototype Bean의 주의점은 무엇인가?
    • 요청마다 새 인스턴스를 만들지만 생성 이후 소멸 관리는 컨테이너가 끝까지 책임지지 않는다.
  • lazy 초기화의 trade-off는 무엇인가?
    • 시작 시간을 줄일 수 있지만 Bean 생성 오류를 실제 사용 시점까지 늦출 수 있다.