본문 바로가기

Spring/스프링 핵심 원리 - 기본

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

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 스프링 핵심 원리 - 기본편에 대해 공부하고, 정리한 내용입니다.

 


0. 들어가며

이전 게시글에서는 스프링이 나오게 된 개념에 대해 알아보았습니다. 이번 게시글에서는 스프링 자체가 어떻게 동작되는 지를 알아보겠습니다.

 

03. 스프링 핵심 원리 이해 2 - 객체 지향 원리 적용

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 스프링 핵심 원리 - 기본편에 대해 공부하고, 정리한 내용입니다. 1. 들어가며 지난 게시물에서는 스프링 프레임워크의 탄생 배경과 그 특징, 그

soo99.tistory.com


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

1) 스프링 컨테이너 생성

 - 밑의 코드는 스프링 프로젝트가 시작될 때, 가장 먼저 실행되는 코드입니다. 

public static void main(String[] args) {
  
  	// TODO: 스프링 컨테이너 생성
	ApplicationContext applicationContext = 
    		new AnnotationConfigApplicationContext(AppConfig.class);

}

 

(1) ApplicationContext applicatonContext = new AnnotationConfigApplicationContext(AppConfig.class);

  • 위 코드에서 ApplicationContext 스프링 컨테이너의 최상위 인터페이스입니다.
  • 즉, 스프링 컨테이너를 생성하고, 초기화 하는 역활을 합니다.
  • 이 컨테이너는 마치 '큰 상자'와 같은 역할을 하며, 이 상자 안에는 프로젝트에서 사용되는 여러 자바의 객체들을 보관하고, 관리하는 역할을 합니다. 이 객체들을 '빈(Bean)'이라고 부릅니다. 
  • 스프링 컨테이너를 통해 개발자들은 효율적이고, 편리하게 프로젝트를 진행할 수 있습니다.

(2) ApplicationContextapplicatonContext = new AnnotationConfigApplicationContext(AppConfig.class);

  • `AnnotationConfigApplicationContext` 클래스는 위 부분은 `ApplicationContext` 인터페이스를 구현한 클래스 입니다.
  •  ApplicationContextapplicatonContext 인터페이스의 인스턴스를 생성하는 코드입니다.
  • 여기서 `AnnotationConfigApplicationContext`어노테이션 기반의 설정 정보를 사용하여, 스프링 컨테이너를 생성하고 초기화합니다.

'AnnotationConfigApplicationContext 클래스'는 'ApplicationContext 인터페이스'를 참조하는 것을 알 수 있습니다.


(3) ApplicationContextapplicatonContext = new AnnotationConfigApplicationContext(AppConfig.class);

  • AnnotationConfigApplicationContext 클래스는 `AppConfig.class`라는 자바 설정 정보 클래스를 인자로 받아, 이 클래스에 정의된 빈 설정 정보를 바탕으로 스프링 컨테이너를 초기화합니다.
  •  `AppConfig.class`는 스프링 컨테이너가 관리해야 하는 빈들에 대한 정보가 들어 있습니다. 이 정보는 어떤 클래스가 빈이 되어야 하는지, 그리고 그 빈들이 어떤 다른 빈들에 의존하고 있는지 등을 스프링에게 알려주는 역할을 합니다.

  • 따라서 `AnnotationConfigApplicationContext` 클래스는 이 `AppConfig.class`를 파라미터로 받아서, 그 안에 있는 정보를 읽어들입니다. 그리고 이 정보를 바탕으로 스프링 컨테이너를 초기화하게 됩니다.
  • 초기화 과정에서는 `AppConfig.class`에 지정된 빈들이 스프링 컨테이너 안에 등록되고, 이 빈들 사이의 의존 관계가 설정됩니다.
  • 이렇게 생성된 스프링 컨테이너는 프로그램 실행 동안 빈들을 관리하게 됩니다. 필요한 빈을 요청받으면, 스프링 컨테이너는 해당 빈의 인스턴스를 생성하거나 기존에 생성해둔 인스턴스를 제공하게 됩니다. 이를 통해 개발자는 빈들의 생명 주기나 의존 관계 등을 직접 관리하지 않아도 됩니다.

※ 참고사항
 주의해야 할 중요한 점은, 스프링 컨테이너를 구분하는 과정에서 가장 상위 계층에 위치하는 'BeanFactory'와 그 하위 계층인 'ApplicationContext'를 명확히 구분하여 사용해야 한다는 것입니다.

 그렇지만, 실제로는 BeanFactory를 직접 사용하는 경우는 매우 드물며, 대다수의 경우에서는 ApplicationContext를 스프링 컨테이너라고 일반적으로 지칭하게 됩니다. 이에 대한 더욱 자세하고 심도 있는 내용은 다음에 깊게 분석하도록 하겠습니다.

 


2) 스프링 빈 등록

  • 위 내용에서 스프링 컨테이너를 생성할 때, 파라미터로 넘겨받은 설정 클래스(`AppConfig.class`)는 `@Bean` 어노테이션이 붙은 메서드들을 찾습니다.
  • 이 메서드들은 스프링 빈을 생성하고 초기화하는 역할을 합니다. 스프링 컨테이너는 이 `@Bean` 어노테이션이 붙은 메서드들을 모두 호출하고, 그 결과로 반환되는 객체들을 스프링 빈 저장소에 보관합니다.
  • 이렇게 보관된 빈들은 스프링 컨테이너가 관리하게 됩니다.

 

※  빈(Bean)의 이름

  • 기본적으로 `@Bean` 어노테이션이 붙은 메서드의 이름으로 지정됩니다. 하지만 필요한 경우, `@Bean(name="빈 이름")`과 같은 형식으로 빈의 이름을 직접 지정할 수도 있습니다.
  • 빈의 이름은 스프링 컨테이너 내에서 유일해야 합니다. 같은 이름을 가진 빈이 여러 개 등록되면, 기존에 등록된 빈을 덮어쓰거나 무시되어 오류가 발생할 수 있습니다. 그래서 빈의 이름은 항상 다르게 지정해주어야 합니다.

=> 최근의 스프링 부트는 이런 설정 실수를 자동으로 찾아내어 개발자에게 알려주는 기능을 제공하고 있습니다. 따라서 이런 실수를 미리 방지하고, 안정적인 프로그램을 개발할 수 있습니다.


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

  • 스프링 컨테이너는 빈들의 의존관계를 설정하는 과정도 수행합니다. 이 과정에서는 각 빈이 필요로 하는 다른 빈들을 찾아서, 그 빈들의 참조 값을 해당 빈에 연결해줍니다. 이렇게 하면 빈들이 서로를 참조하면서 동작할 수 있게 됩니다.
  • 이 과정은 단순히 자바 메서드를 호출해서 객체를 생성하는 것과는 차이가 있습니다. 스프링 컨테이너 빈을 싱글톤으로 관리하기 때문에, 필요한 빈을 요청할 때마다 새로운 객체를 생성하는 것이 아니라, 이미 생성해둔 객체의 참조 값을 제공합니다. 따라서 빈들 사이의 의존관계는 스프링 컨테이너가 관리하는 빈 저장소에 등록된 객체들 사이의 참조 관계로 연결됩니다.

(1) 스프링 빈 의존관계 설정 이전의 준비단계
(2) 스프링 빈 의존관계 설정 이후의 준비 완료단계

  • 스프링에서는 빈을 생성하는 단계와 의존관계를 주입하는 단계를 별도로 처리합니다. 하지만 자바 코드로 스프링 빈을 등록할 때, 즉 `@Bean` 어노테이션을 이용할 때는 생성자를 호출하면서 동시에 의존관계 주입도 함께 처리됩니다.
  • 이런 작업은 `@Autowired`와 같은 어노테이션을 이용하여 자동으로 처리할 수 있습니다. 이에 대한 자세한 내용은 '의존관계 자동 주입'에 대해 학습하면서 더 깊게 알아보겠습니다.

2. 스프링 컨테이너에 등록된 모든 빈 조회

1) 컨테이너에 등록된 스프링 빈 조회하기

 위 내용을 통해 스프링 컨테이너를 생성하였으며, 설정(구성) 정보를 참고하여 스프링 빈도 등록하고, 빈들의 의존관계도 설정하였습니다. 이제 스프링 컨테이너에 등록된 빈들이 재대로 등록되었는 지 확인해보기 위해서 스프링 컨테이너에 있는 빈들 모두 조회해보겠습니다.

 

(1) 스프링 빈 조회 방법

 스프링 컨테이너에 등록되어 있는 스프링 빈들을 조회하는 방법에 대해 설명하겠습니다. 이를 위해 JUnit 테스트 코드를 작성하여 스프링 컨테이너에 등록된 빈들을 조회할 수 있습니다.

  AnnotationConfigApplicationContext ac =
            new AnnotationConfigApplicationContext(AppConfig.class);

(2) 모든 빈 조회하기

 스프링 컨테이너에 등록된 모든 빈들을 조회하려면 `AnnotationConfigApplicationContext` 객체`getBeanDefinitionNames` 메서드를 이용합니다. 이 메서드는 스프링 컨테이너에 등록된 모든 빈들의 이름들을 문자열 배열로 반환합니다. 이 이름들을 이용하여 `getBean` 메서드를 호출하면 해당 빈의 객체를 얻을 수 있습니다.

@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        Object bean = ac.getBean(beanDefinitionName);
        System.out.println("name=" + beanDefinitionName + " object=" + bean);
    }
}

(3) 특정 타입의 빈 조회하기 (개발자가 직접 등록한 애플리케이션 빈을 조회하고 싶을 때)

 만약 특정 타입의 빈들만 조회하고 싶다면, `getBeanDefinition` 메서드와 `BeanDefinition` 클래스의 `getRole` 메서드를 이용할 수 있습니다.

  • `getBeanDefinition` 메서드는 특정 빈의 `BeanDefinition` 객체를 반환합니다.
  • `getRole` 메서드는 빈의 역할을 나타내는 값을 반환합니다.
    • => 해당 값이 `BeanDefinition.ROLE_APPLICATION`이면 해당 빈은 개발자가 직접 등록한 애플리케이션 빈을 의미합니다.
    • 해당 값이 `BeanDefinition.ROLE_INFRASTRUCTURE`이면 스프링 내부에서 사용하는 인프라스트럭처 빈을 의미합니다. 따라서 이 값을 이용하여 원하는 타입의 빈들만 출력할 수 있습니다.
@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` 메서드를 이용하면 원하는 타입의 빈들만 손쉽게 조회할 수 있습니다.


 

2) Bean 조회 - 동일한 타입의 빈이 여러 개인 경우

 스프링 컨테이너에서 동일한 타입의 빈이 여러 개 등록된 경우, 빈을 조회하는 방법에 대해 설명하겠습니다.

 

(1) 동일한 타입의 빈이 여러 개인 경우

  • 동일한 타입의 빈이 여러 개인 경우에는 `getBean` 메서드를 사용하면 `NoUniqueBeanDefinitionException`이 발생합니다. => 이는 스프링 컨테이너가 어떤 빈을 반환해야 할지 결정할 수 없기 때문입니다.
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생합니다.")
void findBeanByTypeDuplicate() {
    assertThrows(NoUniqueBeanDefinitionException.class, () ->
            ac.getBean(MemberRepository.class));
}

(2) 빈 이름으로 조회하기 

  • 동일한 타입의 빈이 여러 개인 경우에는 빈의 이름을 지정하여 원하는 빈을 조회할 수 있습니다. `getBean` 메서드의 첫 번째 파라미터에 빈의 이름을 지정하면 됩니다.
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
    MemberRepository memberRepository = ac.getBean("memberRepository1",
            MemberRepository.class);
    assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}

(3) 동일한 타입의 모든 빈 조회하기

  • `getBeansOfType` 메서드를 이용하면 동일한 타입의 모든 빈을 조회할 수 있습니다. 이 메서드는 빈의 이름을 키로, 빈의 객체를 값으로 가진 맵을 반환합니다.
@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);
        // 같은 타입은 2개여야 합니다.
        assertThat(beansOfType.size()).isEqualTo(2);
    }

 

=>  위의 코드에서는 MemberRepository 타입의 모든 빈을 조회하여 그 결과를 출력하고 있습니다. 이 메서드를 이용하면 동일한 타입의 빈이 여러 개일 때도 원하는 빈을 손쉽게 조회할 수 있습니다.


3) Bean 조회 - 상속관계

  스프링 컨테이너에서 부모 타입을 이용하여 빈을 조회하는 방법에 대해 설명하겠습니다. 스프링 컨테이너에서는 부모 타입으로 조회하면, 해당 타입의 자식 타입도 함께 조회됩니다. 이는 자바의 다형성 원칙에 따른 것입니다.

1번 객체의 빈을 조회하면, 모든 자식이 함께 반환됩니다. (즉, 1,2,3,4,5,6,7이 모두 반환됩니다.)

 

(1) 부모 타입으로 조회하기

  • 부모 타입으로 조회하면, 해당 타입의 모든 자식 타입 빈들이 조회됩니다.
  • 만약 동일한 부모 타입을 가진 빈이 여러 개인 경우에는 `getBean` 메서드를 사용하면 `NoUniqueBeanDefinitionException`이 발생합니다.

=> 이는 스프링 컨테이너가 어떤 빈을 반환해야 할지 결정할 수 없기 때문입니다.

@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면, 중복 오류가 발생합니다.")
void findBeanByParentTypeDuplicate() {
    //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
    assertThrows(NoUniqueBeanDefinitionException.class, () ->
            ac.getBean(DiscountPolicy.class));
}

(2) 빈 이름으로 조회하기

  •  동일한 부모 타입을 가진 빈이 여러 개인 경우에는 빈의 이름을 지정하여 원하는 빈을 조회할 수 있습니다.
  • `getBean` 메서드의 첫 번째 파라미터에 빈의 이름을 지정하면 됩니다.
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByParentTypeBeanName() {
    DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy",
            DiscountPolicy.class);
    assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}

(3) 특정 하위 타입으로 조회하기

  • 특정 하위 타입으로 빈을 조회하려면 해당 하위 타입을 파라미터로 `getBean` 메서드를 호출하면 됩니다.
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
    RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
    assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}

(4) 부모 타입의 모든 빈 조회하기

  • `getBeansOfType` 메서드를 이용하면 부모 타입의 모든 빈을 조회할 수 있습니다. 이 메서드는 빈의 이름을 키로, 빈의 객체를 값으로 가진 맵을 반환합니다. 
@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));
    }
}

(5) Object 타입으로 모든 빈 조회하기

  • 만약 Object 타입으로 빈을 조회하면, 스프링 컨테이너에 등록된 모든 빈들이 조회됩니다. 이는 모든 클래스가 Object 클래스를 상속받기 때문입니다.
@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 타입의 모든 빈을 조회하여 그 결과를 출력하고 있습니다. 이 방식을 이용하면 스프링 컨테이너에 등록된 모든 빈들을 손쉽게 조회할 수 있습니다.

3. BeanFactory와 ApplicationContext

 스프링 프레임워크에서는 `BeanFactory``ApplicationContext`라는 두 가지 주요한 컨테이너 인터페이스를 제공합니다. 이 두 인터페이스는 스프링 빈의 생성, 관리, 조회 등의 기능을 담당하며, 필요에 따라 선택하여 사용할 수 있습니다. 그렇다면 BeanFactory와 ApplicationContext에 대해 자세히 알아보겠습니다.

 

(1) BeanFactory

  • `BeanFactory`는 스프링 컨테이너의 최상위 인터페이스로서 가장 기본적인 형태를 가집니다.
  • 이 인터페이스는 스프링 빈의 생성과 관리, 그리고 이름을 통한 빈의 조회 등의 기능을 제공합니다.
  • `getBean()` 메서드를 통해 스프링 빈을 조회할 수 있습니다.
  • `BeanFactory`는 가장 기본적인 기능만을 제공하기 때문에, 보통의 애플리케이션 개발에서는 이보다 더 많은 기능을 제공하는 `ApplicationContext`를 사용합니다.

BeanFactory 인터페이스


(2) ApplicationContext

  • `ApplicationContext`는 `BeanFactory`의 모든 기능을 상속받아 제공하며, 그 외에도 여러 가지 부가 기능을 제공합니다.
  • 이 부가 기능들은 대부분 실제 애플리케이션 개발에 필요한 기능들입니다.
    • 메시지 소스를 활용한 국제화 기능: 사용자의 지역 설정에 따라 다른 메시지를 출력할 수 있습니다.
    • 환경 변수: 애플리케이션의 실행 환경(예: 개발 환경, 테스트 환경, 운영 환경)에 따라 다른 설정을 적용할 수 있습니다.
    • 애플리케이션 이벤트: 이벤트 발생과 이벤트 처리를 위한 이벤트 리스너 등을 지원합니다.
    • 편리한 리소스 조회: 클래스패스, 파일 시스템 등 다양한 위치의 리소스를 쉽게 조회하고 사용할 수 있습니다.

ApplicationContext 인터페이스의 상속구조


(3) 정리

  • 따라서, `BeanFactory`와 `ApplicationContext` 둘 다 스프링 컨테이너라고 할 수 있으며, `ApplicationContext`는 `BeanFactory`의 모든 기능을 포함하면서 추가적인 부가 기능을 제공합니다.
  • 일반적으로 애플리케이션 개발에서는 이러한 부가 기능이 필요하기 때문에 `ApplicationContext`를 더 많이 사용합니다.

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

  • 스프링 프레임워크는 다양한 설정 형식을 지원하여 개발자의 요구에 맞게 유연하게 사용할 수 있습니다. 주로 자바 코드와 XML 형식을 사용하며, Groovy 등 다른 형식도 지원합니다.

 

(1) 자바 코드를 이용한 설정

  • 스프링 프레임워크에서는 애노테이션 기반의 자바 코드를 이용하여 스프링 빈을 등록하고 구성할 수 있습니다.
  • 이 방법은 `AnnotationConfigApplicationContext` 클래스를 사용하며, 설정 정보를 담은 자바 클래스를 인자로 넘겨주면 됩니다.
  • 예를 들어, 다음과 같이 `AppConfig.class`를 인자로 넘겨주어 스프링 컨테이너를 생성할 수 있습니다.
new AnnotationConfigApplicationContext(AppConfig.class)

(2) XML을 이용한 설정

  • 스프링 프레임워크 초기에는 XML을 주로 사용하여 스프링 빈을 등록하고 구성하였습니다. 최근에는 스프링 부트의 등장으로 자바 코드를 이용한 설정이 주로 사용되지만, 여전히 많은 레거시 프로젝트에서는 XML 기반의 설정을 사용하고 있습니다. XML 설정을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있다는 장점이 있습니다.
  • `GenericXmlApplicationContext` 클래스를 사용하며, 설정 정보를 담은 XML 파일을 인자로 넘겨주면 됩니다. 예를 들어, 다음과 같이 `appConfig.xml`을 인자로 넘겨주어 스프링 컨테이너를 생성할 수 있습니다.
package hello.core.xml;

public class XmlAppContext {

    @Test
    void xmlAppContext() {
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

 

  • 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>

 

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

  • 스프링 프레임워크는 다양한 설정 형식을 지원하며, 그 핵심에는 `BeanDefinition`이라는 추상화 개념이 있습니다.
  • 이 `BeanDefinition`은 스프링 빈의 설정 정보를 담고 있으며, 스프링 컨테이너가 스프링 빈을 생성할 때 이 정보를 참고 합니다.

(1) BeanDefinition의 역할

  • `BeanDefinition`은 스프링 빈의 설정 정보를 담는 역할을 합니다.
  • 이 정보에는 빈의 클래스 이름, 빈의 스코프, 빈의 생성자 인자프로퍼티 값 등이 포함됩니다.
  • 스프링 컨테이너는 이 `BeanDefinition` 정보를 읽어서 스프링 빈을 생성하고 관리합니다.
  • 따라서 스프링 컨테이너는 설정 정보가 어떤 형식으로 제공되었는지(예: 자바 코드, XML) 알 필요가 없습니다. 스프링 컨테이너에게 중요한 것은 `BeanDefinition`이 제공하는 빈의 설정 정보뿐입니다.

BeanDefinition 인터페이스의 추상화


(2) BeanDefinition의 생성

  • `BeanDefinition`은 각각의 빈 설정에 대해 하나씩 생성됩니다.
  • 예를 들어, 자바 코드에서 `@Bean` 애노테이션이 붙은 메서드마다, XML에서 `<bean>` 태그마다 `BeanDefinition`이 하나씩 생성됩니다. => 이렇게 생성된 `BeanDefinition`들은 스프링 컨테이너가 관리하며, 이를 통해 스프링 컨테이너는 어떤 빈들이 등록되어 있는지, 각 빈은 어떻게 생성되고 관리되어야 하는지를 알 수 있습니다.
  • 따라서, `BeanDefinition`은 스프링 컨테이너가 스프링 빈을 관리하는 핵심 메커니즘입니다. 이를 통해 스프링은 다양한 설정 형식을 지원하고, 유연한 빈 관리를 가능하게 합니다.

AnnotationConfigApplicationContext 클래스 안에서 AnnotatedBeanDefinitionReader를 확인할 수 있습니다.
GenericXmlApplicationContext 클래스 안에는 XmlBeanDefinitionReader를 확인할 수 있습니다.


(3) BeanDefinition 살펴보기

 위에서는 `BeanDefinition`을 이용하여 각각의 빈 설정을 생성하는 과정을 살펴보았습니다. 이제 실제로 스프링 컨테이너에 등록된 빈들이 어떤 설정 메타정보를 가지고 있는지 확인해보기 위해 테스트 코드를 작성해겠습니다.

package hello.core.beandefinition;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; 
import org.springframework.context.support.GenericXmlApplicationContext;

public class BeanDefinitionTest {
    AnnotationConfigApplicationContext ac = new 
    AnnotationConfigApplicationContext(AppConfig.class); 
    //    GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        
    @Test
    @DisplayName("빈 설정 메타정보 확인") 
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames(); 
        
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition =
            ac.getBeanDefinition(beanDefinitionName);
            
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) { 
                System.out.println("beanDefinitionName" + beanDefinitionName +
                " beanDefinition = " + beanDefinition);
            } 
        } 
    }
}

 

`BeanDefinition`은 스프링 컨테이너가 스프링 빈을 생성, 구성, 관리하기 위해 필요한 정보를 담고 있는 추상화 개념입니다. 각 빈에 대한 설정 정보는 다음과 같습니다.

  • `BeanClassName`: 생성할 빈의 클래스 이름입니다. 팩토리 메서드를 통해 빈을 생성하는 경우에는 이 정보가 없을 수 있습니다. 예를 들어, `@Bean` 애노테이션이 붙은 메서드를 통해 빈을 생성하는 경우가 이에 해당합니다.
  • `factoryBeanName`: 팩토리 역할을 하는 빈의 이름입니다. `BeanClassName` 대신 이를 사용하여 빈을 생성하는 경우에 사용합니다. 예를 들어, `AppConfig`와 같은 설정 클래스의 이름이 될 수 있습니다.
  • `factoryMethodName`: 팩토리 메서드의 이름입니다. 이 메서드를 호출하여 빈을 생성합니다. 예를 들어, `memberService`와 같은 메서드 이름이 될 수 있습니다.
  • `Scope`: 빈의 스코프를 나타냅니다. 기본적으로 싱글톤으로 설정됩니다. 이외에 프로토타입, 웹 관련 스코프 등이 있습니다.
  • `lazyInit`: 이 설정이 `true`인 경우, 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제로 빈을 사용할 때까지 생성을 지연합니다. 이를 통해 빈의 생성 시점을 늦출 수 있습니다.
  • `InitMethodName`: 스프링 빈이 생성되고 의존관계가 적용된 후에 호출되는 초기화 메서드의 이름입니다.
  • `DestroyMethodName`: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드의 이름입니다.
  • `Constructor arguments, Properties`: 빈을 생성할 때 사용할 생성자 인자나 프로퍼티 값입니다. 이 정보를 통해 빈의 의존관계가 정의됩니다. 팩토리 메서드를 통해 빈을 생성하는 경우에는 이 정보가 없을 수 있습니다.

=> 이렇게 `BeanDefinition`을 통해 스프링 컨테이너는 개별 빈의 생성 방법, 초기화 방법, 생명주기 등을 알 수 있습니다. 이 정보를 바탕으로 컨테이너는 빈을 올바르게 생성하고, 관리합니다.


※ ' BeanDefinition'의 정리

  • `BeanDefinition`스프링 컨테이너가 스프링 빈을 생성하고 구성하는 데 필요한 메타데이터를 담고 있는 개념입니다. 이를 통해 스프링 컨테이너는 빈의 생명주기를 제어하고, 의존성을 주입하는 등의 작업을 수행합니다. 개발자는 `BeanDefinition`을 직접 생성하여 스프링 컨테이너에 등록할 수 있습니다.
  • 하지만 실제 개발 실무에서는 보통 이렇게 `BeanDefinition`을 직접 생성하거나 사용하는 일은 거의 없습니다. 스프링 프레임워크가 제공하는 다양한 설정 방법(예: 자바 코드, XML)을 통해 빈을 등록하고, 이러한 설정 정보는 내부적으로 `BeanDefinition`으로 변환되어 스프링 컨테이너에 등록됩니다. 따라서 `BeanDefinition`에 대해 너무 깊게 이해하려고 하지 않아도 됩니다.
  • 중요한 것은 스프링이 다양한 형태의 설정 정보를 `BeanDefinition`이라는 추상화를 통해 통일적으로 다룬다는 사실을 이해하는 것입니다. 스프링의 내부 코드나 스프링을 사용하는 다른 오픈 소스 프로젝트의 코드를 보다 보면 가끔 `BeanDefinition`이라는 이름을 볼 수 있습니다. 이럴 때는 `BeanDefinition`이 스프링 빈의 설정 정보를 담고 있으며, 이를 통해 스프링 컨테이너가 빈을 생성하고 관리한다는 사실을 기억하면 됩니다.