오늘은 지금까지 잘 모르고 사용했던 Spring Bean 에 대해 공부해보고자 합니다.
[목차]
- Spring Bean 이란
- Bean 의 생명 주기와 Bean Scope
- Bean 의 사용 이유
- Spring Bean 의 권장 사용 방법
1. Spring 에서의 Bean 이란
Spring Framework 에서의 Bean 은 Spring Boot 의 핵심 기술 중 하나인 제어의 역전(IOC)와 의존성 주입(DI)와 밀첩한 관계를 가집니다.
Spring Framework는 IOC Container 를 통해 객체 주입(생성)에 대한 의존성을 코드 구현부에서 직접 가져가는게 종속성 주입(DI)을 통해 객체의 호출부에서는 어떤 객체가 생성되는지 신경 쓰지 않게하여 객체간의 결합도를 낮춰 설계할 수 있도록 해주는 기술을 제공합니다.
✔️ 이때 Spring IOC Container(org.springframework.context.ApplicationContext) 에서 관리되는 객체를 우리는 Bean 혹은 Bean 객체라고 부릅니다.
Pojo 객체를 IOC Contanier 에 Bean 객체로서 관리하도록 지정하는 방법은 @Configuration, @Baen 등의 어노테이션을 통해 런타임 시 객체를 생성해 ApplicationContext 에서 관리하도록 지정할 수 있습니다.
(참고 : https://docs.spring.io/spring-framework/reference/core/beans/java.html)
2. Bean 생명 주기와 Bean Scope
✔️ Bean Life Cycle
- Spring 실행
- Spring IOC 컨테이너 생성 (Application Context)
- Component Scan → Bean 등록
- 의존성 주입
- Bean 초기화 콜백 메서드 실행
- Spring 종료 전 소멸 콜백 메서드 실행
(Bean 콜백 메서드에 관해서는 코드를 까보지 않아 잘 모릅니다,, 김영한님 강의에 나오길래 일단 적어봤어요) - 프로세스 종료
✔️ Component Scan
- Component Scan 은 @Component 주석이 붙어있는 클래스를 찾아 Bean 객체로 등록하는 과정입니다.
- 결국 Component Scan 이란, 특정 객체를 Spring IOC Container 에서 관리하도록 객채를 생성하여 넣어줘~ 라는 과정입니다.
- 그렇다면, 외부 라이브러리를 사용하여 생성하는 객체를 Bean 으로 등록하고 싶을때는 어떻게 해야할까요?
👏 이럴 때 @Bean 어노테이션을 사용합니다.
구체적인 Bean 사용 방법에 대해서는 조금 더 아래에서 공부해보겠습니다.
✔️ Bean Scope
- 기본적으로는 Singleton 입니다. 한번 빈 객체로 등록되면 객체는 재 생성 되지않고 생성된 객체를 주입받아 사용할 수 있습니다.
- 프로토타입 : prototype scope 로 설정하면 의존성을 주입 받을 때마다 IOC Container 에서 객체를 생성합니다. 기본적으로 초기화까지 진행하고 주입된 객체에 대한 관리(할당 해제)는 관여하지 않습니다.
- 그 외에 request, session, application, websocket 의 범위마다로 Bean 의 생명주기를 설정할 수 있습니다.
ScopeDescription (참고)
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext. |
session | Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext. |
application | Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext. |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext. |
3. Bean 의 사용 이유
개인적인 생각 정리 입니다.
Spring Framework 에서 제공하는 IOC 컨테이너를 통한 의존성 주입을 위해 Bean 을 사용하는 목적이 가장 크다고 생각되긴 합니다.
여기서 조금 더 생각해보면
- Bean 객체는 Default 로 Singleton Scope 를 가집니다.
- OkHttpClient 와 같은 외부 라이브러리 객체를 코드단에서 직접 생성하지 않고 Bean으로 등록하였을 때, 객체 인스턴스가 여러 개 생김으로 써 발생하는 다양한 문제점(메모리, 라이브러리 커넥셔 풀 등등,,)에 대해 고민할 필요가 없어지게됩니다.
- (추가로, 의존성을 외부에서 주입하기 때문에 특정 라이브러리를 필요하는 기능을 인터페이스로 추출한다면 라이브러리 변경에도 자유로울 수 있겠네요)
즉, 메모리 관점에서 객체를 Singleton 으로 관리할 수 있다는 것 또한 Bean 의 주요한 장점이라고 생각됩니다.
4. Spring Bean 의 권장 사용 방법
⚙️ Config
- Bean 으로 등록할 객체를 정의합니다.
✔️ Bean 등록과정
- Bean 의 Default Scope 는 Singleton 이기 때문에 @Configuration이 붙어있는 클래스를 Component Scan 과정에서 탐색하고
- @Bean이 붙어있는 메서드의 응답값을 IOC Contanier 에 등록합니다. (메서드는 1번만 실행)
- 이때 IOC 컨테이너에 등록되는 Bean 객체의 이름은 메서드 이름입니다. (name 속성으로 지정 가능)
- 만약 아래와 같이 같은 응답객체를 Bean 으로 등록하였을 때 @Primay 주석을 이용하여 Default Bean 객체를 지정할 수 있습니다.
@Configuration
public class OkHttpClientConfig {
@Primary
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.build();
}
@Bean
public OkHttpClient okHttpClientLongConnection() {
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.MINUTES)
.build();
}
}
⚙️ Bean 주입
- Bean 객체는 의존성 주입이 가능하기 때문에 원하는 객체를 정의합니다.
- 만약 @Primary 가 붙은 Default Bean 이 아닌 속성의 객체를 주입하고 싶다면 @Qualifer 주석을 통해 주입할 Bean 의 이름을 지정할 수 있습니다.
@Service
@RequiredArgsConstructor
public class TestService {
private final OkHttpClient okHttpClient;
@Qualifier("okHttpClientLongConnection")
private final OkHttpClient okHttpClientLongConnection;
public void hello() throws IOException {
var response = okHttpClient.newCall(null).execute();
var response2 = okHttpClientLongConnection.newCall(null).execute();
}
}
❗️ 만약 생성자 주입을 Lombok의 @RequiredArgsConstructor 사용한다면 아래와 같은 오류가 발생합니다. 이는 롬복에 @Qualifer 가 붙은 주석을 런타임시 실제 생성자로 변환할 때 함께 복사하지 못해서 나오는 에러입니다.
Lombok does not copy the annotation 'org.springframework.beans.factory.annotation.Qualifier' into the constructor
❗️아래와 같은 별도의 설정 파일(lombok.config) 을 통해 @Qualifier 어노테이션도 함께 복사하도록 세팅이 가능합니다.
# lombok.config
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
각각 다른 객체가 주입된 결과
5. Spring Bean 의 다른 사용방법 (권장 x)
사실 아래와 같이도 Bean 을 사용할 수 있습니다.
- @Configuration 또한 @Component 를 포함하기에 해당 클래스도 Bean 객체로 등록되어 있습니다.
- 또한 @Bean 이 명시된 메서드의 응답값이 Bean 객체로 지정되어 있기 때문에 해당 메서드를 실행해도, 실제로 메서드가 실행되지 않으며 메서드 이름으로 Bean 객체를 찾아서 반환합니다. (싱글톤)
@Service
@RequiredArgsConstructor
public class TestService {
private final OkHttpClientConfig okHttpClientConfig;
public void hello2() {
OkHttpClient okHttpClient1 = okHttpClientConfig.okHttpClient();
OkHttpClient okHttpClient2 = okHttpClientConfig.okHttpClientLongConnection();
}
}
다만, 이러한 방식은 Spring 에서 권장하는 방식이 아니라고 합니다.
@Configuration 은 @Bean 을 관리하는 목적으로만 사용하고 Bean 객체를 직접 주입받는게 싱글 톤을 명확하게 보장하는 방식이라고 하네요.
끝읕
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 다중 인스턴스에서 스케줄링 중복 실행 제어 하기 (@Scheduled Lock - shed lock) (0) | 2024.10.10 |
---|---|
동시성 제어 문제에 대한 고찰 (With. Spring, JAVA, MySQL, Redis, Kafka) (0) | 2024.08.30 |
Repository는 어느 모듈에 위치해야할까? (DIP. 고수준모듈, 저수준 모듈) (4) | 2024.07.09 |
Spring Boot 에서 Redis Cache 사용하기 (2) | 2024.07.02 |
Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction) (0) | 2024.07.01 |