일반적으로 ApplicationContext 인터페이스를 스프링 컨테이너라고 한다.
스프링 컨테이너는 XML 기반 혹은 자바 어노테이션 기반 자바 클래스로 설정이 가능하다.
→ XML 기반 (과거) : ClassPathXmlApplicationContext
→ 자바 어노테이션 기반 (최근) : AnnotaionConfigApplicationContext
ApplicationContext annotationCtx = new AnnotationConfigApplicationContext(AppConig.class);
ApplicationContext xmlCtx = new ClassPathXmlApplicationContext("application.xml");
✔️ 스프링 컨테이너는 BeanFactory와 ApplicationContext로 구분되지만, 일반적으로 BeanFactory를 직접 사용하는 경우가 드물기 때문에 스프링 컨테이너 = ApplicationContext라 칭한다.
스프링 컨테이너 생성
ApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
파라미터로 들어간 AppConfig.class를 구성 정보로 지정.
AppConfig.class 내부에 @Bean 어노테이션으로 지정된 메소드를 스프링 빈 저장소에 저장
→ 빈 이름은 일반적으로 @Bean 어노테이션 지정된 메소드 명이 들어감
→ public MemberRepository memberRepository() {...}
memberRepository가 빈 이름이 됨
→ @Bean(name = "memberRepository")
로 이름 지정 가능
✔️ 빈 이름은 반드시 고유(Unique) 해야한다. 고유하지 않을 시 기존 빈을 덮어버리거나 다른 빈이 무시되는 오류가 발생한다.
✔️ 스프링은 빈 생성 단계와 의존성 주입 단계가 구분된다.
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
//스프링에 등록된 모든 빈이름 가져오기
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
//빈 이름으로 빈 객체 가져오기
Object bean = ac.getBean(beanDefinitionName);
System.out.println("beanDefinitionName = " + beanDefinitionName + " object = "+ bean);
}
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
//beanDefinition : 빈에 대한 정보
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
//빈 역할
if(beanDefinition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = "+bean);
}
}
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
// getBean(빈 이름, 타입)
MemberService memberService = ac.getBean("memberService", MemberService.class);
// getBean(빈 이름)
MemberService memberService = ac.getBean("memberService");
// getBean(타입)
MemberService memberService = ac.getBean(MemberService.class);
// 참고 : 구현에 의존한 코드, 구체 클래스를 접근하기 보단 추상화한 인터페이스를 접근하자!
MemberSerivce memberService = ac.getBean(MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회 x")
void findBeanByNameX() {
//ac.getbean("xxxx",MemerService.class):
MemberService xxxx = ac.getBean("xxxx", MemberService.class);
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx",MemberService.class));
}
// xxxx라는 이름으로 등록된 빈 객체가 없기 때문에 에러 발생
AppConfig.class
@Bean
public MemberRepository membeRepository1() {...}
@Bean
public MemberRepository memberRepository2() {...}
----
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplication() {
//타입만 지정
MemberRepository bean = ac.getBean(MemberRepository.class);
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
→ 해결책 : 타입으로 조회하는 경우 같은 타입이 있다면, 이름을 지정해준다.
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByTypeDuplication() {
MemberRepository bean = ac.getBean("memberRepository1",MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = "+ beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
→ Object 타입의 경우 최상위 객체 타입이기 때문에 스프링에서 설정한 모든 빈이 조회된다.
→ 부모 타입으로 조회 시 자식 타입이 둘 이상이면 NoUniqueBeanDefinitionException을 발생한다.
@Test
@DisplayName("부모 타입으로 모두 조회(Object 타입)")
void findBeanByObjectType() {
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key+" value = "+beansOfType.get(key));
}
}
@Test
@DisplayName("부모 타입으로 조회 시 자식이 둘 있으면, 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
✔️ ApplicationContext = BeanFactory + 부가기능 , 그러므로 ApplicationContext를 많이 사용하고, 이를 일반적으로 스프링 컨테이너라 부른다.
김영한님의 스프링 핵심 원리 기본편 : https://www.inflearn.com/course/스프링-핵심-원리-기본편/dashboard