<Spring> 스프링 컨테이너와 스프링 빈

라모스·2022년 1월 21일
0

Spring☘️

목록 보기
4/18
post-thumbnail

스프링 컨테이너 생성

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

ApplicationContext스프링 컨테이너라고 하며 이는 인터페이스이다.
스프링 컨테이너는 XML을 기반으로도, 애노테이션 기반의 자바 설정 클래스로도 만들 수 있다.

📌 참고
더 정확히는 스프링 컨테이너를 부를 때 BeanFactory, ApplicationContext로 구분해서 이야기한다. BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라 한다.

스프링 컨테이너 생성 과정

1. 스프링 컨테이너 생성

new AnnotationConfigApplicationContext(AppConfig.class)
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 여기서는 AppConfig.class로 지정함.

2. 스프링 빈 등록

스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
빈 이름은 메서드 이름을 사용하는데 다음과 같이 직접 부여할 수 도 있다.
@Bean(name="memberService2")

다만, 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.

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

기존 AppConfig에는 MemberServiceOrderService가 있는데 이 두 클래스를 생성하기 위해서는 각각 할인정책, 회원 리포지토리에 의존관계를 주입해줘야 한다. 이런 정보가 AppConfig라는 구성정보 클래스에 담겨있기 때문에 이를 베이스로 스프링 빈 의존관계를 설정해준다.

스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다. 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있는데 이처럼 자바 코드를 통해 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.

컨테이너에 등록된 모든 빈 조회

package hello.core.beanfind;

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;
import static org.assertj.core.api.Assertions.assertThat;

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);
    }
}
  • 실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.
  • ac.getBeanDefinitionNames(): 스프링에 등록된 모든 빈 이름을 조회
  • ac.getBean(): 빈 이름으로 빈 객체(인스턴스)를 조회
@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);
        }
    }
}
  • 스프링이 내부에서 사용하는 빈은 제외하고, 직접 등록한 빈만 출력.
  • 스프링이 내부에서 사용하는 빈은 getRole()로 구분할 수 있다.
    • ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
    • ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈

스프링 빈 조회

기본

스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 다음과 같다.

  • ac.getBean(빈이름, 타입)
  • ac.getBean(타입)

조회 대상 스프링 빈이 없으면 NoSuchBeanDefinitionException: No bean named 'xxxxx' available 예외가 발생한다.

동일한 타입이 둘 이상

getBean()을 통해 타입으로 빈을 조회할 수 있는데, 해당 타입의 빈이 하나가 아니라 둘 이상이라면 어떻게 될까?

타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이라면 오류가 발생한다. 이때는 빈 이름을 지정해야 한다.

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
    
    @Test
    @DisplayName("동일한 타입이 둘 이상인 스프링 빈을 타입으로 조회할 경우 에러 발생")
    void findBeanByTypeDuplicate() {
        assertThatThrownBy(() -> {
            DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        }).isInstanceOf(NoUniqueBeanDefinitionException.class);
    }
    
    @Configuration
    static class SameBeanConfig {
        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
        
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }
    }
}
@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.getBeansOfType(): 해당 타입의 모든 빈을 조회할 수 있다.

상속관계

부모 타입으로 조회하면, 자식 타입도 함께 조회한다. 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회하게 된다.

BeanFactory와 ApplicationContext

BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스
  • 스프링 빈을 관리하고 조회하는 역할을 담당
  • getBean()을 제공함.
  • 지금까지 위에서 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.

ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공한다.
  • BeanFactory의 기능 뿐만 아니라 애플리케이션 개발을 위해 필요한 그 밖의 다양한 부가 기능을 같이 제공한다.

다음과 같은 부가 기능을 제공한다.

  • MessageSource: 메시지 소스를 활용한 국제화 기능
  • EnvironmentCapable: 로컬, 개발, 운영등을 구분해서 처리(환경 변수)
  • ApplicationEventPublisher: 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • ResourceLoader: 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

다양한 설정 형식 지원 - 자바 코드, XML

스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다.

스프링 빈 설정 메타 정보 - BeanDefinition

BeanDefinition이라는 추상화를 통해 스프링은 다양한 설정 형식을 지원한다.
다시 말해, 역할과 구현을 개념적으로 나눈 것이다.
BeanDefinition을 빈 설정 메타정보라고 한다. @Bean, <bean> 당 각각 하나씩 메타 정보가 생성된다. 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

// 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 됨.

References

profile
Step by step goes a long way.

0개의 댓글