Spring/스프링 핵심 원리 기본편 정리

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

Kanten no jiu 2023. 1. 14. 19:28

인프런 김영한님 강좌 스프링 핵심 원리 -기본편 내용을 학습하며 정리한 글입니다.

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

스프링 컨테이너 생성 과정

1. 스프링 컨테이너 생성

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext 를 스프링 컨테이너라 한다.
  • ApplicationContext 는 인터페이스이다.
  • 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
    직전에 AppConfig 를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든
    것이다.
  • 자바 설정 클래스를 기반으로 스프링 컨테이너( ApplicationContext )를 만들어보자.
    • new AnnotationConfigApplicationContext(AppConfig.class);
    • 이 클래스는 ApplicationContext 인터페이스의 구현체이다.
참고 : 스프링 컨테이너를 부를 때, BeanFactory , ApplicationContext 로 구분해서 이야기한다.

 

스프링 생성 과정

2. 스프링 빈 등록

  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보(AppConfig)를 사용해서 스프링 빈을 등록한다.
  • 빈 이름의 디폴트 값은 메서드 이름이다. (@Bean(name="memberService2") 으로 직접 설정 가능)
주의 : 빈 이름은 항상 다름이름을 부여해야한다.
          같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.

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

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
  • 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. (싱글톤 컨테이너)

 


스프링 빈 조회 - 기본

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

  • ac.getBean(빈이름, 타입)
  • ac.getBean(타입)
 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
    OrderService orderService = ac.getBean("orderService", OrderService.class);
    Assertions.assertThat(orderService).isInstanceOf(OrderServiceImpl.class);
}

@Test
@DisplayName("이름 없이 타입으로 조회")
void findBeanByType() {
    OrderService orderService = ac.getBean(OrderService.class);
    Assertions.assertThat(orderService).isInstanceOf(OrderServiceImpl.class);
}

@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName2() {
    OrderService orderService = ac.getBean("orderService", OrderServiceImpl.class);
    Assertions.assertThat(orderService).isInstanceOf(OrderServiceImpl.class);
}

@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByNameX() {
//    Object xxxxxx = ac.getBean("XXXXXX");
    org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("XXXXXX"));
}
참고 : 구체 타입으로 조회하면 변경시 유연성이 떨어진다.

 

스프링 빈 조회 - 동일한 타입이 둘 이상

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

ac.getBeanOfType() 을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.

 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate() {
    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);
}

@Configuration
static class TestConfig {
    @Bean
    public DiscountPolicy rateDiscountPolicy() {
        return new RateDiscountPolicy();
    }
    @Bean
    public DiscountPolicy fixDiscountPolicy() {
        return new FixDiscountPolicy();
    }
}

 

스프링 빈 조회 - 상속 관계

부모 타입을 조회하면, 자식타입도 조회한다. (최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.)

 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate() {
    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);
}

@Configuration
static class TestConfig {
    @Bean
    public DiscountPolicy rateDiscountPolicy() {
        return new RateDiscountPolicy();
    }
    @Bean
    public DiscountPolicy fixDiscountPolicy() {
        return new FixDiscountPolicy();
    }
}

 


BeanFactory와 ApplicationContext

 

 

BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스이다.
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다.
  • getBean() 제공한다.
  • 위 코드의 대부분 기능은 BeanFactory가 제공하는 기능이다.

ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공한다.
  • 애플리케이션을 개발할 때 필요한 빈을 관리하고 조회하는 기능과 수 많은 부가기능이 있다.

 

 

ApplicationContext가 제공하는 부가기능

  • 메세지 소스를 활용한 국제화 기능 예를 들어 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력한다.
  • 환경 변수: 로컬 / 개발 / 운영 등을 구분해서 처리
  • 애플리케이션 이벤트: 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • 편리한 리소스 조회: 파일, 클래스 패스, 외부 등에서 리소스를 편리하게 조회

 


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

스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다. (자바 코드, XML, Groovy...)

 


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

  • 스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 BeanDefinition이라는 추상화가 있다.
  • 쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것이다.
    • XML을 읽어서 BeanDefinition을 만들면 된다.
    • 자바 코드를 읽어서 BeanDefinition을 만들면 된다.
    • 스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
  • BeanDefinition을 빈 설정 메타정보라 한다.
    • @Bean, <bean> 당 각각 하나씩 메타 정보가 생성된다.
  • 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

BeanDefinition 정보

  • BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
  • Scope: 싱글톤(기본값)
  • lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부
  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  • Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)