s
s
spring
Search…
Bean 생명주기와 콜백
스프링 빈은 객체생성 -> 의존관계주입의 라이프사이클을 가진다. (생성자 주입의 경우 예외)
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 후에 필요한 데이터를 사용할 수 있는 준비가 완료된다. 따라서 의존관계 주입이 모두 완료되고 난 후에 초기화가 호출되야한다.
  • 스프링은 의존관계 주입이 완료되면 스프링 빈에 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다.
  • 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다.

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

https://media.geeksforgeeks.org/wp-content/uploads/20200428011831/Bean-Life-Cycle-Process-flow3.png
  1. 1.
    스프링 컨테이너 생성
  2. 2.
    스프링 빈 생성
  3. 3.
    의존관계 주입
  4. 4.
    초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후
  5. 5.
    사용
  6. 6.
    소멸전 콜백 : 빈이 소멸되기 직전에 호출
  7. 7.
    스프링 종료
생성자는 필수 정보를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다.
초기화는 이렇게 생성된 값을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.
즉, 생성자 안에서 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분은 명확하게 나누는 것이 유지보수 관점에 좋다.

콜백 방법

인터페이스 방법

InitializingBean, DisposableBean 을 구현하여, 초기화 메서드와 소멸 메서드를 설정할 수 있다.
  • InitializingBean
    1
    public interface InitializingBean {
    2
    3
    /**
    4
    * Invoked by the containing {@code BeanFactory} after it has set all bean properties
    5
    * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
    6
    * <p>This method allows the bean instance to perform validation of its overall
    7
    * configuration and final initialization when all bean properties have been set.
    8
    * @throws Exception in the event of misconfiguration (such as failure to set an
    9
    * essential property) or if initialization fails for any other reason
    10
    */
    11
    void afterPropertiesSet() throws Exception;
    12
    13
    }
    Copied!
  • DisposableBean
    1
    public interface DisposableBean {
    2
    3
    /**
    4
    * Invoked by the containing {@code BeanFactory} on destruction of a bean.
    5
    * @throws Exception in case of shutdown errors. Exceptions will get logged
    6
    * but not rethrown to allow other beans to release their resources as well.
    7
    */
    8
    void destroy() throws Exception;
    9
    10
    }
    Copied!
  • 예제 클래스
    1
    package dh0023.springcore.lifecycle;
    2
    3
    import org.springframework.beans.factory.DisposableBean;
    4
    import org.springframework.beans.factory.InitializingBean;
    5
    6
    /**
    7
    * 테스트를 위한 가짜 NetworkClient
    8
    */
    9
    public class NetworkClient implements InitializingBean, DisposableBean {
    10
    11
    private String url;
    12
    13
    public NetworkClient() {
    14
    System.out.println("생성자 호출, url = " + url);
    15
    }
    16
    17
    public void setUrl(String url) {
    18
    this.url = url;
    19
    }
    20
    21
    // start service
    22
    public void connect() {
    23
    System.out.println("connect: " + url);
    24
    }
    25
    26
    public void call(String message) {
    27
    System.out.println("call: " + url + " message = " + message);
    28
    }
    29
    30
    public void disconnect() {
    31
    System.out.println("close: " + url);
    32
    }
    33
    34
    /**
    35
    * 의존관계 주입 후 호출
    36
    * @throws Exception
    37
    */
    38
    @Override
    39
    public void afterPropertiesSet() throws Exception {
    40
    System.out.println("NetworkClient.afterPropertiesSet");
    41
    connect();
    42
    call("초기화 연결 메세지");
    43
    }
    44
    45
    /**
    46
    * 소멸직후 콜백
    47
    * @throws Exception
    48
    */
    49
    @Override
    50
    public void destroy() throws Exception {
    51
    System.out.println("NetworkClient.destroy");
    52
    disconnect();
    53
    }
    54
    }
    Copied!
  • Test Code
    ```java package dh0023.springcore.lifecycle;
    import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description;
    public class BeanLifeCycleTest {
    1
    @Test
    2
    @Description("인터페이스 적용 테스트")
    3
    void lifeCycleInterfaceTest() {
    4
    ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
    5
    NetworkClient client = ac.getBean(NetworkClient.class);
    6
    ac.close();
    7
    }
    Copied!
1
@Configuration
2
static class LifeCycleConfig {
3
@Bean
4
public NetworkClient networkClient() {
5
NetworkClient networkClient = new NetworkClient();
6
networkClient.setUrl("http://spring-core.dev");
7
return networkClient;
8
}
9
}
Copied!
}
1
- 결과
Copied!
23:23:39.553 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'networkClient' 생성자 호출, url = null NetworkClient.afterPropertiesSet connect: http://spring-core.dev call: http://spring-core.dev message = 초기화 연결 메세지 23:23:39.645 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.spring[email protected]932bc4a, started on Sun May 16 23:23:38 KST 2021 NetworkClient.destroy close: http://spring-core.dev
Process finished with exit code 0
1
1. Creating shared instance of singleton bean 'networkClient'
2
2. 생성자 호출
3
3. NetworkClient.afterPropertiesSet
4
4. Closing org.spring[email protected]932bc4a
5
5. NetworkClient.destroy
6
6. 스프링 종료
7
8
다음과 같은 순서로 사이클이 수행되는 것을 확인할 수 있다.
9
10
#### 단점
11
12
- 스프링 전용 인터페이스이다. 해당 코드가 스프링 전용 인터페이스에 의존하게 된다.
13
- 초기화, 소멸 메서드의 이름을 변경할 수 없다.
14
- 외부 라이브러리에 적용할 수 없다.
15
16
이 방법은 초창기 나온 방법으로, 현재는 거의 사용하지 않는다.
17
18
19
20
### 설정 정보 사용(메서드 등록)
21
22
`@Bean` 어노테이션에 등록 초기화, 소멸 메서드를 설정하는 방법이다.
23
24
- 예제 클래스
25
26
```java
27
/**
28
* 테스트를 위한 가짜 NetworkClient
29
*/
30
public class NetworkClientMethod {
31
32
private String url;
33
34
public NetworkClientMethod() {
35
System.out.println("생성자 호출, url = " + url);
36
37
}
38
39
public void setUrl(String url) {
40
this.url = url;
41
}
42
43
// start service
44
public void connect() {
45
System.out.println("connect: " + url);
46
}
47
48
public void call(String message) {
49
System.out.println("call: " + url + " message = " + message);
50
}
51
52
public void disconnect() {
53
System.out.println("close: " + url);
54
}
55
56
public void init() {
57
System.out.println("NetworkClientMethod.init");
58
connect();
59
call("초기화 연결 메세지");
60
61
}
62
63
public void close() {
64
System.out.println("NetworkClientMethod.close");
65
disconnect();
66
}
67
68
}
Copied!
  • 테스트 코드
    ```java package dh0023.springcore.lifecycle;
    import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description;
    public class BeanLifeCycleTest {
    1
    @Test
    2
    @Description("메서드 적용 테스트")
    3
    void lifeCycleMethodTest() {
    4
    ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
    5
    NetworkClientMethod client = ac.getBean(NetworkClientMethod.class);
    6
    ac.close();
    7
    }
    Copied!
1
@Configuration
2
static class LifeCycleConfig {
3
4
@Bean(initMethod = "init", destroyMethod = "close")
5
public NetworkClientMethod networkClientMethod() {
6
NetworkClientMethod networkClientMethod = new NetworkClientMethod();
7
networkClientMethod.setUrl("http://spring-core.dev");
8
return networkClientMethod;
9
}
10
}
Copied!
}
1
- 결과
2
3
```java
4
23:35:00.964 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'networkClientMethod'
5
생성자 호출, url = null
6
NetworkClientMethod.init
7
connect: http://spring-core.dev
8
call: http://spring-core.dev message = 초기화 연결 메세지
9
23:35:01.128 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.spring[email protected]932bc4a, started on Sun May 16 23:35:00 KST 2021
10
NetworkClientMethod.close
11
close: http://spring-core.dev
12
13
Process finished with exit code 0
Copied!
  1. 1.
    Creating shared instance of singleton bean 'networkClientMethod'
  2. 2.
    생성자 호출
  3. 3.
    초기화 콜백 : NetworkClientMethod.init
  4. 4.
    Closing org.spring[email protected]932bc4a
  5. 5.
    소멸직후 콜백 : NetworkClientMethod.close
  6. 6.
    스프링 종료

특징

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

@Bean destroyMethod

라이브러리 대부분 close, shutdown 과 같은 이름의 종료 메서드를 사용한다.
1
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
Copied!
1
public static final String INFER_METHOD = "(inferred)";
Copied!
destoryMethod() 의 default값은 (inferred) 로 등록되어 있다. 이 추론 기능은 close, shutdown 과 같은 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해주는 것이다.
따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 된다. 만약 추론기능을 사용하기 싫은 경우에는 destroyMethod="" 로 설정해주면 된다.

어노테이션 방법

  • @PostConstruct
    1
    package javax.annotation;
    2
    3
    import java.lang.annotation.*;
    4
    import static java.lang.annotation.ElementType.*;
    5
    import static java.lang.annotation.RetentionPolicy.*;
    6
    7
    /**
    8
    * The <code>PostConstruct</code> annotation is used on a method that
    9
    * needs to be executed after dependency injection is done to perform
    10
    * any initialization. This method must be invoked before the class
    11
    * is put into service. This annotation must be supported on all classes
    12
    * that support dependency injection. The method annotated with
    13
    * <code>PostConstruct</code> must be invoked even if the class does
    14
    * not request any resources to be injected. Only one
    15
    * method in a given class can be annotated with this annotation.
    16
    * The method on which the <code>PostConstruct</code> annotation is
    17
    * applied must fulfill all of the following criteria:
    18
    * <ul>
    19
    * <li>The method must not have any parameters except in the case of
    20
    * interceptors in which case it takes an <code>InvocationContext</code>
    21
    * object as defined by the Interceptors specification.</li>
    22
    * <li>The method defined on an interceptor class or superclass of an
    23
    * interceptor class must have one of the following signatures:
    24
    * <p>
    25
    * void &#060;METHOD&#062;(InvocationContext)
    26
    * <p>
    27
    * Object &#060;METHOD&#062;(InvocationContext) throws Exception
    28
    * <p>
    29
    * <i>Note: A PostConstruct interceptor method must not throw application
    30
    * exceptions, but it may be declared to throw checked exceptions including
    31
    * the java.lang.Exception if the same interceptor method interposes on
    32
    * business or timeout methods in addition to lifecycle events. If a
    33
    * PostConstruct interceptor method returns a value, it is ignored by
    34
    * the container.</i>
    35
    * </li>
    36
    * <li>The method defined on a non-interceptor class must have the
    37
    * following signature:
    38
    * <p>
    39
    * void &#060;METHOD&#062;()
    40
    * </li>
    41
    * <li>The method on which the <code>PostConstruct</code> annotation
    42
    * is applied may be public, protected, package private or private.</li>
    43
    * <li>The method must not be static except for the application client.</li>
    44
    * <li>The method should not be final.</li>
    45
    * <li>If the method throws an unchecked exception the class must not be put into
    46
    * service except in the case where the exception is handled by an
    47
    * interceptor.</li></ul>
    48
    *
    49
    * @see javax.annotation.PreDestroy
    50
    * @see javax.annotation.Resource
    51
    * @since 1.6, Common Annotations 1.0
    52
    */
    53
    @Documented
    54
    @Retention (RUNTIME)
    55
    @Target(METHOD)
    56
    public @interface PostConstruct {
    57
    }
    Copied!
  • @PreDestroy
    1
    package javax.annotation;
    2
    3
    import java.lang.annotation.*;
    4
    import static java.lang.annotation.ElementType.*;
    5
    import static java.lang.annotation.RetentionPolicy.*;
    6
    7
    /**
    8
    * The <code>PreDestroy</code> annotation is used on a method as a
    9
    * callback notification to signal that the instance is in the
    10
    * process of being removed by the container. The method annotated
    11
    * with <code>PreDestroy</code> is typically used to
    12
    * release resources that it has been holding. This annotation must be
    13
    * supported by all container-managed objects that support the use of
    14
    * the <code>PostConstruct</code> annotation except the Jakarta EE application
    15
    * client. The method on which the <code>PreDestroy</code> annotation
    16
    * is applied must fulfill all of the following criteria:
    17
    * <ul>
    18
    * <li>The method must not have any parameters except in the case of
    19
    * interceptors in which case it takes an <code>InvocationContext</code>
    20
    * object as defined by the Interceptors specification.</li>
    21
    * <li>The method defined on an interceptor class or superclass of an
    22
    * interceptor class must have one of the following signatures:
    23
    * <p>
    24
    * void &#060;METHOD&#062;(InvocationContext)
    25
    * <p>
    26
    * Object &#060;METHOD&#062;(InvocationContext) throws Exception
    27
    * <p>
    28
    * <i>Note: A PreDestroy interceptor method must not throw application
    29
    * exceptions, but it may be declared to throw checked exceptions including
    30
    * the java.lang.Exception if the same interceptor method interposes on
    31
    * business or timeout methods in addition to lifecycle events. If a
    32
    * PreDestroy interceptor method returns a value, it is ignored by
    33
    * the container.</i>
    34
    * </li>
    35
    * <li>The method defined on a non-interceptor class must have the
    36
    * following signature:
    37
    * <p>
    38
    * void &#060;METHOD&#062;()
    39
    * </li>
    40
    * <li>The method on which PreDestroy is applied may be public, protected,
    41
    * package private or private.</li>
    42
    * <li>The method must not be static.</li>
    43
    * <li>The method should not be final.</li>
    44
    * <li>If the method throws an unchecked exception it is ignored by
    45
    * the container.</li>
    46
    * </ul>
    47
    *
    48
    * @see javax.annotation.PostConstruct
    49
    * @see javax.annotation.Resource
    50
    * @since 1.6, Common Annotations 1.0
    51
    */
    52
    53
    @Documented
    54
    @Retention (RUNTIME)
    55
    @Target(METHOD)
    56
    public @interface PreDestroy {
    57
    }
    Copied!
  • 테스트 클래스
    1
    package dh0023.springcore.lifecycle;
    2
    3
    import org.springframework.beans.factory.DisposableBean;
    4
    import org.springframework.beans.factory.InitializingBean;
    5
    6
    import javax.annotation.PostConstruct;
    7
    import javax.annotation.PreDestroy;
    8
    9
    /**
    10
    * 테스트를 위한 가짜 NetworkClient
    11
    */
    12
    public class NetworkClient {
    13
    14
    private String url;
    15
    16
    public NetworkClient() {
    17
    System.out.println("생성자 호출, url = " + url);
    18
    }
    19
    20
    public void setUrl(String url) {
    21
    this.url = url;
    22
    }
    23
    24
    // start service
    25
    public void connect() {
    26
    System.out.println("connect: " + url);
    27
    }
    28
    29
    public void call(String message) {
    30
    System.out.println("call: " + url + " message = " + message);
    31
    }
    32
    33
    public void disconnect() {
    34
    System.out.println("close: " + url);
    35
    }
    36
    37
    /**
    38
    * 의존관계 주입 후 호출
    39
    * @throws Exception
    40
    */
    41
    @PostConstruct
    42
    public void init() throws Exception {
    43
    System.out.println("NetworkClient.init");
    44
    connect();
    45
    call("초기화 연결 메세지");
    46
    }
    47
    48
    /**
    49
    * 소멸직전 콜백
    50
    * @throws Exception
    51
    */
    52
    @PreDestroy
    53
    public void close() throws Exception {
    54
    System.out.println("NetworkClient.close");
    55
    disconnect();
    56
    }
    57
    }
    Copied!
  • 테스트 코드
    ```java package dh0023.springcore.lifecycle;
    import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description;
    public class BeanLifeCycleTest { @Test @Description("어노테이션 방 적용 테스트") void lifeCycleTest() { ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class); NetworkClient client = ac.getBean(NetworkClient.class); ac.close(); }
1
@Configuration
2
static class LifeCycleConfig {
3
@Bean
4
public NetworkClient networkClient() {
5
NetworkClient networkClient = new NetworkClient();
6
networkClient.setUrl("http://spring-core.dev");
7
return networkClient;
8
}
9
}
Copied!
}
1
- 결과
2
3
```java
4
23:50:43.425 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'networkClient'
5
생성자 호출, url = null
6
NetworkClient.init
7
connect: http://spring-core.dev
8
call: http://spring-core.dev message = 초기화 연결 메세지
9
23:50:43.557 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.spring[email protected]932bc4a, started on Sun May 16 23:50:42 KST 2021
10
NetworkClient.destroy
11
close: http://spring-core.dev
Copied!

특징

  • 최신 스프링에서 가장 권장하는 방법
  • 애노테이션만 붙이면 되므로 매우 편리
  • javax.annotation 패키지는 스프링 종속적인 기술이 아니라 자바 표준이다. 따라서, 스프링이 아닌 다른 컨테이너에서도 동작한다.

단점

  • 외부 라이브러리에 적용하지 못한다. 외부 라이브러리를 초기화/종료 해야하면 @Bean 의 기능을 사용해야한다.

참고