6 minute read


스프링 컨테이너

[기존]:
기존에는 개발자가 직접 자바코드로 모든 것을 함. 

[스프링 사용]:
스프링 컨테이러에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아 사용하도록 변경되었다.

Application Context 를 스프링 컨테이너라고 함. (더 정확히는 BeanFactory 와 구분 하지만 일반적으로 BeanFactory 를 직접 사용하는 경우는 거의 없음)

  • 기존에는 애플리케이션 구성 클래스(AppConfig) 등을 사용해서 직접 객체를 생성하고 DI(Dependency Injection)를 함.
  • 스프링에서는 구성 정보 설정을 스프링 컨테이너를 통해서 사용.
  • 스프링 컨테이너는 @Configuration 이 붙은 구성 클래스를 설정(구성)정보로 사용.
    • @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록.
    • 위처럼 스프링 컨테이너에 등록된 객체를 Spring Bean 이라고 함.
  • 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용.
  • 스프링 컨테이너를 통해서 필요한 스프링 빈(객체) 를 찾아야 함.
    • applicationContext.getBean("빈 이름", 반환 클래스) 를 통해 스프링 빈을 찾을 수 있다.



ApplicationContext

  • ApplicationContext 는 Interface 이다.
  • 스프링 컨테이너의 생성 방식 두 가지
    • XML
    • Annotation 기반의 JAVA 설정 클래스

Annotation 기반의 JAVA 설정 클래스를 통한 생성

    // 자바 기반 설정 클래스인 AppConfig.class 사용
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class)
  • AnnotationConfigApplicationContext( ) 는 ApplicationContext 인터페이스의 구현체.



스프링 컨테이너의 생성 과정

1. 스프링 컨테이너 생성

path

  1. new AnnotationConfigApplicationContext(설정 / 구성 클래스)
    • 위 구현체 생성 시 스프링 컨테이너가 생성됨.
  2. 스프링 컨테이너 내부에는 스프링 빈 저장소가 존재.(Key-Value 형태)
    • Key : 스프링 빈의 이름
    • Value : 스프링 빈 객체
  3. 스프링 컨테이너는 생성 시에 사용한 설정 / 구성 클래스 의 정보를 보고 스프링 빈 등록.



2. 스프링 빈 등록

path
스프링 컨테이너는 생성 시에 넘겨 받은 설정 정보 클래스에서 @Bean Annotation이 붙은 메서드들을 모두 찾아 스프링 빈 저장소에 등록한다.

  • Key : 메서드 이름 (옵션 name=”” 을 사용해 직접 부여도 가능)
    Bean(name="carefreelife")
    
  • 스프링 빈 이름은 유일한 이름을 부여해야 한다.
    • 같은 이름을 가진 빈 존재 시 무시되거나 Overwrite 되는 빈이 생기고, 오류가 발생할 수 있다.
  • Value : 메서드 리턴 값 (객체)



3. 스프링 빈 의존관계 설정

path

  • 스프링 컨테이너는 등록된 설정 정보를 사용해서 의존 관계를 주입한다. (Dependency Injection)
  • 객체 인스턴스 간 의존관계를 동적으로 주입.



스프링 빈 조회

Overview

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);  
    }  
}

path
실행 모습 - 직접 등록한 빈 뿐 아니라 스프링 내부 빈까지 출력되는 모습을 볼 수 있다.

  • 모든 빈 출력하기
    • 실행 시 스프링에 등록된 모든 빈 정보를 출력할 수 있다.
    • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
    • ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.



@Test  
@DisplayName("애플리케이션 빈 출력하기")  
void findApplicationBean() {  
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();  
  
    for (String beanDefinitionName : beanDefinitionNames) {  
        // getBeanDefinition : 각 빈에 대한 MetaData 정보를 얻을 수 있음.  
        BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);  
  
        // getRole - BeanDefinition.ROLE_APPLICATION :  
        // - 스프링 내부 빈이 아닌 애플리케이션 개발을 위해 직접 등록한 빈.  
        // - 외부 라이브러리에 의해 생성되는 빈  
        // getRole - BeanDefinition.ROLE_INFRASTRUCTURE :  
		// - 스프링이 내부에서 사용하는 빈
        if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {  
            Object bean = ac.getBean(beanDefinitionName);  
            System.out.println("name = " + beanDefinitionName + " Object = " + bean);  
        }  
    }  
}

path
실행 모습 - Bean 의 메타데이터 정보를 꺼내 ROLE_APPLICATION 에 해당하는 빈만 출력

  • 애플리케이션 빈 출력하기
    • 스프링이 내부에서 사용하는 빈은 제외하고, 개발자가 직접 등록한 빈 출력.
    • 스프링이 내부에서 사용하는 빈은 getRole() 메서드로서 구분할 수 있다.
      • ROLE_APPLICATION : 일반적으로 개발자가 개발을 위해 정의한 빈
      • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈



스프링 빈 조회 (기본)

// 스프링 컨테이너에서 스프링 빈을 조회하는 가장 기본적인 방법
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

// 방법 1
Object bean = ac.getBean( 이름, 타입);

// 방법 2
Object bean = ac.getBean(타입);

// 조회 대상 스프링 빈이 없으면 아래와 같은 예외가 발생한다.
`NoSuchBeanDefinitionException: No bean named '~' available`



스프링 빈 이름을 통한 빈 조회

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  
  
@Test  
@DisplayName("빈 이름으로 조회")  
void findBeanByName() {  
	// ac.getBean("빈 이름", 빈 타입)
    MemberService memberService = ac.getBean("memberService", MemberService.class);  
    System.out.println("memberService = " + memberService);  
    System.out.println("memberService.getClass() = " + memberService.getClass());  
}



스프링 빈 타입을 통한 조회

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); 

@Test  
@DisplayName("빈 타입으로 조회")  
void findBeanByType() {  
    MemberService memberService = ac.getBean(MemberService.class);  
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  
}



구체적 타입을 통한 스프링 빈 조회

@Test  
@DisplayName("구체적 타입으로 조회 - 선호되지 않음")  
void findBeanBySpec() {  
    MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);  
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  
}
  • 위와 같이 구체적 타입을 명시하여 빈 조회를 수행하는 것이 가능은 하지만 역할과 구현의 분리 관점에서 구현에 의존하는 방향성을 가지기 때문에 좋지 않은 코드이다.



스프링 빈 조회 실패 테스트 - NoSuchBeanDefinitionException

앞서 스프링 빈 조회 실패 시 NoSuchBeanDefinitionException 예외가 발생함을 언급했었다.

  • 실패 테스트를 통해 확인해보자.
@Test  
@DisplayName("[실패 테스트] 빈 이름으로 조회되지 않는 경우 : 예외 발생")  
void findBeanByNameFail() {  
	// no_such_name 이라는 이름의 빈은 현재 존재하지 않는다.
	MemberService no_such_name = ac.getBean("no_such_name", MemberService.class);  
}
  • 위 코드 실행 시 “no_such_name” 이라는 이름을 가진 빈이 등록되어 있지 않으므로 NoSuchBeanDefinitionException 예외를 발생시킬 것이다.

path
실행 모습 - NoSuchBeanDefinitionException 예외가 발생한 것을 볼 수 있다.



// JUnit5 를 사용하여 실제로 해당 Exception 이 발생하는지 확인하는 방법도 있다.
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test  
@DisplayName("[실패 테스트] 빈 이름으로 조회되지 않는 경우 : 예외 발생")  
void findBeanByNameFail() {  
	// no_such_name 이라는 이름의 빈은 현재 존재하지 않는다.
	assertThrows(NoSuchBeanDefinitionException.class,  
        () -> ac.getBean("no_such_name", MemberService.class));
}



스프링 빈 조회 실패 테스트 - NoUniqueBeanDefinitionException

스프링 빈 조회 시 조회 방법에 따라 중복 값을 가진 빈이 존재하면 NoUniqueBeanDefinitionException 예외가 발생한다.



중복 타입을 가진 빈 조회 시 - 예외 발생

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);  
  
@Test  
@DisplayName("빈 타입을 통한 빈 조회 시 동일한 타입이 둘 이상 있으면, 중복 오류가 발생한다.")  
void findBeanByTypeDuplicate() { 

	// MemberRepository.class 타입을 가진 빈 조회
    MemberRepository bean = ac.getBean(MemberRepository.class);  
}  

@Configuration
static class SameBeanConfig {  

	// MemberRepository.class 타입을 가짐
    @Bean  
    public MemberRepository memberRepository1() {  
        return new MemoryMemberRepository();  
    }  

	// MemberRepository.class 타입을 가짐 (중복)
    @Bean  
    public MemberRepository memberRepository2() {  
        return new MemoryMemberRepository();  
    }  
}
  • 스프링 빈 조회 시 파라미터에 빈의 타입만을 지정했기 때문에 지정된 타입을 가진 빈 두 개가 조회되며, getBean() 메서드에서는 하나의 빈만 반환하므로 NoUniqueBeanDefinitionException 예외가 발생하게 된다.

path
실행 모습 - NoUniqueBeanDefinitionException 예외 발생



중복 타입을 가진 빈 조회 시 - 예외 해결

@Test  
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 빈 이름을 지정해야 한다")  
void findDuplicateBeanByName() {  
	// 중복된 타입을 조회하지만, 빈의 이름을 특정하여 원하는 하나의 빈을 조회.
    MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);  
    assertThat(memberRepository).isInstanceOf(MemberRepository.class);  
}



특정 타입의 스프링 빈 전체 조회하기

이전에는 getBean() 메서드를 사용함에 하나의 빈만 찾을 수 있어 예외가 발생했다.

  • getBeansOfType() 메서드를 통해 중복된 타입을 가진 모든 빈을 찾을 수 있다.
@Test  
@DisplayName("특정 타입을 가진 모든 빈 조회하기")  
void findAllBeanByType() {  
	// getBeansOfType() 사용 시 찾은 모든 빈을 Map<String, 타입> 형태로 반환해준다.
    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);  
}

path
실행 모습 - 예외가 발생하지 않고 동일 타입을 가진 모든 빈을 조회 할 수 있다.



BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스.
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다.
  • getBean() 메서드를 제공한다.



ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공한다.
  • BeanFactory 와의 차이점
    • 애플리케이션을 개발할 때에 빈을 관리하고 조회하는 기능 외에 추가적인 부가 기능을 필요로 한다.



ApplicationContext 가 제공하는 부가기능

path

  • 메시지 소스를 활용한 국제화 기능
    • 한국에서 접근 시 한국어 출력
    • 영미권에서 접근 시 영어 출력
  • 환경 변수
    • Local, Dev, Release 등의 환경을 구분하여 처리가능.
  • Application Event
    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원.
  • 편리한 리소스 조회
    • File, Classpath, External 등의 Resource 들을 편리하게 조회.



정리

  • Application Context 는 BeanFactory 의 기능을 상속받는다.
  • ApplicationContext 는 빈 관리기능 + 편리한 부가 기능을 제공한다.
  • BeanFactory 를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext 를 사용한다.
  • BeanFactory / ApplicationContext 를 스프링 컨테이너라 한다.



참고:Inflearn - 김영한님_강의(스프링 핵심 원리 기본편)



혹시 이해가 안가거나 추가적인 설명이 필요한 부분, 오류 등의 피드백은 언제든지 환영합니다!

긴 글 읽어주셔서 감사합니다. 포스팅을 마칩니다.



처음으로~

Task Lists

  • 스프링 컨테이너
  • ApplicationContext
  • Annotation 기반의 JAVA 설정 클래스를 통한 생성
  • 스프링 컨테이너의 생성 과정
  • 스프링 빈 조회 (기본)
  • 스프링 빈 이름을 통한 빈 조회
  • 스프링 빈 타입을 통한 조회
  • 구체적 타입을 통한 스프링 빈 조회
  • 스프링 빈 조회 실패 테스트
  • 중복 타입을 가진 빈 조회 시 - 예외 발생
  • 중복 타입을 가진 빈 조회 시 - 예외 해결
  • 특정 타입의 스프링 빈 전체 조회하기
  • ApplicationContext 가 제공하는 부가기능
  • 정리

Comments