개발/SPRING & JPA

[Spring] bean lifecycle

Leedo1982 2021. 5. 25. 05:39

Spring Bean 이란

Spring IoC/DI 컨테이너에 의해 생성되고 관리되는 Java Object 또는 인스턴스 이다.
ApplicationContext 또는 BeanFactory 인터페이스를 사용하여 빈라이프 사이클을 제어한다.
ApplicationContext는 BeanFactory 인터페이스를 확장한 것으로, BeanFactory 인터페이스를 통해 보다 유용한 기능을 제공합니다.

아래, 우리는 스프링에게 @Component 주석을 사용하여 Foo class bean life cycle을 제어하도록 요청했습니다.

@Component
Class Foo { //variables and methods ...}

The Spring Bean Lifecyle

Spring Bean의 creation과 destroy 의 life cycle 단계를 보자.

1. Bean Definition

bean 은 stereotype annotation 과 XML 설정을 통해 정의 된다.

stereotype annotation : @Component, @Repository, @Service 및 @Controller 주석을 배치하고 자동 컴포넌트 스캔을 활성화하면 스프링이 자동으로 container에 bean을 가져오고 종속성에 주입합니다.

2. Bean Creation and Instantiate

bean 이 생성되자마자 ApplicationContext 와 jvm memory 에 로드되고 인스턴스화 된다.

3. Population Bean properties

spring container 은 bean 의 정의를 기반으로 bean id, scope, default values 를 생성합니다.

4. Post-initialization

Spring 은 bean meta-data 세부 정보인 애플리케이션에 액세스하기 위한 Aware interface 와 맞춤형 애플리케이션별 로직을 실행하기 위한 Bean Life Cycle에 연결하는 콜백 메소드를 제공합니다

5. Ready to Serve

이제 Bean이 생성되어 모든 종속성을 주입하고 모든 Awarning 및 콜백 메서드 구현을 실행해야 합니다. Bean 을 제공할 준비가 되었다.

6. Pre-destroy

Spring은 ApplicationContext에서 Bean을 삭제하기 전에 사용자 지정 애플리케이션별 논리 및 정리를 실행하는 콜백 방법을 제공합니다.

7. Bean Destoryed

bean 은 JVM 메모리 에서 제거되거나 파괴 될것이다.
및 JVM 메모리에서 빈이 제거되거나 제거됩니다.

Hooking into the Bean Lifecycle

5가지 방법으로 spring bean life cycle 을 알아보자.

Using Spring Aware Interfaces

Foo 클래스는 BeanNameAware 인터페이스 setBeanName 을 구현합니다. 메소드는 spring container 에 의해 생성된 bean 이름을 제공합니다.

@Component
class Foo implements BeanNameAware {
    @Override
    public void setBeanName(String name) {
        System.out.println("Spring Set Bean Name Method Call");
    }
}

Foo 클래스는 BeanClassLoaderAware 인터페이스 setBeanClassLoader()을 구현합니다. 이 메소드는 애플리케이션에 알립니다. 클래스는 현재 BeanFactory 에 로드 됩니다.

@Component
class Foo2 implements BeanClassLoaderAware {
    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        System.out.println("Spring Set Bean Class Loader Method Call");
    }
}

Foo 클래스는 BeanFactoryAware 인터페이스 setBeanFactory() 을 구현합니다. 이 메소드는 bean type 와 의존성을 제공합니다.

@Component
class Foo3 implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("Spring Set Bean Factory Method Call");
    }
}

Foo 클래스는 ApplicationContextAware 인터페이스 setApplicationContextAware() 을 구현합니다. 이 방법은 애플리케이션의 이름, 환경 및 애플리케이션에 생성된 bean 등을 제공합니다.

@Component
class Foo4 implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        System.out.println("Spring Set Application Context Method Call");
    }
}
2021-05-25 19:57:03.112  INFO 19527 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.11 on ip-192-168-211-2.ap-northeast-2.compute.internal with PID 19527 (/Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo/target/classes started by idowon in /Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo)
2021-05-25 19:57:03.113  INFO 19527 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
Spring Set Bean Name Method Call
Spring Set Bean Class Loader Method Call
Spring Set Bean Factory Method Call
Spring Set Application Context Method Call
2021-05-25 19:57:03.398  INFO 19527 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.477 seconds (JVM running for 0.981)
2021-05-25 19:57:03.400  INFO 19527 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
2021-05-25 19:57:03.402  INFO 19527 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC

Process finished with exit code 0

Using Spring @PostConstruct and @PreDestroy annotations

@PostConstruct 어노테이션이 달린 Foo 클래스는 postConstructMethod() 메소드를 Aware 인터페이스 구현이 실행된 후에 호출한다.
@PreDestroy 어노테이션이 달린 Foo 클래스는 bean destory 시점에 실행됩니다.

@Component
class Foo5 {
    @PostConstruct
    public void postConstructMethod() {
        System.out.println("Spring Bean Post Construct Annotation Method ");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("Spring Bean Pre Destroy Annotation Method");
    }
}
log

2021-05-25 20:01:34.964  INFO 19577 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.11 on ip-192-168-211-2.ap-northeast-2.compute.internal with PID 19577 (/Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo/target/classes started by idowon in /Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo)
2021-05-25 20:01:34.966  INFO 19577 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
Spring Set Bean Name Method Call
Spring Set Bean Class Loader Method Call
Spring Set Bean Factory Method Call
Spring Set Application Context Method Call
Spring Bean Post Construct Annotation Method 
2021-05-25 20:01:35.265  INFO 19577 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.493 seconds (JVM running for 0.986)
2021-05-25 20:01:35.266  INFO 19577 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
2021-05-25 20:01:35.267  INFO 19577 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
Spring Bean Pre Destroy Annotation Method

Process finished with exit code 0

Using Spring InitializingBean and DisposableBean Interfaces

Foo 클래스는 InitialzeBean 인터페이스의 afterPropertiesSet() 를 구현하고 이 메소드는 Bean 의 속성을 채운후 호출됩니다.
Foo 클래스는 DisposableBean 인터페이스의 destory() 를 구현하고, 이 메소드는 bean destory 되는 시점에 호출됩니다.

@Component
class Foo6 implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() {
        System.out.println("Spring Bean Post Contract After  Properties Set Method ");
    }
    @Override
    public void destroy() {
        System.out.println("Spring Disposable Bean Destroy Method ");
    }
}
2021-05-25 20:05:33.253  INFO 19612 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.11 on ip-192-168-211-2.ap-northeast-2.compute.internal with PID 19612 (/Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo/target/classes started by idowon in /Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo)
2021-05-25 20:05:33.255  INFO 19612 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
Spring Set Bean Name Method Call
Spring Set Bean Class Loader Method Call
Spring Set Bean Factory Method Call
Spring Set Application Context Method Call
Spring Bean Post Construct Annotation Method 
Spring Bean Post Contract After  Properties Set Method 
2021-05-25 20:05:33.536  INFO 19612 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.451 seconds (JVM running for 1.009)
2021-05-25 20:05:33.537  INFO 19612 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
2021-05-25 20:05:33.538  INFO 19612 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
Spring Disposable Bean Destroy Method 
Spring Bean Pre Destroy Annotation Method

Process finished with exit code 0

Using @Bean annotation and Attributes

config 클래스에 @Bean 주석을 getFoInstance() 메서드에 달고 initMethod 내에 지정된 init 메서드는 인스턴스 생성시 호출된다.
destoryMethod 내부의 지전된 메서드는 bean 제거시 호출된다.

@Component
 class Foo7 {
    public void init() {
        System.out.println("Spring @Bean Initialization Method  Call");
    }
    public void destroy() {
        System.out.println("Spring @Bean Destroy Method");
    }
}
@Configuration
class Config {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Foo7 getFooInstance() {
        return new Foo7();
    }
}

2021-05-25 20:09:57.761  INFO 19678 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.11 on ip-192-168-211-2.ap-northeast-2.compute.internal with PID 19678 (/Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo/target/classes started by idowon in /Users/idowon/Library/Mobile Documents/com~apple~CloudDocs/projectDowon/demo)
2021-05-25 20:09:57.762  INFO 19678 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
Spring Set Bean Name Method Call
Spring Set Bean Class Loader Method Call
Spring Set Bean Factory Method Call
Spring Set Application Context Method Call
Spring Bean Post Construct Annotation Method 
Spring Bean Post Contract After  Properties Set Method 
Spring @Bean Initialization Method  Call
2021-05-25 20:09:58.083  INFO 19678 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.496 seconds (JVM running for 1.298)
2021-05-25 20:09:58.084  INFO 19678 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
2021-05-25 20:09:58.085  INFO 19678 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
Spring @Bean Destroy Method
Spring Disposable Bean Destroy Method 
Spring Bean Pre Destroy Annotation Method

Process finished with exit code 0

Using XML configuration(Bean Tag)

오늘날의 spring 애플리케이션은 기존의 xml bean 구성을 사용하지 않습니다.

Why Would I Need to Hook into the Bean Lifecycle?

  • bean 을 생성하는 동안 default value 를 할당할때
  • bean 의 생성 및 파괴의 일부로 파일과 데이터베이스 연결을 열고 닫을때
  • bean 을 파괴하는 동안 정리하거나 bean 을 생성동안 애플리케이션 metadata를 로딩할 때
  • bean 의 생성 및 파괴의 일부로 프로세스나 스레드를 시작 및 종료할때
  • bean 생성하는 동안 애플리케이션의 종속성 모듈이 가동되고 실행중인지 확인할 때

How It Works

import org.springframework.beans.factory.annotation.Value;
import java.io.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
public class MovieRental {
@Value("MovieRentalUser1")
    private String name;
    @Value("c:/MovieRental")
    private String filePath;
    private BufferedWriter bufferedWriter;
public void openMovieRentalFile() throws IOException {
        File file = new File(filePath, name + ".txt");
        bufferedWriter = new BufferedWriter(new  
        OutputStreamWriter(new FileOutputStream(file, true)));
    }
public void movieCheckout(List<String> movieList, BufferedWriter bufferedWriter) throws IOException {
        bufferedWriter.write(movieList.stream().collect(Collectors.joining(",")) + " , " + LocalDateTime.now());
    }
public void closeMovieRentalFile() throws IOException {
        bufferedWriter.close();
    }
}

public static void main(String argv[]) throws IOException {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringLifeCycleDemo.class, argv);
    List<String> movieList = Arrays.asList("Superman", "Batman", "Spiderman");
    MovieRental movieRental = applicationContext.getBean(MovieRental.class);
    movieRental.movieCheckout(movieList);
    ((BeanDefinitionRegistry) applicationContext).removeBeanDefinition("movieRental");
}

동영상 대여를 사용하여 빈을 만들고 삭제하는 동안 기본값과 파일을 열고 닫는 예를 살펴보겠다.
MovieRental 클래스는 NetFlix 에서 대여를 위해 영화를 확인하는데 사용됩니다. 모든 사용자는 파일 이름으로 지정되 파일을 값습니다. 대여할때마다 영화, 날짜 및 시간을 기록합니다.

생성자를 사용하여 빈을 생성하면 nullpointexception 이 생성됩니다. 그이유는 우리가 이름 파일경로를 할당되지 않고 파일을 열기 전에 쓰려고 하기 때문입니다.
이를 피하기 위해서는
@PostConstruct 로 열린 openMovieRentalFile()에 주석을 달면 MovieRental을 생성할 때 호출됩니다.
@PreDestroy를 사용하여 closeMovieRentalFile()에 주석을 달면 MovieRental이 파기되는 동안 호출됩니다.

@Component
public class MovieRental {

    @Value("MovieRentalUser1")
    private String name;
    @Value("c:/MovieRental")
    private String filePath;
    private BufferedWriter bufferedWriter;

    @PostConstruct
    public void openMovieRentalFile() throws IOException {
        System.out.println("openMovieRentalFile");
        File file = new File(filePath, name + ".txt");
        bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
    }

    public void movieCheckout(List<String> movieList) throws IOException {
        bufferedWriter.write(movieList.stream().collect(Collectors.joining(",")) + " , " + LocalDateTime.now());
    }

    @PreDestroy
    public void closeMovieRentalFile() throws IOException {
        bufferedWriter.close();
        System.out.println("closeMovieRentalFile");
    }

}

Conclusion

spring container 내부를 건드는 것은 항상 일장일단이 있다.
Best Practice의 관점 Spring Document 기본 설정은 JSR-250 @PostCructure 및 @PreDestroy 주석을 사용한 다음 @Bean init-method 및 destroy-method 태핑 옵션을 사용하는 것입니다.
스프링 문서에서는 특별히 사용하지 말 것을 제안하며 스프링별 Initializing Bean 및 Disposable Bean 인터페이스와 함께 묶습니다.
스프링 빈 수명 주기 콜백 방법은 been 종속성과 NullPointexception 를 방지하기 위해 생성자 기반 been setter에 대한 대체 방법입니다.

출처

'개발 > SPRING & JPA' 카테고리의 다른 글

Process Vs Thread  (0) 2021.05.30
JVM 구조  (0) 2021.05.30
[토비] 8.4 스프링의 기술  (0) 2021.05.23
[Spring-Core] Bean Overview  (0) 2021.04.19
[Spring-Core] 1.2 Container Overview  (0) 2021.04.16