본문 바로가기

SPRING/Spring

[스프링| 스프링 핵심 원리 | 기본편 | 빈 생명주기 콜백] 빈 생명주기 콜백 시작

 

public class NetworkClient { // 네트워크 클라이언트를 위한 클래스 선언

    private String url; // 네트워크 연결을 위한 URL 주소를 저장하는 변수

    public NetworkClient() { // 클래스의 생성자
        System.out.println("생성자 호출, url = " + url); // 생성자 호출 시 URL 상태를 출력
        connect(); // 네트워크 연결 메소드 호출
        call("초기화 연결 메시지"); // 초기 메시지 전송을 위한 메소드 호출
    }

    public void setUrl(String url) { // URL을 설정하는 메소드
        this.url = url; // 인스턴스 변수 url에 매개변수로 받은 url을 저장
    }

    public void connect() { // 네트워크 연결을 위한 메소드
        System.out.println("connect: " + url); // 현재 URL로 연결 중임을 출력
    }

    public void call(String message) { // 네트워크를 통해 메시지를 보내는 메소드
        System.out.println("call: " + url + " message = " + message); // 보낸 메시지와 URL을 출력
    }

    public void disconnect() { // 네트워크 연결 해제 메소드
        System.out.println("close: " + url); // 연결 해제됨을 출력
    }

}
public class BeanLifeCycleTest { // 스프링 빈의 생명주기를 테스트하는 클래스

    @Test // JUnit을 사용한 테스트 메서드임을 선언
    public void lifeCycleTest() { // 빈 생명주기를 테스트하는 메서드
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class); // 스프링 컨텍스트를 초기화하고 설정 클래스를 로드
        NetworkClient client = ac.getBean(NetworkClient.class); // 설정된 빈을 가져옴
        ac.close(); // 스프링 컨테이너를 종료
    }

    @Configuration // 클래스가 스프링 설정 정보를 포함함을 선언
    static class LifeCycleConfig { // 빈 설정 정보를 포함하는 내부 클래스
        @Bean // 메서드가 빈 객체를 생성하여 스프링 컨테이너에 등록됨을 선언
        public NetworkClient networkClient() { // NetworkClient 타입의 빈을 생성하는 메서드
            NetworkClient networkClient = new NetworkClient(); // NetworkClient 객체 생성
            networkClient.setUrl("http://hello-spring.dev"); // 생성된 객체의 URL 설정
            return networkClient; // 설정된 객체 반환
        }
    }
}

참고: 객체의 생성과 초기화를 분리하자.**
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇 게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다.
따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명 확하게 나누는 것이 유지보수 관점에서 좋다. 물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다.

참고: 싱글톤 빈들은 스프링 컨테이너가 종료될 때 싱글톤 빈들도 함께 종료되기 때문에 스프링 컨테이너가 종료 되기 직전에 소멸전 콜백이 일어난다. 뒤에서 설명하겠지만 싱글톤 처럼 컨테이너의 시작과 종료까지 생존하는 빈 도 있지만, 생명주기가 짧은 빈들도 있는데 이 빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 소멸전 콜 백이 일어난다. 자세한 내용은 스코프에서 알아보겠다.

1. 빈의 생명주기 단계

빈의 생명주기는 크게 다음과 같은 단계로 구성됩니다:

  1. 빈 인스턴스화: 주어진 클래스의 인스턴스가 생성됩니다.
  2. 속성 설정: 빈에 필요한 속성이 주입됩니다. 이는 주로 의존성 주입을 통해 이루어집니다.
  3. 빈 이름 인식: 빈이 자신의 이름을 알 필요가 있는 경우, BeanNameAware 인터페이스를 통해 빈의 이름을 설정할 수 있습니다.
  4. 빈 팩토리 인식: 빈이 속한 빈 팩토리를 알아야 하는 경우, BeanFactoryAware 인터페이스를 통해 설정됩니다.
  5. 환경 설정 또는 커스텀 초기화: ApplicationContextAware 또는 커스텀 초기화 메서드를 통해 빈은 필요한 환경 설정을 완료할 수 있습니다.
  6. 초기화 콜백: 빈이 완전히 생성되고 모든 속성이 설정된 후, 초기화 메서드가 호출됩니다. @PostConstruct 애노테이션, InitializingBean 인터페이스의 afterPropertiesSet 메서드 또는 XML 설정의 init-method를 사용할 수 있습니다.
  7. 빈 사용: 빈이 애플리케이션에서 사용됩니다.
  8. 소멸 콜백: 애플리케이션이 종료되기 전에 빈이 소멸되기 전에 호출됩니다. 이는 @PreDestroy 애노테이션, DisposableBean 인터페이스의 destroy 메서드 또는 XML 설정의 destroy-method를 사용하여 처리할 수 있습니다.

2. 빈 포스트 프로세서

빈 포스트 프로세서는 빈이 초기화 과정을 거치는 동안 추가적인 처리를 할 수 있게 해줍니다. BeanPostProcessor 인터페이스를 구현하는 클래스는 postProcessBeforeInitialization 및 postProcessAfterInitialization 메서드를 제공하여 빈의 초기화 전후에 커스텀 로직을 실행할 수 있습니다.

 

  1. 자원 관리: 빈의 생명주기를 통해 초기화와 소멸 시점을 정확히 제어할 수 있습니다. 이를 통해 빈이 사용하는 외부 자원(데이터베이스 연결, 네트워크 소켓 등)을 효율적으로 할당하고 해제할 수 있어 자원 누수를 방지할 수 있습니다.
  2. 초기화와 설정 보장: 빈이 생성될 때 필요한 속성이나, 환경 설정을 안전하게 완료할 수 있도록 보장합니다. 예를 들어, 데이터베이스 연결을 필요로 하는 빈은 사용되기 전에 데이터베이스 연결이 완료되어 있어야 합니다. 스프링은 @PostConstruct 애노테이션을 사용하여 초기화 로직이 빈의 생성 후 즉시 실행되도록 할 수 있습니다.
  3. 종속성 관리: 빈들 사이의 종속성이 있을 경우, 스프링은 종속된 빈들이 올바른 순서로 생성되고 소멸되도록 관리합니다. 이는 애플리케이션이 안정적으로 실행될 수 있도록 보장합니다.
  4. 효율적인 메모리 관리: 빈의 생명주기를 통해, 사용되지 않는 객체를 적절한 시점에 소멸시켜 메모리를 효율적으로 관리할 수 있습니다. 이는 특히 대규모 애플리케이션 또는 긴 실행 시간을 가진 애플리케이션에서 중요합니다.
  5. 프로그래밍적 편의: 빈의 생명주기 메서드를 사용함으로써 개발자는 빈의 상태에 따른 복잡한 제어를 코드 내에서 명시적으로 처리할 수 있습니다. 이는 코드의 가독성과 유지보수성을 높여줍니다.