스프링 컨테이너란, 자바 객체(빈)의 생명 주기를 관리하는 스프링 컴포넌트이다.
스프링 컨테이너는 빈의 관리/검색 및 부가기능을 제공한다.
ApplicationContext applicationContext = new AnnotationConfigApplication(AppConfig.class)
ApplicationContext를 스프링 컨테이너라 한다.
위의 코드는, AppConfig.class를 설정 정보로 하여 스프링 컨테이너를 만든 것이다.
기본 설정 정보 AppConfig 클래스
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public BoardPolicy boardPolicy(){
return new ReadOnly();
}
@Bean
public BoardService boardService(){
return new BoardServiceImpl(memberRepository(), boardPolicy());
}
}
스프링 컨테이너 생성
public class GetBeanTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 스프링 컨테이너 생성
ac.getBean(이름)을 사용하여 해당 빈을 조회할 수 있다. @Test
@DisplayName("Find Bean By Name")
void findBeanByName(){
// getBean(이름): 해당 빈을 Object 타입으로 반환
Object memberService = ac.getBean("memberService");
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
ac.getBean(타입)을 사용하여 해당 타입의 빈을 조회할 수 있다. @Test
@DisplayName("Find Bean By Type")
void findBeanByType(){
// getBean(타입): 해당 타입 + 자식 타입의 빈을 Object 타입으로 반환
Object memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
ac.getBean(이름, 타입)으로 해당 빈을 조회할 수 있다. @Test
@DisplayName("Find Bean By Name & Type")
void findBeanByNameType(){
// getBean(이름, 타입): 해당 빈을 Object 타입으로 반환
Object memberService = ac.getBean("memberService",MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
ac.getBean(타입)을 사용했을 때, 해당되는 빈이 2개 이상 존재하면 오류가 발생한다.
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(SameTypeBeanConfig.class);
@Configuration
static class SameTypeBeanConfig{
@Bean
public BoardPolicy boardPolicy1(){
return new ReadOnly();
}
@Bean
public BoardPolicy boardPolicy2(){
return new ReadOnly();
}
}
@Test
@DisplayName("Find Bean By Type")
void findBeanByType(){
// 해당 타입의 빈이 여러개 존재한다면, 중복 오류 발생
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(BoardPolicy.class));
}
해결 방법은, ac.getBean(이름, 타입)을 사용하는 것이다.
⚠️ 특정 타입의 빈을 모두 조회하고 싶다면, getBeansOfType(타입)을 사용한다.
// 특정 타입의 빈을 모두 출력하고 싶다면, getBeansOfType()을 사용하면 된다.
@Test
@DisplayName("Find ALL Bean By Type")
void findAllBeanByType(){
Map<String, BoardPolicy> beansOfType = ac.getBeansOfType(BoardPolicy.class);
org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
}
ac.getBean(타입)을 사용하여 빈을 조회할 때, 자식 타입의 빈도 함께 조회된다.
➜ ac.getBean(Object.class)는 모든 스프링 빈을 반환한다.
여기에서 나는 두가지 의문점이 생겼다.
1. 인터페이스 - 구현객체도 상속 관계라 볼 수 있는가?
2. 빈의 타입은 무엇인가?
위 두 의문점에 대한 답은, 인프런 질문 게시판에서 찾을 수 있었다.
- 인터페이스 - 구현객체도 상속 관계라 볼 수 있는가?
요약: 인터페이스도 부모가 될 수 있다.
- 빈의 타입은 무엇인가?
요약: @Bean이 달린 메소드의 리턴 값이 상위 객체라고 하더라도, 생성된 객체의 타입 정보는 하위 객체를 나타낸다.
즉, 빈의 타입은 return new 생성자()로 생성된 객체 자체의 타입을 따라간다.
public class ParentBeanTest {
// 부모 타입의 빈을 조회하면, 자식 타입의 빈도 함께 조회된다.
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(InheritBeanTest.class);
@Configuration
static class InheritBeanTest{
@Bean
public BoardPolicy boardPolicy1(){
return new ReadOnly();
}
@Bean
public BoardPolicy boardPolicy2(){
return new ReadWrite();
}
}
@Test
@DisplayName("부모 타입 조회 시, 자식 타입도 모두 조회된다.")
void findBeanByParentTypeError(){
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(BoardPolicy.class));
}
BoardPolicy.class로 빈을 조회하면, BoardPolicy 인터페이스(부모)를 구현한 모든 구현 객체(자식)를 조회한다.
➜ ReadOnly, ReadWrite 객체 모두 조회됨. NoUniqueBeanDefinitionException 발생
위의 문제를 해결하기 위해서는, 이름 + 타입으로 조회하면 된다.
@Test
@DisplayName("부모 타입으로 조회할 때, 자식이 둘 이상 있으면 자식의 이름을 사용하면 된다.")
void findBeanByParentTypeAndChildName(){
BoardPolicy boardPolicy = ac.getBean("boardPolicy1",BoardPolicy.class);
assertThat(boardPolicy).isInstanceOf(ReadOnly.class);
}
ApplicationContext는 BeanFactory뿐만 아니라 다양한 인터페이스를 상속한다.
| 상속하는 인터페이스 | 추가되는 기능 |
|---|---|
| MessageSource | 메시지 소스를 활용한 국제화 |
| EnvironmentCapable | 환경변수 처리 |
| ApplicationEventPublisher | 애플리케이션 이벤트 처리 지원 |
| ResourceLoader | 리소스 조회 |
| BeanFactory | 빈 조회 및 관리 |
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations. - spring.io
BeanDefinition은 설정 메타 정보를 가지고 있다.
설정 메타 정보(Configuration metadata)는,
를 포함하고 있다.
앞선 예시에서는 자바 코드인 AppConfig 클래스를 설정 정보로 사용했다.
스프링 컨테이너는 XML, Groovy 등도 설정 정보로 받을 수 있다.
https://spring.io/projects/spring-framework
이것이 가능한 이유는, BeanDefinition 인터페이스를 기반으로 스프링 빈을 생성하기 때문이다.
컨테이너는 설정 정보가 자바 클래스인지, XML인지, Groovy인지 알 필요가 없다. BeanDefinition 인터페이스만으로 스프링 빈을 생성하기 때문이다.
설정 정보에 맞게 BeanDefinition을 구현하는 것은 구현체(AnnotatedBeanDefinitionReader, XMLBeanDefinitionReader 등)가 할 일이다.