ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring bean LifeCycle
    카테고리 없음 2023. 4. 22. 14:40
    오늘은 Spring의 Bean 메서드 생명주기에 대해서 알아보자. Spring의 Bean 메서드 생명주기는 여러방법으로 초기화 및 제거를 할 수 있는데 그 방법에 대해서 알아보고 어떠한 생명주기를 갖는지도 알아보자.

    초기화 메서드

    초기화 메서드는 빈 Object가 생성되고 DI 작업까지 마친다음 실행되는 메서드이다. 기본적으로 Object의 초기화작업은 생성자에서 진행하지만 DI를 통해 빈이 주입된 후에 초기화할 작업이 있으면 초기화 메서드를 이용해서 초기화를 진행하면 된다.

    InitializingBean

    Spring에 기본적으로 제공해주는 InitializingBean 인터페이스를 이용해서 초기화 작업을 할 수 있다.
    @Service
    public class LifeCycleTest implements InitializingBean {
    
      @Override
      public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
      }
    }
    
    이 인터페이스의 추상메서드 afterPropertiesSet() 만 구현해주면 Spring이 초기화 시점에 알아서 호출해 준다.

    @PostConstruct

    @PostConstruct 어노테이션은 매우 간단히 사용할 수 있다. 초기화 하고 싶은 메서드 위에 PostConstruct 어노테이션만 붙어주면 Spring이 초기화 메서드를 호출해준다. PostConstruct은 JSR-250 스펙이기 때문에 JSR-250을 구현한 다른 프레임워크 혹은 라이브러리를 사용해 된다. 좀 더 Spring에 의존적이지 않는 장점이 있다.
    @Service
    public class LifeCycleTest {
    
      @PostConstruct
      public void postConstruct() {
        System.out.println("postConstruct");
      }
    }
    
    초기화할 메서드에는 파라미터가 존재하면 안된다. 만약 파라미터가 존재한다면 에러를 발생시킨다. 또 한 return 타입이 있는 메서드를 작성해도 되지만 사용되지 않는다.

    @Bean(initMethod)

    @Bean 어노테이션을 이용해서 빈으로 등록하는 거라면 @Bean 어노테이션 속성에 있는 initMethod를 이용해도 된다.
    @Bean(initMethod = "init")
    public LifeCycleTest lifeCycleTest() {
      return new LifeCycleTest();
    }
    
    public class LifeCycleTest {
      public void init() {
        System.out.println("init");
      }
    }
    
    
    위 처럼 initMethod 속성에 해당하는 메서드명을 작성해주면 된다.

    xml init-method

    만약 xml을 이용해서 빈을 등록한다면 태그에 init-method 속성에 해당하는 메서드명을 입력해주면 된다. @Bean(initMethod)와 같은 방법이다.
    <bean id="lifeCycleTest" class="me.wonwoo.LifeCycleTest" init-method="init"/>
    
    public class LifeCycleTest {
      public void init() {
        System.out.println("init");
      }
    }
    

    제거 메서드

    제거 메서드는 컨테이너가 종료 될 때 호출돼서 빈이 사용한 리소스들을 반환하거나 종료 시점에 처리해야할 작업이 있을 경우 사용하면 된다. 초기화 메서드와 동일하게 제거 메서드도 4가지 방법이 존재한다.

    DisposableBean

    InitializingBean와 동일하게 Spring에서 지원해주는 DisposableBean 인터페이스를 사용해 구현하면 된다. 이것 역시 Spring에 의존적이다.
    @Service
    public class LifeCycleTest implements DisposableBean {
    
      @Override
      public void destroy() throws Exception {
        System.out.println("destroy");
      }
    }
    
    InitializingBean 인터페이스와 마찬가지로 DisposableBean 인터페이스도 추상메서드 destroy()만 구현해주면 Spring이 알아서 호출해 준다.

    @PreDestroy

    @PreDestroy도 PostConstruct어노테이션과 마찬가지로 JSR-250 스펙에 따라 구현되어 있다. 마찬가지로 종료될 때 사용할 메서드 위에 PreDestroy 어노테이션을 작성해주면 된다.
    @Service
    public class LifeCycleTest {
    
      @PreDestroy
      public void destroy() {
        System.out.println("destroy");
      }
    }
    
    

    @Bean(destroyMethod)

    @Bean(initMethod) 과 동일하게 @Bean 어노테이션을 사용해서 빈을 등록할 때 사용하면 된다. @Bean 속성의 destroyMethod을 이용해서 메서드명을 작성해주면 된다.
    @Bean(destroyMethod = "destroy")
    public LifeCycleTest lifeCycleTest() {
      return new LifeCycleTest();
    }
    
    @Service
    public class LifeCycleTest {
    
      public void destroy() throws Exception {
        System.out.println("destroy");
      }
    }
    
    

    destroy-method

    이것 역시 xml로 설정할 경우 사용하면 된다. 사용법은 init-method과 동일하다.
    <bean id="lifeCycleTest" class="me.wonwoo.LifeCycleTest" destroy-method="destroy"/>
    
    @Service
    public class LifeCycleTest {
    
      public void destroy() {
        System.out.println("destroy");
      }
    }
    
    
    이렇게 초기화 메서드와 제거 메서드 메서드를 여러방법으로 사용할 수 있는 법을 알아봤다. 그렇다면 호출 순서는 어떻게 될까?

    호출 순서

    InitializingBean과 PostConstruct 어노테이션 @Bean(initMethod) 을 모두 사용했을 경우 호출 순서가 어떻게 되는지 살펴보자.
    public class LifeCycleTest implements InitializingBean {
    
      @Override
      public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
      }
    
      @PostConstruct
      public void postConstruct() {
        System.out.println("postConstruct");
      }
    
      public void init() {
        System.out.println("init");
      }
    }
    
    @Bean(initMethod = "init")
    public LifeCycleTest lifeCycleTest() {
      return new LifeCycleTest();
    }
    
    위와 같이 클래스를 작성했을 경우 아래와 같은 결과가 나온다.
    postConstruct
    afterPropertiesSet
    init
    
    postConstruct 메서드가 가장 먼저 실행되고 그 다음에는 InitializingBean을 구현한 afterPropertiesSet 마지막으로 init 메서드가 호출이 된다. 여기서는 해보지 않겠지만 종료메서드도 위와 같이 동일하게 실행된다. @PreDestroy -> DisposableBean -> @Bean(destroyMethod) 메서드 순이다.

    AbstractAutowireCapableBeanFactory

    위의 초기화 및 제거 메서드는 AbstractAutowireCapableBeanFactory 클래스의 initializeBean() 메서드가 호출해주고 있다. 가장 먼저 실행 되는 @PostConstruct는 BeanPostProcessor 의해 실행되고 (InitDestroyAnnotationBeanPostProcessor) 나머지 두개는 AbstractAutowireCapableBeanFactory 클래스에 있는 invokeInitMethods() 메서드에 의해 호출 된다.
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    
    try {
      invokeInitMethods(beanName, wrappedBean, mbd);
    }
    
    위의 소스는 AbstractAutowireCapableBeanFactory 클래스의 initializeBean 메서드의 일부분 이다. applyBeanPostProcessorsBeforeInitialization가 호출 되면 @PostConstruct가 실행 되는 시점이고 invokeInitMethods() 메서드가 호출 되면 InitializingBean과 @Bean(initMethod)가 실행 된다.
        else {
          ((InitializingBean) bean).afterPropertiesSet();
          }
      }
      if (mbd != null) {
        String initMethodName = mbd.getInitMethodName();
        if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
        !mbd.isExternallyManagedInitMethod(initMethodName)) {
        invokeCustomInitMethod(beanName, bean, mbd);
      }
    }
    
    invokeInitMethods() 메서드 일부분이다. afterPropertiesSet() 호출 부분이 InitializingBean의 추상메서드를 호출 하는 부분이고 invokeCustomInitMethod() 부분이 initMethod의 정의된 메서드를 호출 하는 부분이다. 이상으로 오늘 Spring의 Bean 메서드 생명주기에 대해서 알아봤다. 필자의 경우에는 제거 메서드들은 사용한 적이 드문데 비해 초기화 메서드는 자주 이용하고 있다. 그 중에 @PostConstruct 어노테이션을 가장 많이 이용하고 그 다음에는 InitializingBean도 가끔 이용하고 있다. @Bean(initMethod) 메서드는 거의 이용하지 않고 있다.

    댓글

Designed by Tistory.