[스프링] 빈 생명주기 콜백

2025. 7. 24. 16:26·스프링

스프링(Spring)에서 애플리케이션을 개발하다 보면, 서버가 시작할 때 외부 서버와 연결하거나, 종료될 때 연결을 끊는 작업이 필요할 때가 있다.


예를 들어, 외부 API에 연결하거나, 데이터베이스와 연결하거나, 리소스를 정리해야 할 수 있다.

 

이러한 작업은 애플리케이션의 시작 시점과 종료 시점에 자동으로 실행되도록 설정하는 것이 좋다.
스프링은 이런 과정을 도와주는 생명주기(Lifecycle) 콜백 기능을 제공한다.

 

1. 빈(Bean) 동작 순서

스프링에서는 객체를 "빈(Bean)"으로 등록해서 관리한다.
이 빈은 다음과 같은 순서로 만들어지고 사용된다.

 

  • 스프링 컨테이너 생성
  • 스프링 빈 생성 (생성자 호출)
  • 의존관계 주입
  • 초기화 콜백 호출
  • 빈 사용
  • 소멸전 콜백 호출
  • 스프링 컨테이너 종료

이 중에서 초기화 작업과 종료 작업을 우리가 직접 설정할 수 있다.
초기화는 빈이 다 준비된 이후에 실행되는 작업,
종료 작업은 빈이 사라지기 직전에 실행되는 작업이다.

 

2. 잘못된 초기화의 예

다음은 네트워크에 연결하는 기능이 있다고 가정한 예제이다.
실제로 연결하지 않고, 단지 메시지를 출력해보자.

public class NetworkClient {
    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
        connect();
        call("초기화 메시지");
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void connect() {
        System.out.println("connect: " + url);
    }

    public void call(String message) {
        System.out.println("call: " + url + " message = " + message);
    }

    public void disconnect() {
        System.out.println("close: " + url);
    }
}

 

 

그리고 스프링 설정은 이렇게 되어 있다.

@Bean
public NetworkClient networkClient() {
    NetworkClient client = new NetworkClient();
    client.setUrl("http://hello-spring.dev");
    return client;
}

 

이 코드를 실행하면 다음과 같은 결과가 나온다:

생성자 호출, url = null
connect: null
call: null message = 초기화 메시지

 

왜 이런 결과가 나왔을까?

  • 생성자에서 connect()를 호출했는데
  • 그 전에 url이 아직 설정되지 않았기 때문

즉, 객체가 생성되는 시점에는 아직 필요한 값들이 준비되지 않았기 때문에 초기화 작업을 하면 안 된다.

 

3. 그럼 초기화는 언제?

스프링은 다음과 같은 방식으로 초기화 시점을 알려주거나, 종료 전에 정리할 수 있는 기능을 제공한다.

 

4. 빈 생명주기 콜백 사용하는 3가지 방법

1) @PostConstruct, @PreDestroy 애노테이션 사용

가장 추천하는 방법이다. 아주 간단하게 사용할 수 있다.

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class NetworkClient {
    private String url;

    public void setUrl(String url) {
        this.url = url;
    }

    @PostConstruct
    public void init() {
        System.out.println("초기화: 연결 시작");
        connect();
        call("초기화 메시지");
    }

    @PreDestroy
    public void close() {
        System.out.println("종료: 연결 끊기");
        disconnect();
    }

    // 기타 메서드 생략
}
  • @PostConstruct: 객체 생성 + 의존관계 주입 완료 → 이 메서드가 자동 실행됨
  • @PreDestroy: 프로그램 종료 직전 → 이 메서드가 자동 실행됨
  • 자바 표준이기 때문에 스프링이 아닌 다른 프레임워크에서도 사용 가능

단점: 외부 라이브러리에는 적용할 수 없다.

 

 

2) 스프링 인터페이스: InitializingBean, DisposableBean 구현

스프링이 초창기에 만든 방법이다.

public class NetworkClient implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        connect();
        call("초기화 메시지");
    }

    @Override
    public void destroy() throws Exception {
        disconnect();
    }
}
  • 스프링이 제공하는 인터페이스를 구현해야 한다.
  • afterPropertiesSet()은 초기화 메서드
  • destroy()는 종료 메서드

단점:

  • 메서드 이름을 마음대로 바꿀 수 없다.
  • 스프링에 종속되기 때문에 다른 프레임워크에서 사용 불가
  • 외부 라이브러리에 적용할 수 없다

 

 

3) @Bean 등록 시 초기화, 종료 메서드 지정

설정 파일에서 메서드 이름을 직접 지정하는 방식이다.

@Configuration
public class LifeCycleConfig {

    @Bean(initMethod = "init", destroyMethod = "close")
    public NetworkClient networkClient() {
        NetworkClient client = new NetworkClient();
        client.setUrl("http://hello-spring.dev");
        return client;
    }
}

 

  • initMethod: 초기화할 메서드 이름 지정
  • destroyMethod: 종료할 메서드 이름 지정
  • 메서드 이름을 자유롭게 정할 수 있음
  • 외부 라이브러리에도 적용 가능

 

5. 어떤 방법을 써야 할까?

스프링에서는 초기화와 종료 작업을 처리하는 방법이 세 가지나 있지만, 상황에 따라 적절한 방법을 선택하는 것이 중요하다.

가장 추천되는 방식은 @PostConstruct와 @PreDestroy 애노테이션을 사용하는 것이다. 애노테이션만 붙이면 자동으로 호출되기 때문에 코드도 깔끔하고 작성하기도 매우 쉽다. 그리고 자바 표준이기 때문에 스프링이 아닌 다른 환경에서도 사용할 수 있다는 장점이 있다. 다만, 외부 라이브러리에는 적용할 수 없다는 단점이 있다. 외부 라이브러리는 우리가 코드를 수정할 수 없기 때문이다.

 

오래된 방식인 InitializingBean과 DisposableBean 인터페이스는 예전부터 존재했지만, 지금은 거의 사용되지 않는다. 이유는 명확하다. 해당 방식은 스프링 전용 코드에 의존하게 되고, 메서드 이름도 고정되어 있어 유연하지 않다.

 

반면에 @Bean 설정에서 initMethod와 destroyMethod 속성을 이용하는 방식은 메서드 이름을 자유롭게 지정할 수 있고, 외부 라이브러리에도 적용할 수 있다는 장점이 있다. 코드 수정 없이 설정으로만 초기화/종료 작업을 지정할 수 있기 때문에 외부 객체나 라이브러리의 생명주기를 제어할 때 매우 유용하다.

 

정리하면, 직접 만든 클래스에는 @PostConstruct, @PreDestroy를 쓰고, 외부 라이브러리에는 @Bean(initMethod, destroyMethod)를 사용하는 것이 가장 적절하다.

 

6. 객체 생성과 초기화는 명확히 나누자

객체를 생성할 때는 가볍게 필요한 정보만 넣고 끝내야 한다.
복잡한 외부 연결이나 무거운 작업은 초기화 단계에서 처리해야 한다.

반대로 프로그램이 종료될 때, 리소스를 정리하거나 연결을 끊는 작업은 종료 콜백 단계에서 처리해야 한다.
이렇게 하면 예상치 못한 예외나 메모리 누수 없이 안정적으로 애플리케이션을 마무리할 수 있다.

 

마무리하며

스프링은 객체의 생명주기를 체계적으로 관리할 수 있도록 다양한 기능을 제공한다.


이러한 구조를 이해하고 적절히 활용하면, 더 안정적이고 유지보수하기 쉬운 애플리케이션을 만들 수 있을 것이다.

 

핵심 정리

  • 스프링 빈의 생명주기는 생성 → 의존관계 주입 → 초기화 → 사용 → 소멸 순서로 진행된다.
  • 초기화 작업은 @PostConstruct, 종료 작업은 @PreDestroy를 사용하면 가장 간단하게 처리할 수 있다.
  • 외부 라이브러리를 초기화하거나 종료할 때는 @Bean(initMethod, destroyMethod) 방식을 사용한다.
  • 생성자에서는 무거운 작업을 피하고, 꼭 필요한 값만 세팅하는 것이 유지보수와 테스트에 유리하다.

 

 

감사합니다.

 

'스프링' 카테고리의 다른 글

[스프링] JPA란?  (1) 2025.07.24
[스프링] 빈 스코프  (1) 2025.07.24
[스프링] 의존관계 자동 주입에 대하여  (0) 2025.06.26
[스프링] 싱글톤과 스프링 컨테이너에 대하여  (3) 2025.06.26
[스프링] 스프링 컨테이너와 빈(Bean)은 왜 필요한가?  (0) 2025.06.26
'스프링' 카테고리의 다른 글
  • [스프링] JPA란?
  • [스프링] 빈 스코프
  • [스프링] 의존관계 자동 주입에 대하여
  • [스프링] 싱글톤과 스프링 컨테이너에 대하여
0kingki_
0kingki_
자바 + 스프링 웹 개발
  • 0kingki_
    0kingki_
    0kingki_
  • 전체
    오늘
    어제
    • 분류 전체보기 (134)
      • 코딩 테스트 (54)
      • 자바 (21)
      • 스프링 (27)
      • 타임리프 (16)
      • 스프링 데이터 JPA (8)
      • 최적화 (2)
      • QueryDSL (4)
      • AWS (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    재귀
    spring
    불변객체
    QueryDSL
    쿼리dsl
    fetch join
    타임리프
    스프링
    thymeleaf
    최적화
    dfs
    Java
    예외처리
    SOLID
    예외 처리
    BFS
    다형성
    객체지향
    스프링 컨테이너
    쿼리
    JPA
    mvc
    컬렉션
    SpringDataJpa
    코딩테스트
    백준
    코딩 테스트
    스프링 데이터 JPA
    자바
    LocalDateTime
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
0kingki_
[스프링] 빈 생명주기 콜백
상단으로

티스토리툴바