-
Spring) Bean 의 생명주기(Bean Lifecycle)Spring 2024. 8. 20. 19:57
개요
Spring 에서 가장 핵심적인 기능인 Bean 은 실제 콩(Bean) 처럼 생명 주기를 가지고있다.
처음에 생명주기라고해서 주기적으로 생성되고 죽는건가? 싶었지만, 그 뜻이 아니라 단순하게 생성되고 마무리되기까지의 과정이다.
사실 생명주기를 왜 알아야 하는가 싶었다, 왜냐하면 Bean 은 Spring 이 실행될때 생성하고 나서 끝 아닌가? 싶기도 했고Spring 이 종료되면 메모리관리고뭐고 이제 서버가 끝나는건데 없어지는것까지 알아야하나싶었다.
하지만 생명주기를 알아야하는 이유는 이 생명주기를 알고 그 사이사이를 커스터마이징을 할 수 있고 서버가 시작하면서 Bean 설정 초기화와 종료전 수집관련해서 꼭 필요한역할이라는것을 알게되었다.
그렇기에 이번글은 Bean 의 생명주기에 대해 공부해보려고한다.
Bean
빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트
Bean은 Spring Framework에서 매우 중요한 개념 중 하나로, Spring 컨테이너에 의해 관리되는 객체를 의미합니다. Spring 애플리케이션에서는 모든 객체가 Bean으로 관리될 수 있으며, 이들 Bean은 애플리케이션의 다양한 컴포넌트들 간의 의존성을 정의하고 관리하는 역할을 합니다.
@Configuration public class AppConfig { @Bean public MyBean myBean() { return new MyBean(); } }
- Spring 컨테이너에 의해 관리됨:
- Bean은 Spring IoC(Inversion of Control) 컨테이너에 의해 생성되고, 관리됩니다. 컨테이너는 Bean의 생명주기를 관리하고, 의존성을 주입하며, 필요에 따라 Bean을 생성하거나 소멸시킵니다.
- 싱글톤 기본 스코프:
- 기본적으로 Spring Bean은 싱글톤 스코프를 가집니다. 즉, 동일한 Bean 정의에 대해 Spring 컨테이너는 단일 인스턴스만을 생성하고, 애플리케이션 내에서 공유합니다.
- 싱글톤 외에도 프로토타입(Prototype), 요청(Request), 세션(Session) 등의 다양한 스코프가 있습니다.
- 의존성 주입 (Dependency Injection):
- Spring은 의존성 주입을 통해 Bean들 간의 의존 관계를 설정합니다. 이를 통해 객체 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성을 높일 수 있습니다.
- 설정 방식의 유연성:
- Spring Bean은 XML, Java Config, 어노테이션 등 다양한 방법으로 설정할 수 있습니다.
- 예를 들어, @Component, @Service, @Repository, @Controller 등의 어노테이션을 사용하면 Spring이 자동으로 해당 클래스를 Bean으로 등록합니다.
생명주기(Bean Lifecycle)
일반적인 싱글톤 타입의 스프링 빈의 생명주기
스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료
Bean 수명 주기는 다음과 같다.
- Bean 인스턴스화:
- Spring IoC 컨테이너는 XML 파일 또는 Java 설정 파일에서 정의된 Bean을 인스턴스화합니다.
- 의존성 주입:
- Bean 정의에 따라 의존성이 주입됩니다. Spring은 필요한 속성이나 의존성을 주입하여 Bean을 완전히 구성합니다.
- setBeanName() 호출:
- Bean이 BeanNameAware 인터페이스를 구현한 경우, Spring은 setBeanName() 메서드를 호출하여 Bean의 ID를 전달합니다.
- setBeanFactory() 호출:
- Bean이 BeanFactoryAware 인터페이스를 구현한 경우, setBeanFactory() 메서드가 호출되어 Spring BeanFactory의 인스턴스가 전달됩니다.
- preProcessBeforeInitialization() 호출:
- Bean과 연관된 BeanPostProcessor가 있다면, postProcessBeforeInitialization() 메서드가 초기화 전 호출됩니다.
- 초기화 메서드 호출:
- Bean 정의에서 init-method가 지정된 경우, 이 초기화 메서드가 호출됩니다.
- postProcessAfterInitialization() 호출:
- 초기화 후에 실행할 작업이 있는 경우, BeanPostProcessor의 postProcessAfterInitialization() 메서드가 호출됩니다.
이 과정이 완료되면, Bean은 사용 가능한 상태가 되어 애플리케이션에서 사용됩니다. Spring 컨테이너가 종료될 때, Bean이 DisposableBean 인터페이스를 구현한 경우 destroy() 메서드가 호출되거나, destroy-method로 지정된 메서드가 호출되어 Bean이 소멸됩니다.
Bean 의 생명주기 콜백
이제 생명주기를 배웠으니 이 Bean 의 생명주기 중간중간에 내가 원하는 코드를 심어볼수있습니다.
크게 초기화 콜백인 @PostConstruct 와 소멸 전 콜백인 @PreDestroy 이 있습니다.
@PostConstruct
@PostConstruct는 Spring에서 빈(Bean)의 초기화 작업을 수행할 때 사용하는 애노테이션입니다. 이 애노테이션이 붙은 메서드는 해당 빈이 생성되고, 모든 의존성 주입이 완료된 후 자동으로 호출됩니다. 이를 통해 빈의 초기화 과정에서 추가적인 설정이나 초기화 작업을 수행할 수 있습니다.
특히 실무에서 주로 사용하는게 key 값입니다. 물론 @Value 를 통해 처음부터 설정해도 좋지만, 해당 Key 값이 DB 심지어 외부 URL 을통해서 얻어야한다면? 예를들어 API_KEY 같은것은 실시간으로 바뀌어야할수있어야 합니다. 이러한 코드들은 초기화 이후에 유저가 사용하기 전에 외부에서 가져와야하는데 이때 사용하는것이 @PostConstruct 입니다.
@Component public class ExternalApiConnector { private final ApiClient apiClient; private String authToken; public ExternalApiConnector(ApiClient apiClient) { this.apiClient = apiClient; } @PostConstruct public void initializeConnection() { this.authToken = apiClient.authenticate(); // 인증 토큰을 받아서 이후 요청에 사용 } public void callExternalService() { apiClient.callService(authToken); } }
이렇게 사용하면 Spring boot 의 Bean 이 생성된 후 초기화 콜백을 실행되며, 해당 Bean 을 사용하기 전에 authToken 에 대해서 값이 세팅되어있을것입니다.
@PreDestroy
@PreDestroy는 Spring에서 빈(Bean)이 소멸되기 직전에 호출되는 메서드를 지정하는 애노테이션입니다. 주로 리소스를 정리하거나, 연결을 종료하거나, 애플리케이션 종료 전에 꼭 처리해야 하는 작업을 수행할 때 사용됩니다. @PreDestroy는 @PostConstruct와 반대로, 빈이 컨테이너에서 제거되기 전에 마지막으로 실행되어야 하는 작업을 정의합니다
아까와는 다르게 서버에서 대상에게 마지막에 전달해야하거나 저장할게 있다면 어떻게 해야할까요,
예를들어 애플리케이션이 종료되기 전에 캐시에 저장된 데이터를 파일이나 데이터베이스에 저장하는 등의 작업을 수행할 수 있습니다.
@Component public class CacheService { private Map<String, String> cache = new HashMap<>(); public void putInCache(String key, String value) { cache.put(key, value); } public String getFromCache(String key) { return cache.get(key); } @PreDestroy public void saveCacheToDatabase() { // 캐시 데이터를 데이터베이스에 저장하는 로직 System.out.println("Saving cache data to database..."); // 실제 데이터베이스 저장 로직 } }
Spring이 자동으로 호출하는 이유
Spring 컨테이너는 애플리케이션의 실행 중에 빈을 관리하며, 각 빈의 생명주기를 제어합니다. 이 생명주기 동안 다양한 단계에서 특정 작업을 자동으로 수행할 수 있는 메커니즘을 제공하는데, @PostConstruct와 @PreDestroy가 그 예입니다.
빈의 생명주기와 애노테이션
- 빈의 생성 (Instantiation): 빈이 생성되고, 생성자가 호출됩니다.
- 의존성 주입 (Dependency Injection): 빈에 필요한 의존성이 주입됩니다. 생성자 주입, 필드 주입, setter 메서드 주입 등이 이 단계에서 이루어집니다.
- 초기화 (Initialization): 빈의 모든 의존성이 주입된 후, 빈이 사용되기 전에 추가적인 초기화 작업이 필요할 수 있습니다. 이때, @PostConstruct 애노테이션이 붙은 메서드가 자동으로 호출됩니다. 이 메서드는 빈이 완전히 초기화된 상태에서 호출되므로, 초기화 작업을 수행하기에 적합합니다.
- 사용 (Usage): 빈이 초기화된 후, 애플리케이션에서 빈이 실제로 사용됩니다.
- 소멸 (Destruction): 애플리케이션이 종료되거나 빈이 컨테이너에서 제거될 때, 리소스를 해제하거나 정리하는 작업이 필요할 수 있습니다. 이때, @PreDestroy 애노테이션이 붙은 메서드가 자동으로 호출됩니다.
@Component public class MyService { @PostConstruct public void init() { // 이 메서드는 빈이 생성되고 모든 의존성이 주입된 후 자동으로 호출됩니다. System.out.println("MyService bean is fully initialized"); } } @Component public class MyService { @PreDestroy public void cleanup() { // 이 메서드는 빈이 소멸되기 직전에 자동으로 호출됩니다. System.out.println("MyService bean is about to be destroyed"); } }
자동 호출의 이유와 장점
- 명시적 관리: 개발자가 명시적으로 특정 작업이 필요한 시점을 지정할 수 있게 해줍니다. 예를 들어, 초기화 작업은 @PostConstruct에서, 정리 작업은 @PreDestroy에서 명확하게 수행할 수 있습니다.
- 코드의 간결성: 초기화 및 정리 작업을 생성자나 소멸자에서 관리하지 않아도 되므로, 코드가 간결해집니다. 또한, 객체가 생성될 때와 소멸될 때 수행해야 할 작업을 분리하여 관리할 수 있어 유지보수성이 향상됩니다.
- 의존성 주입 후 작업 가능: @PostConstruct 메서드는 모든 의존성 주입이 완료된 후에 호출되므로, 주입된 객체를 안전하게 사용할 수 있습니다.
결론
@PostConstruct와 @PreDestroy와 같은 애노테이션은 Spring에서 빈의 생명주기 중 특정 시점에서 자동으로 호출됩니다. 이를 통해 빈의 초기화 및 정리 작업을 명확하고 간편하게 수행할 수 있으며, 개발자가 빈의 생명주기를 쉽게 관리할 수 있도록 도와줍니다. Spring이 이러한 메서드들을 자동으로 호출하기 때문에, 개발자는 이러한 애노테이션을 사용해 필요한 로직을 정의하는 것만으로 빈의 생명주기 내에서 필요한 작업을 처리할 수 있습니다.
'Spring' 카테고리의 다른 글
Java) Checked Exception, Unchecked Exception 그리고 Error (0) 2024.09.01 Spring) @Autowired 와 생성자 주입(Constructor Injection) (0) 2024.08.27 Spring) AOP 와 프록시 패턴(Proxy Pattern) (0) 2024.08.16 Spring) DI(의존성 주입) 그리고 IoC(제어 역전) (0) 2024.08.10 Spring Boot: Java에서 Kotlin으로 (2) - TDD,BDD,DDD (0) 2024.07.28 - Spring 컨테이너에 의해 관리됨: