[Spring] Spring Bean의 생명주기 (2)

seongwop·2026년 4월 21일

Spring

목록 보기
17/18

스프링에서 빈은 단순히 객체 하나를 만들어 두는 개념이 아니다.
컨테이너가 객체를 생성하고, 의존성을 주입하고, 초기화하고, 종료 시점에는 정리까지 담당한다.

전체적으로는 컨테이너에서 관리를 하지만, 개발자가 빈의 생명주기에 개입할 수 있는 방법이 있는데 그 중에서 가장 대표적인 부분이 초기화 콜백소멸 콜백이다.

빈의 전체 흐름은 크게 이렇게 볼 수 있다.

  1. 컨테이너가 빈을 생성한다.
  2. 필요한 의존성을 주입한다.
  3. 초기화 콜백을 실행한다.
  4. 애플리케이션이 빈을 사용한다.
  5. 컨테이너가 종료될 때 소멸 콜백을 실행한다.

핵심은 3번과 5번이다.

즉, 빈이 준비된 직후 어떤 작업을 할 수 있는지, 그리고 빈이 사라지기 직전에 어떤 정리를 할 수 있는지를 보는 것이다.

1. 초기화 콜백

초기화 콜백은 의존성 주입이 끝난 뒤 실행되는 후처리 로직이다.

예를 들면 다음과 같은 작업이 들어갈 수 있다.

  • 설정값 검증
  • 필수 프로퍼티 체크
  • 내부 자료구조 준비
  • 캐시 초기화
  • 간단한 준비 작업

스프링은 이런 초기화 작업을 처리할 수 있는 여러 방식을 제공한다.

InitializingBean

가장 먼저 볼 수 있는 방식은 InitializingBean 인터페이스다.

public class ExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // 초기화 작업
    }
}

이 방식은 컨테이너가 필요한 프로퍼티를 모두 주입한 뒤 afterPropertiesSet()을 호출하는 구조다.

동작은 분명하다.
다만 이 방식은 클래스가 스프링 인터페이스에 직접 의존하게 된다.
그래서 빈 자체를 좀 더 일반적인 객체처럼 유지하고 싶다면 다른 방식을 선택하는 편이 자연스럽다.

@PostConstruct

초기화 콜백에서 가장 많이 보게 되는 방식은 @PostConstruct다.

import jakarta.annotation.PostConstruct;

public class ExampleBean {

    @PostConstruct
    public void init() {
        // 초기화 작업
    }
}

이 방식의 장점은 분명하다.

  • 초기화 시점이 코드에 바로 드러난다
  • 별도의 스프링 인터페이스 구현이 필요 없다
  • 빈의 역할과 초기화 로직이 한눈에 보인다

즉, 빈이 준비된 직후 실행할 작업이 있다는 사실을 코드 안에서 자연스럽게 표현할 수 있다.

일반 init 메서드 사용

초기화는 애너테이션 없이 일반 메서드로도 설정할 수 있다.

public class ExampleBean {

    public void init() {
        // 초기화 작업
    }
}
@Bean(initMethod = "init")
public ExampleBean exampleBean() {
    return new ExampleBean();
}

이 방식은 클래스 자체를 더 단순하게 유지할 수 있다는 점이 특징이다.
초기화 동작은 필요하지만, 그 사실을 클래스 안에 애너테이션으로 직접 드러내고 싶지 않을 때 사용할 수 있다.

2. 소멸 콜백

빈은 생성만큼이나 종료도 중요하다.
애플리케이션이 끝날 때는 사용하던 자원을 정리해야 하기 때문이다.

예를 들면 다음과 같은 작업이 있다.

  • 커넥션 반환
  • 소켓 종료
  • 쓰레드 정지
  • 파일 핸들 해제
  • 리스너 정리

이런 작업을 처리하는 것이 소멸 콜백이다.

DisposableBean

소멸 콜백의 한 가지 방식은 DisposableBean 인터페이스다.

public class ExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // 종료 시 정리 작업
    }
}

이 방식도 분명하게 동작한다.
다만 초기화의 경우와 마찬가지로, 클래스가 스프링 인터페이스에 직접 의존하게 된다.

@PreDestroy

소멸 단계에서 자주 쓰는 방식은 @PreDestroy다.

import jakarta.annotation.PreDestroy;

public class ExampleBean {

    @PreDestroy
    public void cleanup() {
        // 종료 시 정리 작업
    }
}

이 방식은 메서드의 의미가 명확하다.
이 메서드는 빈이 소멸되기 직전에 호출되는 정리 작업이라는 점이 바로 보인다.

일반 destroy 메서드 사용

소멸도 일반 메서드로 설정할 수 있다.

public class ExampleBean {

    public void cleanup() {
        // 종료 시 정리 작업
    }
}
@Bean(destroyMethod = "cleanup")
public ExampleBean exampleBean() {
    return new ExampleBean();
}

또한 close()shutdown() 같은 메서드는 별도 설정 없이 소멸 메서드로 추론되는 경우도 있다.
그래서 자원 정리용 메서드 이름을 이런 식으로 두면 자연스럽게 연결되기도 한다.

여러 생명주기 메커니즘을 같이 쓰면 어떻게 될까

한 빈에 대해 여러 생명주기 메커니즘을 함께 둘 수도 있다.

예를 들어 다음처럼 구성할 수 있다.

  • @PostConstruct
  • InitializingBean.afterPropertiesSet()
  • custom init 메서드

이 경우 서로 다른 메서드라면 모두 실행된다.
반대로 같은 메서드를 여러 방식으로 중복 지정하면 한 번만 실행된다.

초기화 순서는 다음과 같다.

  1. @PostConstruct
  2. InitializingBean.afterPropertiesSet()
  3. custom init 메서드

소멸 순서는 다음과 같다.

  1. @PreDestroy
  2. DisposableBean.destroy()
  3. custom destroy 메서드

즉, 스프링은 여러 방식을 동시에 허용하지만, 아무렇게나 섞이는 것이 아니라 정해진 순서대로 처리한다.

초기화 메서드에는 어떤 작업을 넣어야 할까

초기화 메서드는 빈이 막 준비된 시점에 실행된다.
그래서 이 시점에는 빈 자신의 상태를 정리하고 확인하는 작업이 잘 어울린다.

예를 들면 이런 작업이다.

  • null 체크
  • 범위 검증
  • 설정값 검증
  • 내부 컬렉션 준비
  • 간단한 캐시 세팅

반대로 너무 무거운 작업이나 복잡한 외부 호출을 여기에 몰아넣으면 초기화 단계 자체가 지나치게 무거워질 수 있다.
초기화 메서드는 어디까지나 빈이 정상적으로 사용 가능한 상태가 되기 위한 준비에 가깝게 두는 편이 자연스럽다.

초기화 콜백과 프록시

초기화 콜백을 볼 때 같이 기억해야 할 점이 하나 있다.
초기화 메서드는 빈이 준비되는 과정의 일부라는 점이다.

즉, 이 시점은 빈이 막 완성되어 가는 단계이지, 모든 부가 기능까지 완전히 덧입혀진 이후라고 단순하게 생각하면 안 된다.
그래서 초기화 메서드는 빈 자신의 준비 작업에 집중해서 이해하는 편이 깔끔하다.


스프링은 빈을 생성만 하는 것이 아니라, 초기화와 소멸 시점도 함께 관리한다.
초기화는 InitializingBean, @PostConstruct, 일반 init 메서드로 처리할 수 있고,
소멸은 DisposableBean, @PreDestroy, 일반 destroy 메서드로 처리할 수 있다.

이렇게 대표적으로 개별 빈의 생명주기에 개입하는 방법들 외에도 Aware 인터페이스를 구현하는 방법도 존재하고, BeanPostProcessor, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor 등 개별 빈을 넘어서 다른 빈들의 생명주기에 영향을 주는 확장 지점 또한 존재한다.

0개의 댓글