1. 스프링 컨테이너란
주로 ApplicationContext 인터페이스를 스프링 컨테이너라고 한다.
* 더 정확히는 BeanFactory, ApplicationContext로 구분해야 한다.
그럼 BeanFactory와 ApplicationContext는 또 뭐냐...?
🔸 BeanFactory : 스프링 컨테이너의 최상위 인터페이스로 스프링 빈을 관리, 조회하는 역할을 한다.
🔸 ApplicationContext : Bean Factory를 상속받아 BeanFactory의 기능 + 추가적인 부가 기능을 제공한다.
- BeanFactory의 가계도를 보자.
* 인텔리제이에서 클래스 다이어그램 간단하게 보는 방법
AnnotationConfigApplicationContext클래스에서 마우스 오른쪽을 눌러 다이어그램 메뉴를 선택하면 된다.
ApplicationContext가 제공하는 부가 기능들은 무엇이 있을까?
ApplicationContext가 상속받은 인터페이스를 보면 여러가지가 있는데 대표적으로 아래와 같이 구분할 수 있겠다.
- MessageSource : 국제화 기능
- EnvironmentCapable : 로컬, 개발, 운영 등 분리하여 처리할 수 있는 환경변수 관련 기능
- ApplicationEventPublisher : 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- ResourceLoader : 파일, 클래스패스 등으로부터 리소스를 편리하게 조회하는 기능
2. 스프링 컨테이너의 생성 과정
스프링 생성 과정을 알아보기 위해서 이전에 작성했던 AppConfig 코드를 가져와서 보자.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
// return new RateDiscountPolicy();
return new FixDiscountPolicy();
}
}
public class MemberApp {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);
...
}
}
1) 스프링 컨테이너 생성
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
- 스프링 컨테이너 생성 시 전달받은 매개변수(AppConfig)의 정보를 가지고 스프링 컨테이너를 생성한다.
2) 스프링 빈 등록
- AppConfig에 @Bean이 붙은 메서드들을 스프링 컨테이너의 빈 저장소에 Bean으로 등록한다.
빈 이름 | 빈 실제 객체 |
memberService | MemberServiceImpl@101 |
orderService | OrderServiceImpl@102 |
memberRepository | MemoryMemberRepository@103 |
discountPolicy | FixDiscountPolicy@104 |
3) 스프링 빈 의존관계 설정 준비 + 스프링 빈 의존관계 주입
- 2) 스프링 컨테이너에 빈을 등록한 후, 생성자를 생성자를 호출하면서 필요한 의존관계 주입을 처리한다.
3. 그럼 이제 등록된 빈을 조회해보자
ac.getBean(빈이름, 타입);
ac.getBean(타입);
➡️ 조회하는 스프링 빈이 없으면 예외 발생(NoSuchBeanDefinitionException)
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력")
void printAllBeans() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name : " + beanDefinitionName + ", object : " + bean);
}
}
@Test
@DisplayName("스프링에서 사용하는 빈을 제외하고 내가 등록한 빈 출력")
void printApplicationBeans() {
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 + ", obj : " + bean);
}
}
}
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("타입으로만 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구현체 타입으로 조회")
void findBeanByName_2() {
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회 안될 때")
void findBeanByName_X() {
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx", MemberService.class));
}
}
위에서 빈 조회를 할 때 BeanDefinition이라는 것이 나왔다.
BeanDefinition 정보
- BeanClassName : 생성할 빈의 클래스 명
- factoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름 (appConfig)
- factoryMethodName : 빈을 생성할 팩토리 메서드 지정 (memberService)
- Scope : 싱글톤(default)
- lazyInit : 실제 빈을 사용할 때까지 생성을 지연하는 지 여부
- InitMethodName : 빈을 생성, 의존관계 적용한 뒤에 호출되는 초기화 메서드 명
- DestroyMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties : 의존관계 주입에서 사용
- Role :
- BeanDefinition .ROLE_APPLICATION : 내가 직접 등록한 애플리케이션 빈
- BeanDefinition .ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
더보기
이번에는 xml로 appConfig를 만들어 스프링 빈으로 등록해보자.
먼저 src/main/resource에 appConfig.xml을 만들어주자.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hyunbenny.springcore.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
</bean>
<bean id="memberRepository" class="hyunbenny.springcore.member.MemoryMemberRepository"/>
<bean id="orderService" class="hyunbenny.springcore.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
<constructor-arg name="discountPolicy" ref="discountPolicy"/>
</bean>
<bean id="discountPolicy" class="hyunbenny.springcore.discount.RateDiscountPolicy"></bean>
</beans>
public class BeanDefinitionTest {
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
@Test
@DisplayName("빈 설정 메타 정보 확인")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName : " + beanDefinitionName + " beanDefinition : " + beanDefinition);
}
}
}
}
- AnnotationConfigApplicationContext에 설정 정보를 담은 자바 클래스 파일(AppConfig.class)을 넣는 것이 아니라
- GenericXmlApplicationContext에 appConfig.xml파일을 생성자의 매개변수로 넣어주고 빈 정보를 확인이 되는지 보자.
타입으로 조회할 때, 조회하는 빈 타입이 둘 이상 있으면 NoUniqueBeanDefinitionException이 발생한다.
그렇다면 같은 타입이 여러개 있는 경우에는 어떻게 조회해야 할까?
빈 이름을 지정해서 조회해야 한다.
getBeansOfType() : 해당 타입의 모든 빈 조회
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 빈 조회시 같은 타입으로 둘 이상 있을 경우, 중복 오류 발생")
void findBeanByTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상이라면, 빈 이름을 지정해서 조회")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입 모두 조회")
void findAllBeansByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + ", value = " + beansOfType.get(key));
}
assertThat(beansOfType.size()).isEqualTo(2);
}
// 같은 타입을 조회를 해보기 위해서 같은 타입의 빈을 2개 등록해보자.
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
그러면 상속관계는?
부모타입을 조회하면 자식타입의 빈도 같이 조회가 된다.
➡️ Object 타입을 조회하면 모든 스프링 빈을 조회한다는 말
- 근데 부모타입으로 조회할 경우, 자식타입도 같이 조회가 되는데 자식이 둘 이상 있으면 NoUniqueBeanDefinitionException이 발생한다.
- 따라서 부모타입으로 조회 시에도 자식이 둘 이상 있으면, 이름 지정해서 조회하거나 하위 특정 타입으로 조회해야 한다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'Spring > Spring Framework' 카테고리의 다른 글
[Spring Framework] @ComponentScan (0) | 2023.04.10 |
---|---|
[Spring Framework] 스프링 컨테이너와 싱글톤 컨테이너 (0) | 2023.04.10 |
[Spring Framework] IOC와 DI (0) | 2023.04.05 |
[Spring Framework] 스프링 프레임워크란 (0) | 2023.04.05 |
로버트 마틴의 객체지향 설계 원칙 (SOLID) (0) | 2022.12.30 |