[스프링 핵심 원리] 7

smj_716·2025년 5월 5일

스프링 완전 정복

목록 보기
10/16

스프링을 사용하다 보면 객체의 초기화와 종료 시점에 작업을 해줘야 할 때가 있다. 예를 들어 네트워크 연결을 열거나 닫아야 할 때, 또는 파일이나 데이터베이스 리소스를 정리해야 할 때이다.

💡 왜 생명주기 콜백이 필요할까?

어플리케이션을 실행할 때 꼭 필요한 작업들이 있다.

  • 서버 시작 시 외부 서버와 네트워크 연결
  • 애플리케이션 종료 시 네트워크 연결 해제

이처럼 객체 생성 -> 의존관계 주입 -> 초기화 -> 사용 -> 종료 전 정리 과정이 필요하다. 스프링은 이 과정을 자동으로 관리할 수 있는 콜백 메커니즘을 제공한다.

🔁 스프링 빈의 라이프사이클

스프링 빈은 아래와 같은 순서로 동작한다.
1. 스프링 컨테이너 생성
2. 스프링 빈 생성
3. 의존관계 주입
4. 초기화 콜백
5. 빈 사용
6. 소멸 전 콜백
7. 스프링 종료
🌟 핵심은 초기화 작업의존관계 주입 이후에 실행돼야 한다는 것이다!!!

생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면 초기화는 이렇게 생성된 값들을 활용해 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.


조금 더 이해하기 쉽도록 아래와 같은 예시를 보며 이해해보자!

[NetworkClient 클래스]

public NetworkClient() {
    System.out.println("생성자 호출, url = " + url);
    connect(); // 문제: 아직 URL이 주입되지 않음
}

객체 생성하는 단계에는 url이 없고, 객체를 생성한 다음에 외부에서 수정자 주입을 통해 setUrl()이 호출되어야 url이 존재하게 된다. 즉 url이 아직 주입되기 전이라 null 상태에서 connect()가 실행되는 것이다.
따라서 초기화는 생성자가 아니라 따로 분리해서 처리해야한다.

스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다. 즉 위에서 언급한대로 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다. 그런데 개발자가 의존관계 주입이 모두 완료된 시점을 어떻게 알 수 있을까?

✅ 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다. 또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다.


‼️ 생명주기 콜백 방법 3가지

1. 인터페이스 방식 (InitializingBean, DisposableBean)

스프링이 제공하는 인터페이스를 직접 구현해서 초기화/소멸 메서드를 정의하는 방법이다.

  • afterPropertiesSet()은 빈이 생성되고 의존관계 주입이 끝난 뒤 자동으로 호출된다.
  • destroy()는 컨테이너가 종료되기 전에 호출된다.
public class NetworkClient implements InitializingBean, DisposableBean {
    private String url;

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

    @Override
    public void afterPropertiesSet() {
        connect();
        call("초기화 메시지");
    }

    @Override
    public void destroy() {
        disconnect();
    }
}

👉 단점

  • 스프링 전용 인터페이스이기 때문에 스프링에 종속된다. 즉 스프링이 없는 환경에선 재사용이 불가능하다.
  • 메서드 이름을 바꿀 수 없다.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

요즘은 거의 사용하지 않는 방식이다!!

2. @Bean 등록 시 메서드 지정

@Bean 어노테이션에서 초기화/종료 메서드의 이름을 지정해준다.
빈 클래스 내부에 init(), close() 등의 메서드를 자유롭게 만들고 설정에서 지정하는 방식이다.

public class NetworkClient {
    public void init() {
        connect();
        call("초기화 메시지");
    }

    public void close() {
        disconnect();
    }
}

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

👉 장점

  • 메서드 이름을 자유롭게 지을 수 있다.
  • 스프링 코드에 의존하지 않는다.
  • 외부 라이브러리에도 적용 가능하다.

👉 단점

  • 설정에서 메서드 이름을 지정해줘야한다.

Tip : destroyMethod는 기본값이 "inferred"로 되어 있어서 메서드 이름이 close나 shutdown이면 자동으로 호출된다.
→ 굳이 destroyMethod = "close" 안 써도 된다.

3. @PostConstruct / @PreDestroy 애노테이션

최신 스프링에서 가장 권장하는 방법이다.
자바 표준 애노테이션 @PostConstruct, @PreDestroy를 사용하면 간단히 초기화/소멸을 구현할 수 있다.

public class NetworkClient {
    @PostConstruct
    public void init() {
        connect();
        call("초기화 메시지");
    }

    @PreDestroy
    public void close() {
        disconnect();
    }
}

👉 장점

  • 코드에 애노테이션만 붙이면 되니까 매우 간편하다.
  • 스프링 전용이 아니라 자바 표준이기 때문에 다른 컨테이너에서도 동작 가능하다.
  • 컴포넌트 스캔과 잘 작동한다.

👉 단점

  • 외부 라이브러리에는 애노테이션을 직접 붙일 수 없으니 적용이 불가하다.

결론은 ❓

  • 직접 만든 빈이면 → @PostConstruct, @PreDestroy 사용!
  • 외부 라이브러리라면 → @Bean(initMethod, destroyMethod) 사용!

0개의 댓글