본문 바로가기
Develop/Spring (이론)

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

by 보보트레인 2023. 8. 11.

스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 

그 중심에는 `BeanDefinition` 이라는 추상화가 있다. 

방법1. 자바 코드를 읽어서 BeanDefinition을 만들면 된다.
방법2. XML을 읽어서 BeanDefinition을 만들면 된다.

 

즉, 스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
`BeanDefinition` 을 빈 설정 메타정보라 한다.
`@Bean` , `<bean>` 당 각각 하나씩 메타 정보가 생성된다.
스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

 

BeanDefinition
코드레벨 에서의 구체적인 구조도

1. 자바 코드의 경우 :  AnnotationConfigApplicationContext` 는 `AnnotatedBeanDefinitionReader` 를 사용해서
`AppConfig.class` 를 읽고 `BeanDefinition` 을 생성한다. - BeanFactory를 통해 빈을 등록하는 방법

실제 AnnotationConfigApplicationContext가 정의된 클래스를 확인해 보았다.

 

2. xml의 경우 : GenericXmlApplicationContext` 는 `XmlBeanDefinitionReader` 를 사용해서 `appConfig.xml`
설정 정보를 읽고 `BeanDefinition` 을 생성한다.  - 직접 빈을 등록하는 방법

실제 GenericXmlApplicationContext가 정의된 클래스를 확인해 보았다.

 

3. 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 `BeanDefinition` 을 생성하
면 된다.

 

 

 


BeanDefinition 살펴보기


<BeanDefinition 정보>


- BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)


- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig


- factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService


- Scope: 싱글톤(기본값)


- lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연
  처리 하는지 여부


- InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명


- DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명


- Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용
  하면 없음)

 

 


<실습>

방법1. 자바 코드를 읽어서 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 {

    //자바 코드를 읽어서 BeanDefinition을 등록하는 방법
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean() {
        //name을 모두 뽑아서 배열에 담음
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        //향상된 for문
        for (String beanDefinitionName : beanDefinitionNames) {

            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                System.out.println("beanDefinitionName" + beanDefinitionName + " beanDefinition = " + beanDefinition);
            }
        }
    }
}

 

<실행결과>

beanDefinitionNameappConfig beanDefinition = Generic bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$37c97858]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
beanDefinitionNamememberRepository beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberRepository; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionNamememberService beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionNamediscountPolicy beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=discountPolicy; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionNameorderService beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=orderService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig

Process finished with exit code 0

해석해보면

factoryBeanName에서 factoryMethodName을 찾아 호출한다. 
즉, AnnotationConfigApplicationContext를 사용하면 factoryBean을 통해 Bean이 등록됨을 이해해야한다.

 

→ BeanFactory방식 참고 글 링크 : 

BeanFactory와 ApplicationContext (tistory.com) 

 

BeanFactory와 ApplicationContext

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

iron-mentalman.tistory.com

 

 

방법2. XML을 읽어서 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 {

    //XML을 읽어서 BeanDefinition을 등록하는 방법
    GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean() {
        //name을 모두 뽑아서 배열에 담음
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        //향상된 for문
        for (String beanDefinitionName : beanDefinitionNames) {

            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                System.out.println("beanDefinitionName" + beanDefinitionName + " beanDefinition = " + beanDefinition);
            }
        }
    }
}

 

<실행결과>

beanDefinitionNamememberService beanDefinition = Generic bean: class [hello.core.member.MemberServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNamememberRepository beanDefinition = Generic bean: class [hello.core.member.MemoryMemberRepository]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNameorderService beanDefinition = Generic bean: class [hello.core.order.OrderServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]
beanDefinitionNamediscountPolicy beanDefinition = Generic bean: class [hello.core.discount.RateDiscountPolicy]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [appConfig.xml]

Process finished with exit code 0

해석해보면

factoryBean관련 클래스가 모두 null임을 확인할 수 있다

직접 빈을 xml에 등록하여 xml을 바로 읽어 정의하는 방법이기에 factoryBean관련 클래스/메서드가 사용될 일이 없다. 

반응형