카테고리 없음

Bean 생명주기

JABHACK 2025. 1. 18. 16:02

생명주기와 콜백

📌 콜백 메서드Spring Bean의 생명주기에서 특정 시점에 실행되도록 설정된 메서드로 초기화 혹은 종료 시점에 필요한 작업(리소스 초기화, 정리)을 처리할 때 주로 사용된다.

 

  • Bean 생명주기
    • Spring은 Bean의 생성과 관리, 소멸까지 자동으로 처리한다.
      • 개발자가 직접 객체의 생명주기를 관리할 필요가 없다.

 

Spring Container 생성

  • 애플리케이션이 실행되면 Spring은 ApplicationContext 또는 BeanFactory와 같은 컨테이너를 생성한다.
  • 컨테이너는 @Configuration, @ComponentScan 또는 XML 파일 등의 설정을 통해 Bean 정의 정보를 읽는다.

 

Bean 인스턴스 생성

  • 컨테이너는 필요한 Bean의 인스턴스를 생성합니다.
  • 일반적으로 기본 생성자가 호출되어 객체가 생성되며, 이 과정에서 싱글톤 Bean은 애플리케이션이 시작할 때 미리 생성된다.
  • 프로토타입 Bean은 요청 시점에 생성된다.

 

의존성 주입 (Dependency Injection, DI)

  • 생성된 Bean에 의존하는 다른 Bean들이 있을 경우, Spring은 지정된 의존성 주입을 수행한다.
  • 생성자 주입, setter 주입, 또는 필드 주입 방식이 사용될 수 있지만 주로 생성자 주입을 사용한다.

 

 

초기화 콜백

  • @PostConstruct 또는 InitializingBean의 afterPropertiesSet() 메서드 호출
  • 주로 데이터베이스 연결, 리소스 준비, 설정 작업과 같은 준비를 수행한다.

 

Bean 사용

  • Bean이 초기화되고 의존성이 주입된 후에는 애플리케이션에서 자유롭게 Bean을 사용할 수 있다.
  • 컨테이너는 애플리케이션의 요청에 따라 필요한 Bean을 제공하고, Bean들은 비즈니스 로직을 수행한다.

 

소멸전 콜백

  • @PreDestroy 또는 DisposableBean의 destroy() 메서드 호출
  • Application이 종료되거나 컨테이너가 종료되기 직전에 Spring은 Bean의 소멸 메서드를 호출하여 리소스를 정리한다.
  • 주로 파일 닫기, 데이터베이스 연결 해제 등 리소스 정리를 위해 사용된다.

 

Spring 종료(Bean 소멸)

  • Bean의 소멸 메서드가 호출된 후, Spring은 Bean을 메모리에서 제거한다.
  • 이로써 해당 Bean은 더 이상 사용할 수 없으며, 모든 리소스가 정리된다.

 

생명주기 요약

  1. Spring Container 초기화
  2. Bean 인스턴스 생성
  3. 의존성 주입 (DI)
  4. 초기화 메서드 호출 (@PostConstruct, afterPropertiesSet())
    • Bean 생성 및 의존관계 설정이 완료된 후 호출
  5. Bean 사용
  6. 소멸 메서드 호출 (@PreDestroy, destroy())
    • Bean이 소멸되기 직전에 호출
  7. Bean 소멸

 

Spring의 생명주기 콜백 지원

  1. InitalizingBean, DisposableBean Interface
  2. @Bean 속성
  3. @PostConstruct, @PreDestroy Annotation

 

InitializingBean, DisposableBean

📌 Bean이 생성되고 모든 의존성이 주입된 후 InitializingBeanafterPropertiesSet() 메서드가 호출되고 초기화 작업을 수행할 수 있다. 컨테이너가 종료될 때DisposableBeandestroy() 메서드가 호출되며, 리소스 해제나 정리 작업을 처리할 수 있다.

  • 인터페이스를 사용하여 콜백 메서드를 사용하는 방법은 최근에는 잘 사용하지 않는 방법이지만 전체적인 동작 흐름을 코드로 이해하기 위해 만든 예시입니다.
public class MyBean implements InitializingBean, DisposableBean {

    private String data;

    public MyBean() {
        System.out.println("Bean 생성자 호출");
        System.out.println("data = " + data);
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    // InitializingBean 인터페이스의 초기화 메서드
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBean 초기화 - afterPropertiesSet() 호출됨");
        System.out.println("data = " + data);
    }

    // DisposableBean 인터페이스의 종료 메서드
    @Override
    public void destroy() throws Exception {
        System.out.println("MyBean 종료 - destroy() 호출됨");
        data = null;
    }

    public void doSomething() {
        System.out.println("MyBean 작업 중...");
    }

}
@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        MyBean myBean = new MyBean();
        // 의존관계 설정
        myBean.setData("Example");
        return myBean;
    }

}
public class InterfaceCallback {

    public static void main(String[] args) {
        // Spring 컨테이너 생성 및 설정 클래스 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // Bean 사용
        MyBean myBean = context.getBean(MyBean.class);
        myBean.doSomething();

				// 종료 되기전 호출
        System.out.println(myBean.getData());
        // 컨테이너 종료 (DisposableBean 호출됨)
        context.close();
        System.out.println(myBean.getData());
    }

}

출력결과

// 1. Bean 생성자 호출
Bean 생성자 호출
data = null
// 2. 의존관계 설정 setData()
// 3. 초기화
MyBean 초기화 - afterPropertiesSet() 호출됨
// 4. Bean 사용
data = Example
MyBean 작업 중...
// 5. Spring 종료 전 Data 출력
Example
// 6. destroy() -> data = null
MyBean 종료 - destroy() 호출됨
// 7. close 이후 Data 출력
null

 

  • 인터페이스 사용의 단점
    1. Spring에 의존적이다.
      • 외부 라이브러리 등의 코드에 적용할 수 없다.
    2. Method의 이름을 바꿀 수 없다.(오버라이딩)

 

@Bean 속성

📌 Spring에서 생명주기 콜백을 지원하는 방법으로 설정 정보에 초기화 메서드와 종료 메서드를 지정하는 방법을 사용할 수 있다. 이 방법을 선택하면 외부 라이브러리에도 콜백 메서드를 사용할 수 있다.

 

  • 설정 정보에 초기화 메서드, 종료 메서드 지정
    • *@Bean*(initMethod = "초기화 메서드명", destroyMethod = "소멸 메서드명")
public class MyBeanV2 {

    private String data;

    public MyBeanV2() {
        System.out.println("Bean 생성자 호출");
        System.out.println("data = " + data);
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void init() {
        System.out.println("MyBean 초기화 - init() 호출됨");
        System.out.println("data = " + data);
    }

    public void close() {
        System.out.println("MyBean 종료 - close() 호출됨");
        data = null;
    }

    public void doSomething() {
        System.out.println("MyBean 작업 중...");
    }

}
@Configuration
public class AppConfigV2 {

    @Bean(initMethod = "init", destroyMethod = "close")
    public MyBeanV2 myBeanV2() {
        MyBeanV2 myBeanV2 = new MyBeanV2();
        // 의존관계 설정
        myBeanV2.setData("Example");
        return myBeanV2;
    }

}
public class InterfaceCallbackV2 {

    public static void main(String[] args) {
        // Spring 컨테이너 생성 및 설정 클래스 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigV2.class);

        // Bean 사용
        MyBeanV2 myBeanV2 = context.getBean(MyBeanV2.class);
        myBeanV2.doSomething();

        // Bean 소멸 전 필드
        System.out.println(myBeanV2.getData());

        // 컨테이너 종료 (destroy 호출)
        context.close();

        // Bean 소멸 후 필드
        System.out.println(myBeanV2.getData());
    }

}

실행결과

 

  • 설정정보 사용
    1. Bean이 Spring 내부적으로 구현된 코드에 의존하지 않는다.
    2. 메서드 이름을 자유롭게 설정할 수 있다.
    3. 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

 

 

@PostConstruct, @PreDestroy

📌 Bean의 생명주기 동안 특정 작업을 실행하기 위해 사용하는 Annotation으로 각각 Spring Bean의 초기화와 소멸 시점에 호출되는 메서드를 지정할 때 사용된다.

 

public class MyBeanV3 {

    private String data;

    public MyBeanV3() {
        System.out.println("Bean 생성자 호출");
        System.out.println("data = " + data);
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    // 초기화 메서드
    @PostConstruct
    public void init() {
        System.out.println("MyBean 초기화 - init() 호출됨");
        System.out.println("data = " + data);
    }

    // 소멸 메서드
    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("MyBean 종료 - destroy() 호출됨");
        data = null;
    }

    public void doSomething() {
        System.out.println("MyBean 작업 중...");
    }

}
@Configuration
public class AppConfigV3 {

    @Bean
    public MyBeanV3 myBeanV3() {
        MyBeanV3 myBeanV3 = new MyBeanV3();
        // 의존관계 설정
        myBeanV3.setData("Example");
        return myBeanV3;
    }

}
public class InterfaceCallbackV3 {

    public static void main(String[] args) {
        // Spring 컨테이너 생성 및 설정 클래스 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfigV3.class);

        // Bean 사용
        MyBeanV3 myBeanV3 = context.getBean(MyBeanV3.class);
        myBeanV3.doSomething();

        // Bean 소멸 전 필드
        System.out.println(myBeanV3.getData());

        // 컨테이너 종료 (destroy 호출)
        context.close();

        // Bean 소멸 후 필드
        System.out.println(myBeanV3.getData());
    }

}

 

실행결과

 

  • 표준 Annotation 방식의 단점
    • 외부 라이브러리에 적용이 불가능하다.

 

 

Bean Scope

📌 Spring 컨테이너에서 Bean이 어떻게 생성되고 관리되는지를 정의하는 개념으로 Spring은 다양한 범위(스코프)를 제공하여 Bean의 생명주기를 설정할 수 있는데, 각 스코프는 Bean이 얼마나 오래 유지되는지, 여러 번 사용할 수 있는지 등을 결정합니다.

 

 

Bean Scope 등록 방법

// 자동 등록
@Scope("singleton") // 생략 가능(기본 값)
@Component // @Service 사용 가능
public class MemberServiceImpl implements MemberService { ... }

// 수동 등록
@Configuration
public class AppConfig {

    @Scope("singleton") // 생략 가능
    @Bean
    public MemberService memberService() {
		    return new MemberServiceImpl();
    }
    
}

 

코드 예시

더보기
public class SingletonBean {

    public SingletonBean() {
        System.out.println("Bean 생성자 호출");
    }

    // 초기화 메서드
    @PostConstruct
    public void init() {
        System.out.println("init() 호출");
    }

    // 소멸 메서드
    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("destroy() 호출");
    }

    public void doSomething() {
        System.out.println("SingletonBean 작업 중...");
    }

}
@Configuration
public class SingletonAppConfig {

    @Scope("singleton")
    @Bean
    public SingletonBean singletonBean() {
        SingletonBean singletonBean = new SingletonBean();
        return singletonBean;
    }

}
public class Singleton {

    public static void main(String[] args) {
        // Spring 컨테이너 생성 및 설정 클래스 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonAppConfig.class);

        // Bean 사용
        SingletonBean singletonBean = context.getBean(SingletonBean.class);
        SingletonBean singletonBean2 = context.getBean(SingletonBean.class);

        System.out.println("singletonBean = " + singletonBean);
        System.out.println("singletonBean2 = " + singletonBean2);

        singletonBean.doSomething();

        // 컨테이너 종료
        context.close();

    }

}

출력결과(정상 호출)

Bean 생성자 호출
init() 호출
singletonBean = com.example.springcallback.scope.SingletonBean@76ed1b7c
singletonBean2 = com.example.springcallback.scope.SingletonBean@76ed1b7c
SingletonBean 작업 중...
destroy() 호출
  • 같은 Bean 객체가 조회된다.
  • 초기화, 종료 메서드 정상 실행

 

프로토타입 스코프 사용

public class ProtoTypeBean {

    public ProtoTypeBean() {
        System.out.println("Bean 생성자 호출");
    }

    // 초기화 메서드
    @PostConstruct
    public void init() {
        System.out.println("init() 호출");
    }

    // 소멸 메서드
    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("destroy() 호출");
    }

    public void doSomething() {
        System.out.println("ProtoTypeBean 작업 중...");
    }

}
@Configuration
public class ProtoTypeAppConfig {

    @Scope("prototype")
    @Bean
    public ProtoTypeBean protoTypeBean() {
        ProtoTypeBean protoTypeBean = new ProtoTypeBean();
        return protoTypeBean;
    }

}
public class ProtoType {

    public static void main(String[] args) {
        // Spring 컨테이너 생성 및 설정 클래스 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProtoTypeAppConfig.class);

        // Bean 사용
        System.out.println("protoTypeBean 조회");
        ProtoTypeBean protoTypeBean = context.getBean(ProtoTypeBean.class);
        System.out.println("protoTypeBean2 조회");
        ProtoTypeBean protoTypeBean2 = context.getBean(ProtoTypeBean.class);

        System.out.println("singletonBean = " + protoTypeBean);
        System.out.println("singletonBean2 = " + protoTypeBean2);

        protoTypeBean.doSomething();

        // 컨테이너 종료
        context.close();

    }

}

 

출력결과

protoTypeBean 조회
Bean 생성자 호출
init() 호출
protoTypeBean2 조회
Bean 생성자 호출
init() 호출
singletonBean = com.example.springcallback.prototype.ProtoTypeBean@7c1e2a9e
singletonBean2 = com.example.springcallback.prototype.ProtoTypeBean@fa36558
ProtoTypeBean 작업 중...
  • 조회할 때 Bean이 생성되고 초기화된다.
  • close가 되어도 클라이언트가 직접 소멸하지 않아서 소멸되지 않는다.
    • @PreDestroy 실행안됨

 

  • 사용처
    1. Singleton(Default)
      • 대부분의 Sevice, Repository 등 Application 전체에서 공유되는 Bean
      • 상태를 가지면 안된다.
    2. Prototype
      • 매번 새로운 인스턴스가 필요한 경우
      • 상태를 가지는 객체(특정 설정값이 다른 임시 작업 객체)
    3. Request
      • Web Application에서 요청별로 별도의 Bean이 필요한 경우
      • 요청 데이터를 처리하는 객체