[스프링 핵심 원리 - 기본편] 8. 빈 생명주기 콜백

2024. 1. 21. 03:18·인프런 Spring 강의 정리/Spring 핵심 원리 - 기본편
728x90
반응형

📌 빈 생명주기 콜백 시작

✔ 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화 종료 작업이 필요!

 

예시

  • 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정.(실제로 네트워크에 연결하는 것은 아니고 출력을 하도록 함)
  • 이 NetworkClient는 애플리케이션 시작 시점에 connect()를 호출해서 연결을 맺어두어야 하고, 애플리케이션이 종료되면 disConnect()를 호출해서 연결 끊어야 함

 

예제 코드 NetworkClient.java

🔶 test/.../hello.core/lifecycle/NetworkClient

package hello.core.lifecycle;

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);
    }
}
  • constructor -> selectNone

 

예제 코드 BeanLifeCycleTest.java

 

🔶 BeanLifeCycleTest

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close();
    }

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

 

🔶 ac.close()

  • AnnotationConfigApplicationContext로 해주거나
  • ConfigurableApplicationContext로 해주기

-> 실행했더니 결과가 좀 이상함

생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지
  • 생성자부분을 보면 url 정보 없이 connect가 호출됨
  • 객체를 생성하는 단계에는 url이 없고, 객체를 생성한 다음에 외부에서 수정자 주입을 통해서 setUrl()이 호출되어야 url이 존재하게 됨

 

라이프 사이클

  • 스프링빈은 객체생성 -> 의존관계 주입의 라이프 사이클을 가짐
    • 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료됨
    • 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 함!(완료 시점을 어떻게 아는가?)
  • 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공함
  • 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줌
    • 안전하게 종료 완료

 

 

스프링 빈의 이벤트 라이프사이클

 

✔ 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

  • 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
  • 소멸전 콜백 : 빈이 소멸되기 직전에 호출

 

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

 

💡 cf 2)
싱글톤 빈들은 스프링 컨테이너가 종료될 때 싱글톤 빈들도 함께 종료되기 때문에, 스프링 컨테이너가 종료되기 직전에 소멸전 콜백이 일어남
-> 싱글톤 처럼 컨테이너의 시작과 종료까지 생존하는 빈도 있지만, 생명 주기가 짧은 빈들도 있는데 이 빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 소멸전 콜백이 일어남

 

 

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원함

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 애노테이션 지원

 

인터페이스(InitializingBean, DisposableBean)

🔶 NetworkClient.java

package hello.core.lifecycle;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class NetworkClient implements InitializingBean, DisposableBean {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = "+url);

    }

    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);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("NetworkClient.afterPropertiesSet");
        connect();
        call("초기화 연결 메시지");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}

 

출력 결과

생성자 호출, url = null
NetworkClient.afterPropertiesSet
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:24:49.043 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.destroy
close + http://hello-spring.dev
  • 출력 결과를 보면 초기화 메서드가 주입 완료 후에 적절하게 호출된 것을 확인할 수 있음
  • 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출된 것도 확인할 수 있음

 

 

초기화, 소멸 인터페이스 단점

  • 이 인터페이스는 스프링 전용 인터페이스임
    • 해당 코드가 스프링 전용 인터페이스에 의존
  • 초기화, 소멸 메서드의 이름을 변경할 수 없음
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용 불가

 

💡 cf) 인터페이스를 사용하는 초기화, 종료 방법은 스프링 초창기에 나온 방법들이고, 지금은 거의 사용 안함

 

빈 등록 초기화, 소멸 메서드

 

✔ 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정할 수 있음

 

설정 정보를 사용하도록 변경

🔶 NetworkClient.java

package hello.core.lifecycle;


public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = "+url);

    }

    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);
    }

    public void init() {
        System.out.println("NetworkClient.afterPropertiesSet");
        connect();
        call("초기화 연결 메시지");
    }
    public void close()  {
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}

 

설정 정보에 초기화 소멸 메서드 지정

🔶 BeanLifeCycleTest.java

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

 

결과

생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:33:10.029 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.close
close + http://hello-spring.dev

 

✔ 설정 정보 사용 특징

  • 메서드 이름을 자유롭게 줄 수 있음
  • 스프링 빈이 스프링 코드에 의존하지 않음
  • 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있음

 

✔ 종료 메서드 추론

  • @Bean의 destroyMethod 속성에는 아주 특별한 기능이 있음
  • 라이브러리는 대부분 close, shutdown이라는 이름의 종료 메서드를 사용
  • @Bean의 destroyMethod는 기본값이 (inferred)(추론)으로 등록되어 있음
  • 이 추론 기능은 close, shutdown이라는 이름의 메서드를 자동으로 호출해줌(이름 그대로 종료 메서드를 추론해서 호출해줌)
    • 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작함
  • 추론 기능을 사용하기 싫으면 destroyMethod=""처럼 빈 공백 지정

 

애노테이션 @PostConstruct, @PreDestroy (권장)

🔶 NetworkClient.java

package hello.core.lifecycle;


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

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = "+url);

    }

    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);
    }

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

    @PreDestroy
    public void close()  {
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}
  • @Bean(initMethod = "init", destroyMethod = "close") 헤둔건 다시 @Bean으로 바꿈

 

실행 결과

생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
19:40:50.269 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing NetworkClient.close
close + http://hello-spring.dev
  • @PostConstruct, @PreDestroy이 두 애노테이션을 사용하면 가장 편리하게 초기화와 종료를 실행할 수 있음

 

@PostConstruct, @PreDestroy 애노테이션 특징

  • 최신 스프링에서 가장 권장하는 방법
  • 애노테이션 하나만 붙이면 되므로 매우 간단!!
  • 패키지를 잘 보면 javax.annotation.PostConstruct임
    • 스프링에 종속적인 기술이 아니라 JSR-250라는 자바 표준임
    • 스프링이 아닌 다른 컨테이너에서도 동작
  • 컴포넌트 스캔과 잘어울림
  • 단점 : 외부 라이브러리에는 적용하지 못함
    • 외부 라이브러리를 초기화, 종료 해야하면 @Bean 기능 사용

 

정리

 

❗@PostConstruct, @PreDestroy 애노테이션을 사용하자

  • 코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 @Bean의 inirMethod, destroyMethod 사용

 

 

 

 

출처 : <인프런 스프링 핵심원리 - 기본편_김영한>을 수강하고 참고하여 작성한 글입니다.

 

 

 


 

PREV

 

[스프링 핵심 원리 - 기본편] 7. 의존관계 자동 주입

📌 다양한 의존 관계 주입 방법 🔶 의존관계 주입의 4가지 방법 생성자 주입 수정자 주입(setter 주입) 필드 주입 일반 메서드 주입 ⚡ 생성자 주입 이름 그대로 생성자를 통해서 의존관계를 주입

nyeroni.tistory.com

NEXT

 

[스프링 핵심 원리 - 기본편] 9. 빈 스코프

📌 빈 스코프란? 스프링 빈은 스프링 컨테이너의 시작과 함께 생성돼서 스프링 컨테이너가 종료될 때 까지 유지됨 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문 ✔ 스코프 : 빈이 존

nyeroni.tistory.com

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'인프런 Spring 강의 정리 > Spring 핵심 원리 - 기본편' 카테고리의 다른 글

[스프링 핵심 원리 - 기본편] 9. 빈 스코프  (1) 2024.01.21
[스프링 핵심 원리 - 기본편] 7. 의존관계 자동 주입  (3) 2024.01.21
[스프링 핵심 원리 - 기본편] 6. 컴포넌트 스캔  (1) 2024.01.20
[스프링 핵심 원리 - 기본편] 5. 싱글톤 컨테이너  (0) 2024.01.20
[스프링 핵심 원리 - 기본편] 4. 스프링 컨테이너와 스프링 빈  (1) 2024.01.20
'인프런 Spring 강의 정리/Spring 핵심 원리 - 기본편' 카테고리의 다른 글
  • [스프링 핵심 원리 - 기본편] 9. 빈 스코프
  • [스프링 핵심 원리 - 기본편] 7. 의존관계 자동 주입
  • [스프링 핵심 원리 - 기본편] 6. 컴포넌트 스캔
  • [스프링 핵심 원리 - 기본편] 5. 싱글톤 컨테이너
예롱메롱
예롱메롱
  • 예롱메롱
    예롱이의 개발 블로그
    예롱메롱
  • 전체
    오늘
    어제
    • 전체보기 (274)
      • 프로젝트 (35)
        • Wedle (12)
        • 인스타그램 클론 코딩 (13)
        • 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 (10)
      • 인프런 Spring 강의 정리 (79)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (7)
        • Spring 핵심 원리 - 기본편 (9)
        • 모든 개발자를 위한 HTTP 웹 기본 지식 (8)
        • 자바 ORM 표준 JPA 프로그래밍 - 기본편 (11)
        • 실전! 스프링 부트와 JPA 활용1 - 웹 애플리.. (6)
        • 실전! 스프링 부트와 JPA 활용2 - API 개.. (5)
        • 실전! 스프링 데이터 JPA (7)
        • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (7)
        • 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (11)
        • 실전! Querydsl (8)
      • Cloud (3)
      • Spring (6)
        • spring boot (5)
        • 소셜로그인 (1)
      • Docker (2)
      • DevOps (0)
      • Coding Test (114)
        • Programmers (37)
        • Baekjoon (76)
      • KB It's Your Life 6기 (1)
      • CS (18)
        • 알고리즘 (13)
        • 컴퓨터 구조 (1)
        • Operating System (0)
        • Network (0)
        • Database (4)
      • git (1)
      • Language (15)
        • Java (5)
        • C++ (6)
        • Python (4)
    • GITHUB GITHUB
    • INSTAGRAM INSTAGRAM
  • hELLO· Designed By정상우.v4.10.3
예롱메롱
[스프링 핵심 원리 - 기본편] 8. 빈 생명주기 콜백
상단으로

티스토리툴바