이번 포스팅에서는 스프링 컨테이너와 빈에 대해서 간략하게 정리해 보겠습니다.
스프링 컨테이너
스프링 컨테이너를 생성할 때
ApplicationContext application = new AnnotationConfigApplicationContext(AppConfig.class)
코드를 사용하면 됩니다.
여기서
ApplicationContext가 스프링 컨테이너라고 하고 인터페이스입니다.
위에서 AppConfig.class는 애노테이션을 이용해 빈을 등록하는 클래스라서
AnnotationConfigApplicationContext를 이용해 스프링 컨테이너를 구체화 시켰습니다.
추가적으로
BeanFactory와 ApplicationContext를 스프링 컨테이너라고 부릅니다. 하지만 둘의 차이가 있고 다음과 같은 그림으로 정리할 수 있습니다.
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스입니다.
- 스프링 빈을 관리하고 조회하는 역할을 담당합니다.
- getBean()을 제공합니다.
ApplicationContext
- BeanFactory 기능을 모두 상속받아서 제공합니다.
- 추가적으로 몇가지 기능을 더 사용할 수 있습니다.
추가적인 기능
- 메시지소스를 활용한 국제화 기능
- 환경 변수
- 애플리케이션 이벤트
- 편리한 리소스 조회
다시 정리하면
ApplicationContext와 BeanFactory모두 스프링컨테이너이지만
ApplicationContext가 BeanFactory와 몇몇의 인터페이스를 더 상속받아
BeanFactory의 기능 뿐만 아니라 더 편리한 기능을 추가적으로 사용할 수 있게 되었습니다.
스프링 컨테이너의 빈 등록
위에서 사용된 AppConfig의 코드입니다.
AppConfig의 정보를 바탕으로 스프링 컨테이너가 만들어지면서
스프링 빈 저장소에 빈 정보를 저장합니다.
빈 이름 | 빈 객체 |
memberService | MemberServiceImpl |
orderService | OrderServiceImpl |
memberRepository | MemoryMemberRepository |
discountPolicy | RateDiscountPolicy |
이처럼 메소드 명을 이름으로 메소드에서 생성된 객체를 빈 객체로 저장합니다.
그리고 처음에 @Bean(name="memberService22") 이런 방식으로 빈 이름을 다르게 설정할 수 있습니다.
** 주의
- 빈 이름은 항상 다르게 정해야 합니다. 만약 같은 이름을 부여한다면 다른 빈이 무시되거나 기존의 빈이 덮어져버릴 수 있습니다.
컨테이너에 등록된 빈 조회
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class)를 통해서 스프링 컨테이너를 불러오고 컨테이너의 AppConfig의 내용을 바탕으로 스프링 빈 저장소에 빈을 저장했습니다.
그렇다면 빈들이 잘 저장되었는지 확인해보는 과정을 가져보겠습니다.
사용할 메소드는 Application에 있는 getBeanDefinitionNames와 getBean메소드를 활용하겠습니다.
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + "object = " + bean);
}
}
}
처음 test에서는 AppConfig와 관계없이 spring을 실행하기 위한 모든 빈들이 다 조회되었습니다.
그래서 두번째 test에서는 ROLE_APPLICATION을 활용해 제가 직접 만든 빈만 죄회되게 했습니다.
첫번째 test
두번째 test
이번에는 원하는 빈만 찾아오겠습니다.
getBean(이름, 타입)
getBean(타입)
이렇게 조회를 할 수 있습니다.
만약 이름이나 타입으로 조회했을때 빈이 없다면 NoSuchBeanDefinitionException에러가 발생하게 됩니다.
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName(){
Object memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입으로만 조회")
void findBeanByType(){
Object memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2(){
Object memberService = ac.getBean("memberService", MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
그런데 만약 이름없이 타입으로만 찾을 때 두 개 이상의 빈이 존재한다면 어떻게 될까요?
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
/*
assertThrows(NoUniqueBeanDefinitionException.class,
()->ac.getBean(DiscountPolicy.class));
*/
}
@Configuration
static class TestConfig{
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
이런 코드를 작성해봤습니다. 현재 빈 저장소에는
빈 이름 | 빈 객체 |
rateDiscountPolicy | RateDiscountPolicy |
fixDiscountPolicy | FixDiscountPolicy |
이때 test를 실행시키면
이런 오류가 발생함을 알수 있습니다. 즉 같은 타입의 빈이 두개이기 때문에 무엇을 가져와야할지 모르겠다는 뜻입니다.
이럴때는 빈 이름을 이용하여 조회를 해야합니다.
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByParentTypeBeanName(){
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
바로 위의 코드와 또 위의 코드를 봤을 때 뭔가 이상함을 느끼실 수도 있습니다... 사실 제가 느꼈었거든요 ㅎㅎ
바로
빈 이름 | 빈 객체 |
rateDiscountPolicy | RateDiscountPolicy |
fixDiscountPolicy | FixDiscountPolicy |
이 표를 보면 객체가 다른데 왜 DiscountPolicy를 호출했을 때 둘 다 나오는 것일까? 입니다. 이는
스프링 컨테이너에 타입으로 검색을 한다면 그 타입을 상속받은 모든 자식 타입들도 다 찾아집니다. 그래서
DiscountPolicy를 호출했을 때 이 타입을 상속받은 RateDiscountPolicy와 FixDiscountPolicy를 모두 호출했던 것입니다.
위에 그림을 본다면 더 쉽게 이해하실 수 있습니다.
출처
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주
www.inflearn.com
'스프링' 카테고리의 다른 글
스프링 컨포넌트 스캔 (0) | 2021.09.15 |
---|---|
스프링 싱글톤 (0) | 2021.09.14 |
스프링 프레임워크란 무엇일까? (0) | 2021.08.17 |
순수 자바를 이용한 스프링 의존관계의 원리(IoC,DI) (1) | 2021.08.07 |
스프링으로 게시판 만들기 6 - 데이터 수정 (0) | 2021.08.05 |
댓글