스프링 컨테이너와 스프링 빈

권영태·2024년 1월 4일
0

스프링

목록 보기
14/18

잘 몰랐던 스프링 컨테이너와 빈을 공부하고 정리한 내용이다.

출처 : 스프링 핵심 원리 - 기본편

스프링 컨테이너

스프링 컨테이너란

스프링에서 자바 객체들을 관리하는 공간을 스프링 컨테이너라고 부른다. 스프링에서 관리하는 자바 객체들을 빈(Bean)이라고 부르며, 스프링 컨테이너는 여러 빈(Bean)의 생성부터 소멸까지 개발자 대신 관리해준다.

스프링 컨테이너는 BeanFactory, ApplicationContext 로 구분해서 부르는데, BeanFactory는 빈을 관리하고 조회하는 역할만 담당한다면 ApplicationContext는 BeanFacory의 기능뿐만 아니라 다른 편리한 부가 기능까지 담당한다.

  • 편리한 부가 기능은 다음과 같다.
    1. 메시지소스를 활용한 국제화 기능
    2. 환경변수
    3. 애플리케이션 이벤트
    4. 편리한 리소스 조회

즉, BeanFactory를 직접 사용할 일은 거의 없고 대부분 부가기능이 포함된 ApplicaitonContext를 사용한다.
결론적으로 BeanFactory나 ApplicationContext 모두 스프링 컨테이너라고 부른다.

// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(...);

ApplicationContext는 인터페이스로 AnnotationConfigApplicationContext로 구현되어 있다.

스프링 컨테이너 생성 과정

1. 스프링 컨테이너 생성

// 스프링 컨테이너 생성(AppConfig.class)
ApplicaionContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • 인터페이스 ApplicationContext의 구현체인 AnnotationConfigApplicationContext를 통해 스프링 컨테이너를 생성한다.
  • 이때 생성된 스프링 컨테이너는 스프링 빈 저장소를 갖고 있으며, 스프링 빈 저장소는 Key-Value 형식처럼 빈 이름-빈 객체형식으로 Bean을 등록 및 관리한다.
  • 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 하는데, 여기선 AppConfig.class가 구성 정보로 지정되어 있다.
  • 이렇게 스프링 컨테이너는 지정된 구성 정보를 활용하여 스프링 빈 저장소에 빈을 등록•관리한다.



2. 스프링 컨테이너 등록

  • 실제 AppConfig.class에 등록된 Bean들을 스프링 빈 저장소가 어떻게 등록•관리하고 있는지 확인할 수 있다.
  • 기본적으로 빈 이름은 메서드 이름을 사용하지만 직접 빈 이름을 부여할 수 있다.
// BeanName을 메서드 이름이 아닌, springBeanTest로 부여하기
@Bean(name="springBeanTest")
public MemberService memberservice() {
    return new MemberServiceImpl(memberRepository());
}
  • 주의 : 빈 이름은 항상 다른 이름을 부여해야 한다.




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

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.

컨테이너에 등록된 빈 조회

1. 스프링 빈 조회 - 이름/타입

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

@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);
}
  • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈(스프링 내부 + 애플리케이션) 이름을 조회한다.
  • ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다. 아래 두 가지 방법으로 조회 가능!
    • ac.getBean(빈 이름, 타입)
      • 빈 타입으로 조회할 때 구체 타입으로 조회한다면 변경시 우연성이 떨어지니 유의하자.
    • ac.getBean(타입)
      • 타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 NoUniqueBeanDefinitionException이 발생하니 빈 이름을 함께 지정해 조회해야 된다.
      • 만약 타입으로 조회하고 싶다면 ac.getBeanOfType()을 사용하자.
        getBeanOfType()의 반환 타입은 Map<String, ?>
    • 만약 빈 이름으로 조회되지 않는다면 NoSuchBeanDefinitionException이 발생한다.
  • 스프링 내부에서 사용하는 빈은 getRole()로 구분할 수 있다.
    • ROLE_APPLICATION : 애플리케이션 빈. 일반적으로 사용자가 정의한 빈
    • ROLE_INFRASTRUCTURE : 스프링 내부에서 사용하는 빈
  • BeanDefinition
    • BeanDefinitionApplicationContextBeanDifinitionReader를 통해 설정 정보들을 읽어 BeanDefiniton 인터페이스 객체로 생성해 설정 정보들을 관리한다.
    • BeanDefinition을 통해 Bean의 설정 정보들을 확인할 수 있고, 이를 통해 직접 등록한 애플리케이션 빈만 조회할 수 있다.



2. 스프링 빈 조회 - 상속 관계

AnnotationConfigApplicationContext 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 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));
    }
}
  • 부모 타입으로 조회하면, 자식 타입도 함께 조회되는데 만약 자식이 둘 이상 있다면 중복 오류가 발생한다.
    그래서 자식이 둘 이상일 땐 조회하려는 빈 이름을 함께 지정하면 된다.
  • 그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈이 조회된다.
profile
GitHub : https://github.com/dudxo

0개의 댓글