스프링 컨테이너란?
스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다. 여기서 말하는 자바 객체를 스프링에서는 빈(Bean)이라고 부른다.
이런 식으로 스프링 컨테이너 안에 스프링 빈 저장소가 있고, 그 안에 빈이 들어있다고 생각하면 된다.
스프링 컨테이너의 종류
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스다.
- 스프링 빈을 관리하고 조회하는 역할을 담당한다.
ApplicationContext
- BeanFactory 기능을 모두 상속받아서 제공한다.
- 빈을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데, 그러면 둘의 차이가 뭘까?
정리
ApplicationContext는 BeanFactory의 기능을 상속받는다.
ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다. BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.
주의사항
- 조회 대상 스프링 빈이 없으면 예외가 발생
- 동일한 타입이 둘 이상이면 오류 -> 빈 이름을 지정한다.
스프링 빈 조회
import hello.core.discount.DiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
위와 같은 AppConfig가 있다고 하자.
테스트코드를 통해 조회해 보자
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
//Role Role_APPLICAION: 직접 등록한 애플리케이션 빈
//Role Role_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("beanDefinitionName = " + beanDefinitionName + " object = " + bean);
}
}
}
}
- new AnnotationConfigApplicationContext(AppConfig.class)으로 스프링 컨테이너를 만든다.
- ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
- ac.getBean(타입): 스프링 컨테이너에서 스프이 빈을 찾는 가장 기본적인 조회 방법
싱글톤 컨테이너
스프링 컨테이너는 객체의 인스턴스를 싱글톤으로 관리하므로 싱글톤 컨테이너라고도 불린다. 그래서 AppConfig 클래스를 읽어 들여서 만든 빈 목록에 있는 임의의 빈을 여러 번 불러도, 그 빈들의 주소는 같다.
자바 코드로만 보면 MemoryMemberRepository()를 2번 이상 호출 하여 싱글톤이 깨지는 것처럼 보인다. 하지만 실제로는 싱글톤을 보장한다. 왜 그럴까?
바로 @Configuration 어노테이션이 있기 때문이다. 일단 스프링 컨테이너의 AppConfig를 빈으로 꺼내오고 getClass() 메서드를 호출하여 출력해 보자
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
실행 결과가 뭔가 이상하다. 원래는 'class blog.AppConfig'까지만 나와야 하는데 Enhancer~이라는 낯선 명령어가 나오는 것을 알 수 있다. 이것은 우리가 만든 AppConfig가 아니라 CGLIB라는 바이트코드 조작 라이브러리를 이용해서 AppConfig 클래스를 상속받은 임의의 클래스를 만들고, 그 임의의 클래스를 스프링 빈으로 등록한 것이다.
그리고 CGLIB 라이브러리를 이용한 클래스는 내부적으로 @Bean이 붙은 메서드마다 이미 스프링 빈이 존재하는지 확인하고 있다면 존재하는 빈을 반환하는 형식으로 역할을 한다. 이로 인해 싱글톤을 보장하는 것이다. 참고로, 특정 객체를 빈으로 등록하는 순간 그 객체의 자식 객체들도 연쇄적으로 모두 빈에 등록되므로 AppConfig의 자식 객체인 AppConfig (ver. CGLIB) 객체도 같이 등록된다.
만약, @Configuration을 적용하지 않고, @Bean만 적용한다면 싱글톤을 보장하지 못한다.
출처
'Spring' 카테고리의 다른 글
Spring Security + JWT 회원가입, 로그인 기능 구현 (7) | 2023.01.17 |
---|---|
빈의 생명주기 (1) | 2023.01.03 |
컴포넌트 스캔과 의존 관계 자동 주입 (3) | 2023.01.02 |
Spring Boot 동작 원리 (4) | 2022.12.24 |
스프링의 핵심 (5) | 2022.12.24 |