스프링 컨테이너와 스프링 빈
스프링 컨테이너 생성
스프링 컨테이너가 생성되는 과정을 알아보기 전에 먼저 기본적인 개념을 알고 넘어가도록 하자.
스프링 컨테이너를 생성하는 코드는 다음과 같다.
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
- ApplicationContext를 스프링 컨테이너라고 한다.
- ApplicationContext는 인터페이스이다.
- 스프링 컨테이너는 XML을 기반으로 만들 수도 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수도 있다.
- 이전에 AppConfig를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 생성한 방식이다.
- new AnnotationConfigApplicationContext(AppConfig.class);를 통해 스프링 컨테이너를 생성할 수 있다.
- AnnotationConfigApplicationContext 클래스는 ApplicationContext의 구현체이다.
스프링 컨테이너의 생성 과정
1. 스프링 컨테이너 생성
- new AnnotationConfigApplicationContext(AppConfig.class)를 통해 스프링 컨테이너를 생성한다.
- 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.
- 예시에서는 구성 정보로 AppConfig.class를 사용하였다.
2. 스프링 빈 등록
스프링 컨테이너는 파라미터로 들어온 구성 정보를 사용해 '@Bean'이 붙은 메서드를 모두 호출하여 스프링 빈으로 등록한다. 메서드 호출을 통해 반환된 객체는 빈 객체로 등록된다.
빈 이름
- 빈 이름으로 메서드 명을 사용한다.
- 빈 이름은 직접 부여가 가능하다. (빈 이름은 중복되지 않아야 한다)
3. 스프링 빈 의존관계 설정 - 준비
4. 스프링 빈 의존관계 설정 - 완료
스프링 컨테이너에 스프링 빈이 모두 등록되면, 스프링 컨테이너는 설정 정보를 참고하여 의존관계를 주입(DI)한다. 단순히 자바 코드를 호출하는 것과는 다르다.
※ 참고
스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있지만, 생성자 주입을 통해 스프링 빈을 등록하는 경우 스프링 빈이 생성되면서 의존관계 주입도 함께 처리된다.
컨테이너에 등록된 모든 빈 조회
스프링 컨테이너에 실제 스프링 빈들이 잘 등록되었는지 확인해 보자.
예시 코드
package hello.core.beanfind;
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 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); // 빈의 메타데이터 정보
// Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
// Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
}
}
모든 빈 출력하기
- 스프링에 등록된 모든 빈을 정보를 출력할 수 있다.
- ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
- ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
애플리케이션 빈 출력하기
- 스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력한다.
- 스프링이 내부에서 사용하는 빈은 getRole()을 통해 구분할 수 있다.
- ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
- ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
스프링 빈 조회 - 기본
스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법에 대해 알아보자.
예시 코드
package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationContextBasicFindTest {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입만으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
MemberServiceImpl memberService = ac.getBean("memberService",MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX() {
// ac.getBean("xxxxx", MemberService.class);
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("xxxxx", MemberService.class));
}
}
스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 방법은 다음과 같다.
- ac.getBean(빈 이름, 타입)
- ac.getBean(타입)
만약, 조회 대상 스프링 빈이 없으면, NoSuchBeanDefinitionException 에러가 발생한다. 이 경우, assertThrows와 람다 함수를 이용해 예외 처리를 해주면 된다.
참고로, 구체 타입으로 스프링 빈을 조회할 경우, 유연성이 떨어지니 이 방법은 권장하지 않는다.
스프링 빈 조회 - 동일한 타입이 둘 이상
이번에는 스프링 빈을 조회할 때 동일한 타입이 둘 이상 있는 경우 어떻게 조회해야 하는지 코드를 통해 알아보자.
예시 코드
package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void FindBeanByTypeDuplicate() {
// MemberRepository bean = ac.getBean(MemberRepository.class);
Assertions.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 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);
}
@Configuration
static class SameBeanConfig {
@Bean
MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
스프링 빈을 타입으로 조회 시 같은 타입의 빈이 둘 이상이면 오류가 발생한다. 이때에는 빈 이름을 지정하면 된다.
ac.getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.
스프링 빈 조회 - 상속 관계
스프링 빈을 조회할 때, 상속 관계를 갖고 있는 경우 부모 타입으로 조회하면 자식 타입도 함께 조회한다. 따라서, 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
예시 코드
package hello.core.beanfind;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ApplicationContextExtendsFindTest {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
// DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면 빈 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
@Test
@DisplayName("부모 타입으로 모두 조회하기 - Object")
void findAllBeanByObjectType() {
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
BeanFactory와 ApplicationContext
이번에는 BeanFactory와 ApplicationContext에 대해 알아보자.
해당 그림을 보면, 최상위에 BeanFactory 인터페이스가 있고 이를 상속받은 ApplicationContext 인터페이스가 있다. ApplicationContext는 구현 클래스로 AnnotationConfigApplicationContext를 가진다.
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스이다.
- 스프링 빈을 관리하고 조회하는 역할을 담당한다.
- 'getBean()'을 제공한다.
- 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.
ApplicationContext
- BeanFactory의 기능을 모두 상속받아 제공한다.
- 빈을 관리하고 검색하는 기능을 제공하는 BeanFactory 외에 애플리케이션을 개발하기 위한 수많은 부가기능을 제공한다.
ApplicationContext가 제공하는 부가기능
ApplicationContext는 그림과 같이 BeanFactory 이외에도 다양한 인터페이스들을 상속받고 있다.
- MessageSource
- 메시지 소스를 활용한 국제화 기능
- ex) 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
- EnvironmentCapable
- 환경변수와 관련된 기능
- 로컬, 개발, 운영 등을 구분해서 처리
- ApplicationEventPublisher
- 애플리케이션 이벤트 기능
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- ResourceLoader
- 편리한 리소스 조회 기능
- 파일, 클래스, 외부 등에서 리소스를 편리하게 조회
정리
- ApplicationContext는 BeanFactory의 기능을 상속받는다.
- ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
- BeanFactory를 직접 사용할 일은 거의 없으며, 부가 기능이 포함된 ApplicationContext를 거의 사용한다.
- BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.
'Spring' 카테고리의 다른 글
[스프링] 스프링 핵심 원리 기본편 정리: 6. 컴포넌트 스캔 (0) | 2023.09.01 |
---|---|
[스프링] 스프링 핵심 원리 기본편 정리: 5. 싱글톤 컨테이너 (0) | 2023.08.30 |
[스프링] 스프링 핵심 원리 기본편 정리: 3. 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (0) | 2023.08.17 |
[스프링] 스프링 핵심 원리 기본편 정리: 2. 스프링 핵심 원리 이해1 - 예제 만들기 (0) | 2023.08.07 |
[스프링] 스프링 핵심 원리 기본편 정리: 1. 객체 지향 설계와 스프링 (0) | 2023.08.04 |