Home 스프링 컨테이너와 스프링 빈
Post
Cancel

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

이 게시글은 인프런의 유료 강의 스프링 핵심 원리 - 기본편을 수강하며 공부한 내용을 담고있습니다.
저작권에 의해 강의내용에 해당하는 전체 코드는 공개할 수 없습니다.
저작권에 의해 모든 내용을 담을 수 없습니다. 중복 및 반복되는 내용은 생략했습니다.

스프링 컨테이너

스프링 컨테이너란 Spring 에서 자바 객체들을 관리하면서 다양한 기능들을 제공하기 위해 존재한다.

생성

1
2
// ApplicationContext 는 interface 이다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

위 코드는 스프링 컨테이너를 생성하는 코드고 ApplicationContext 가 스프링 컨테이너 역할을 한다.
ApplicationContext 는 인터페이스이기 때문에 구현체를 만들어야한다.
AnnotationConfigApplicationContext 클래스는 이름에서 알 수 있듯이 @Configuration 이 붙은 클래스를 가지고 ApplicationContext를 생성한다.
그리고 AppConfig 클래스를 기반으로 스프링 컨테이너로 만들것이기 때문에 생성자의 매개변수로 AppConfig.class 를 준것이다.

사진1
스프링 컨테이너를 생성하게되면 구성정보(여기서는 AppConfig)를 바탕으로 Spring Bean을 생성한다.

  • Bean 이름을 지정해줄 수 있지만 지정하지 않으면 기본적으로 메서드명을 이름으로 사용한다.
  • Bean 이름을 중복으로 지으면 오류가 나거나 나지 않더라도 이름이 중복된 Bean을 호출하기 어렵기 때문에 중복하지 않는게 중요하다.

사진2
그리고 이렇게 의존관계가 설정이된다. 사실 코드를 보면 이런 순서로 생성되는 것이 아님을 알 수 있다.

1
2
3
4
@Bean
public MemberService memberService() {
    return new MemberServiceImpl(memberRepository());
}

memberService 빈이 생성될 때 이미 memberRepository 빈을 생성하기 떄문이다.
(이 부분은 이해를 위해 일부러 이렇게 설명했다고 함. 추후 싱글톤 강의에서 제대로된 설명을 다시 함.)

사진3
결과적으로 Spring 컨테이너에는 그림과 같은 정보가 담겨지게된다.

Bean 조회 - 연습

만들어본 Bean을 조회해본다.

사진4

  1. ac 에 저장된 Bean 이름들을 모두 가져온다.
  2. 가져온 이름들로 Bean에 저장된 객체들을 가져온다.
  3. 콘솔 창에 출력한다.

사진5
출력 결과물을 보면 내가 작성하지 않은 Bean 도 보인다. Spring 자체에서 사용하는 Bean 이므로 넘어가면 된다.
눈여겨 보아야 할 것은 AppConfig 도 Bean 으로 등록되어 있다는 점과 각 Bean 에 어떤 객체가 저장되어 있는지이다.

Bean 조회 - 기본

  • getBean(이름, 타입)
  • getBean(타입)

기본적인 bean 조회 방법은 두 가지 방법이있다.
사진6
memberService 라는 이름을 가진 Bean 이 ac 에 있는지 조회했다.
가져온 값을 콘솔에 출력해서 눈으로 테스트 하는 방법도 있지만 수백개의 테스트를 눈으로 확인할 수 없으니, Assertions 를 이용해 테스트 로직을 작성한다.

1
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

memberService 가 MemberServiceImpl 클래스인지 검사한다. 맞으므로 오류가 발생하지 않고 콘솔에도 녹색 체크표시가 나온다.

타입으로 조회 하는 방법은 이름으로 조회하는 방법과 동일 하므로 생략한다.

사진7
test case 는 실패 case 도 있어야 한다.
실패한 경우 사진과 같이 NoSuchBeanDefinitionException 예외가 발생한다.
실패한경우가 의도적으로 동작하는지 체크하기 위해 NoSuchBeanDefinitionException 예외를 검증하는 테스트 코드를 작성한다.<br

1
2
3
4
5
6
7
8
@Test
@DisplayName("이름으로 빈 조회 실패")
void findBeanByName_fail() {
    //ac.getBean("asdf", MemberService.class);
    // 실패한 경우를 테스트할 때는 JUnit 의 Assertions 를 사용한다.(위에서 사용한 Assertions 랑 이름만 같고 다른 클래스임.)
    org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("asdf", MemberService.class));
}

assertThrows 메서드를 사용하여 ac.getBean(“asdf”, MemberService.class) 메서드가 호출 될 때 NoSuchBeanDefinitionException 예외가 발생하면 정상으로 처리되게 한다.
테스트를 실행하면 녹색 체크표시를 볼 수 있다.

Bean 타입으로 조회 - 중복이 있는 경우

  • getBean(타입)
  • getBeansOfType(타입) : 해당 타입의 모든 Bean 들을 조회

getBean(타입) 으로 조회 시 여러 Bean이 동일한 타입을 가지고 있는 경우가 있다. 이 경우 어떤 Bean을 리턴해줘야할지 모르기 때문에 예외가 발생한다.
이런 경우 getBeansOfType(타입) 메서드를 사용하여 중복되는 빈을 전부 가져온다.

사진8
중복된 빈을 테스트 하기 위해 임시로 @Configuration 클래스를 하나 만들어준다.
그리고 해당 클래스로 스프링 컨테이너를 만든다.

사진9
getBeansOfType 메서드를 사용해 조회하면 Map 자료구조로 모든 Bean 들을 리턴해준다.
하나씩 꺼내서 print 로 직접 확인해보면 위에서 임시로 만든 Bean들이 담겨져 있는 것을 볼 수 있다.

Bean 조회 - 상속 관계

Bean 을 조회할 때 부모 클래스로 조회하면 자식 클래스까지 모두 조회된다.
코드를 보면 쉽게 이해가 된다.

1
2
3
4
5
6
7
8
9
@Test
@DisplayName("타입으로 모든 빈 조회")
void findAllBeansByType() {
    // getBeansOfType 의 매개변수를 Object 클래스로 수정
    //Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
    Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
    for (String key : beansOfType.keySet()) {
        System.out.println("key : " + key + " value : " + beansOfType.get(key));
    }

모든 클래스는 Object 클래스를 상속받는다.
Object 클래스로 타입을 지정해서 모든 Bean 을 조회하면 Object 클래스뿐만 아니라 상속받는 모든 클래스가 출력되게 된다.

BeanFactory와 ApplicationContext

사진10
AnnotationConfigApplicationContext 는 ApplicationContext 를 상속받고(구현하고) ApplicationContext는 BeanFactory를 상속받는다.

  • BeanFactory
    • Spring 컨테이너 최상위 인터페이스
    • 스프링 Bean 을 관리 및 조회 (getBean 등을 제공)
  • ApplicationContext
    • 여태 사용한 기능은 전부 BeanFactory 의 기능이다.
    • ApplicationContext 를 사용하는 이유는 BeanFactory 뿐만 아니라 필요한 다른 여러 인터페이스를 상속받기 때문이다.

사진11
사진12
EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource 등을 상속받은 것을 볼 수 있다.

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

정리
BeanFactory, ApplicationContext는 스프링 컨테이너다.
ApplicationContext는 빈 관리기능(BeanFactory) + 편리한 부가기능을 제공한다.

다양한 설정 형식 지정(자바코드, XML)

사진13
스프링 컨테이너는 @Configuration 말고 다른 방법으로도 만들 수 있다. 그 중 하나는 XML이다. 아래는 XML 코드이다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
    </bean>
    
    <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository"/>

    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
        <constructor-arg name="discountPolicy" ref="discountPolicy"/>
    </bean>
    
    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy"/>
</beans>

src > main > resources 경로에 appConfig.xml 파일을 생성하고 위 코드를 작성한다.
기본적으로 XML 문법을 알고있으면 기존에 작성한 AppConfig.java 코드와 비교하면서 보면 바로 이해된다.

1
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

그리고 new GenericXmlApplicationContext 를 이용해 스프링 컨테이너를 만든다.

XML 로 작성하게 되면 컴파일 없이 Bean 설정 정보를 변경할 수 있다는 장점이 있다.
만약 위 코드를 이해하기 어렵다면 최근에는 사용하지 않으니 패스하고 지나가도 된다.

스프링 빈 설정 메타정보

사진14
@Configuration 이 붙은 클래스, XML 이외에 다양한 방법으로 Bean 을 설정할 수 있다.
어떻게 이게 가능할까?
그 중심에는 BeanDefinition 이라는 추상화가 있다.
XML 또는 자바코드를 읽어서 BeanDefinition 을 만들고,
스프링 컨테이너는 BeanDefinition 을 바탕으로 Bean을 만든다.
스프링 컨테이너는 Bean 의 메타정보가 XML에서 온건지 자바코드에서 온건지 전혀 몰라도된다.

사진15
그림의 빨간색 영역을 보면 AnnotationConfigApplicationContext 의 AnnotatedBeanDefinitionReader 가 AppConfig.class 를 읽고 BeanDefinition 을 생성하는 모습을 확인할 수 있다.
나머지 그림도 마찬가지의 내용이 담겨져있다.
사진16
실제 코드에 AnnotatedBeanDefinitionReader 가 존재하는 모습을 확인할 수 있다. 다음은 생성된 BeanDefinition 을 직접 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
@Test
void findApplicationBean() {
  // 일단 정의된 Bean들의 이름을 가져온다.
  String[] beanDefinitionNames = ac.getBeanDefinitionNames();
  // 반복문으로 Bean 이름을 하나씩 가져온다.
  for (String beanDefinitionName : beanDefinitionNames) {
    // get BeanDefinition 으로 BeanDefinition 을 꺼내온다.
    BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

    // 내용을 확인하기에 앞서 모든 Bean을 출력하면 내용이 너무 길어지니 if문으로 우리가 정의한 Bean만 출력한다.
    if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
      System.out.println("beanDefinitionName = " + beanDefinitionName +
          "\n beanDefinition = " + beanDefinition);
    }
  }
}
1
2
beanDefinitionName = memberService 
beanDefinition = Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.AppConfig

테스트 코드를 작성해 BeanDefinition 을 콘솔창에 출력하면 위와 같은 결과를 얻을 수 있다. 전부 출력하면 지저분해서 memberService 만 가져왔다.

  • beanDefinition = Root bean
  • class = null;
    생성할 빈의 클래스 명(여기서는 팩토리 역할의 빈을 사용해서 null이다.)
  • scope = ;
    싱글톤(기본값)
  • abstract = false;
  • lazyInit = null;
    스프링 컨테이너를 생설할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 떄 까지 최대한 생성을 지연처리 하는지 여부
  • autowireMode = 3;
  • dependencyCheck = 0;
  • autowireCandidate = true;
  • primary = false;
  • factoryBeanName = appConfig;
    팩토리 역할의 빈을 사용할 경우에 출력된다.
  • factoryMethodName = memberService; <br 빈을 생성할 팩토리 메서드의 이름.
  • initMethodNames = null;
    빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • destoryMethodNames = [(inferred)];
    빈의 생명주기가 끝나서 제거하기 직전 호출되는 메서드 명
  • defined in hello.core.AppConfig



이전 포스팅 : 기본 예제에 스프링 기술 적용하기
다음 포스팅 : 스프링과 싱글톤 기초

This post is licensed under CC BY 4.0 by the author.

기본 예제에 스프링 기술 적용하기

스프링과 싱글톤 기초