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. 빈의 생명주기 단계
빈의 생명주기는 크게 다음과 같은 단계로 구성됩니다:
- 빈 인스턴스화: 주어진 클래스의 인스턴스가 생성됩니다.
- 속성 설정: 빈에 필요한 속성이 주입됩니다. 이는 주로 의존성 주입을 통해 이루어집니다.
- 빈 이름 인식: 빈이 자신의 이름을 알 필요가 있는 경우, BeanNameAware 인터페이스를 통해 빈의 이름을 설정할 수 있습니다.
- 빈 팩토리 인식: 빈이 속한 빈 팩토리를 알아야 하는 경우, BeanFactoryAware 인터페이스를 통해 설정됩니다.
- 환경 설정 또는 커스텀 초기화: ApplicationContextAware 또는 커스텀 초기화 메서드를 통해 빈은 필요한 환경 설정을 완료할 수 있습니다.
- 초기화 콜백: 빈이 완전히 생성되고 모든 속성이 설정된 후, 초기화 메서드가 호출됩니다. @PostConstruct 애노테이션, InitializingBean 인터페이스의 afterPropertiesSet 메서드 또는 XML 설정의 init-method를 사용할 수 있습니다.
- 빈 사용: 빈이 애플리케이션에서 사용됩니다.
- 소멸 콜백: 애플리케이션이 종료되기 전에 빈이 소멸되기 전에 호출됩니다. 이는 @PreDestroy 애노테이션, DisposableBean 인터페이스의 destroy 메서드 또는 XML 설정의 destroy-method를 사용하여 처리할 수 있습니다.
2. 빈 포스트 프로세서
빈 포스트 프로세서는 빈이 초기화 과정을 거치는 동안 추가적인 처리를 할 수 있게 해줍니다. BeanPostProcessor 인터페이스를 구현하는 클래스는 postProcessBeforeInitialization 및 postProcessAfterInitialization 메서드를 제공하여 빈의 초기화 전후에 커스텀 로직을 실행할 수 있습니다.
- 자원 관리: 빈의 생명주기를 통해 초기화와 소멸 시점을 정확히 제어할 수 있습니다. 이를 통해 빈이 사용하는 외부 자원(데이터베이스 연결, 네트워크 소켓 등)을 효율적으로 할당하고 해제할 수 있어 자원 누수를 방지할 수 있습니다.
- 초기화와 설정 보장: 빈이 생성될 때 필요한 속성이나, 환경 설정을 안전하게 완료할 수 있도록 보장합니다. 예를 들어, 데이터베이스 연결을 필요로 하는 빈은 사용되기 전에 데이터베이스 연결이 완료되어 있어야 합니다. 스프링은 @PostConstruct 애노테이션을 사용하여 초기화 로직이 빈의 생성 후 즉시 실행되도록 할 수 있습니다.
- 종속성 관리: 빈들 사이의 종속성이 있을 경우, 스프링은 종속된 빈들이 올바른 순서로 생성되고 소멸되도록 관리합니다. 이는 애플리케이션이 안정적으로 실행될 수 있도록 보장합니다.
- 효율적인 메모리 관리: 빈의 생명주기를 통해, 사용되지 않는 객체를 적절한 시점에 소멸시켜 메모리를 효율적으로 관리할 수 있습니다. 이는 특히 대규모 애플리케이션 또는 긴 실행 시간을 가진 애플리케이션에서 중요합니다.
- 프로그래밍적 편의: 빈의 생명주기 메서드를 사용함으로써 개발자는 빈의 상태에 따른 복잡한 제어를 코드 내에서 명시적으로 처리할 수 있습니다. 이는 코드의 가독성과 유지보수성을 높여줍니다.