당니의 개발자 스토리
스프링 빈 설정 메타 정보 - BeanDefinition 본문
스프링 빈 설정 메타 정보 - BeanDefinition
이번엔 조금 깊이 있는 내용인데요. Spring Bean의 설정 메타 정보, Bean Definition에 대해서 알아보겠습니다.

스프링은 어떻게 이런 다양한 설정 형식을 지원할까요? XML도 지원하고, Java Config도 지원하는데 비슷하게 하더라도, 크게 애플리케이션에 영향이 없죠.
왜냐하면 그 중심에는 BeanDefinition이라는 추상화가 있기 때문에 그래요. 그러니까 Bean 정보에 대한 거 자체를 추상화 시켜버린 거예요.
쉽게 얘기해서 이것도 역할과 구현을 개념적으로 잘 나눈 거예요.
XML을 읽어서 그냥 BeanDefinition을 만들면 돼요. 또는 자바 코드를 읽어서 BeanDefinition을 만들면 됩니다.
스프링 컨테이너 입장에서는 이게 Java 코드인지 XML인지 몰라요. 오직 BeanDefinition만 알면 돼요. 그걸 기반으로 빈을 생성하고 다 하는 거예요.
이 BeanDefinition을 빈 설정 메타 정보라고 하구요.
Java에서 @Bean 또는 XML에서 <bean>을 하면, 이 빈 하나당 각각 하나씩 메타 정보가 생성된다고 이해하시면 됩니다.
스프링 컨테이너는 이 메타 정보를 기반으로 스프링 빈을 생성하는 겁니다.

그래서 스프링 컨테이너가 BeanDefinition 정보를 가지고 동작한다고 보시면 되구요.
그래서 그림상 스프링 컨테이너 자체는 BeanDefinition에만 의존해요. 스프링 컨테이너는 BeanDefinition이 클래스로 설정된 정보인지, XML로 설정된 정보인지, 아니면 임의로 설정된 건지는 상관을 안하는 거예요.
이 설계 자체를 추상화에만 의존하도록 설계를 한 거예요.

보시면, BeanDefinition 자체가 인터페이스에요. 인터페이스니까 구현체가 있겠죠?

이게 다 BeanDefinition의 구현체입니다.
따라서 스프링 컨테이너는 추상화에만 의존하는 거에요.
이제 코드 레벨로 조금만 더 깊이있게 들어가 볼게요. 조금 더 깊이 알면 좋으니까 하는 거라서, 몰라도 스프링 개발하는데 전혀 지장이 없습니다. 편안하게 들어주세요.


먼저, AnnotationConfigApplicationContext에 들어가면, 뭐가 있냐면

AnnotatedBeanDefinitionReader 라는 게 있죠. AnnotatedBeanDefinitionReader가 뭘 해주냐면, 이 AppConfig.class 설정 정보를 읽어 가지고, BeanDefinition을 만들어냅니다.

AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class에 들어가서, 자바 코드 설정 정보처럼, 메타데이터처럼, XML 읽는 것처럼 똑같이 읽고 BeanDefinition을 생성합니다.

그래서 '이 빈은 memberService고, 객체는 MemberServiceImpl이다.' 이러한 정보를 BeanDefinition에 다 담아놓는 거예요.
정확하게는 빈의 이름을 담는다기 보다, 빈의 어떤 메타 정보들을 다 담아놓는 거예요. 그래서 내가 생성할 클래스는 뭐고 이런 정보들을 다 담아놓습니다.
자 그리고 두번째 했던 GenericXmlApplicationContext 가볼게요.

GenericXmlApplicationContext는 XmlBeanDefinitionReader 라는 게 있죠. XmlBeanDefinitionReader가 appConfig.xml 정보를 가지고 BeanDefinition을 만드는 거예요.

이 xml 같은 경우에는 4개가 만들어지죠. 4개의 BeanDefinition 정보를 만들어요.
그리고 필요하면, XxxBeanDefinitionReader를 만들어서 읽으시면 됩니다. 이메일 형식으로 만들 수도 있어요. 또는 JSON 같은 걸로 읽는 걸 만들 수도 있죠. 그렇게 해서 BeanDefinition을 생성하면 됩니다.

그래서 이 정보를 가지고 BeanDefinition이라는 애를 만든대요. 또는,

이 정보를 가지고 BeanDefinition을 만든대요. BeanDefinition은 빈 당 하나의 설정 정보인 거죠.

그러니까 정리를 하면, 스프링 컨테이너(ApplicationContext)의 구현체 중 하나인 AnnotationConfigApplicationContext가 AnnotatedBeanDefinitionReader를 통해서 AppConfig.class(설정 정보)를 읽고, @Bean 하나당 BeanDefinition을 만듭니다. 그리고 스프링 컨테이너는 이 BeanDefinition(메타정보)을 보고, 스프링 빈(객체)를 생성하고 컨테이너에 등록하는 거죠.
BeanDefinition을 더 자세히 살펴보면,

일단 글로 설명하기 보다, 먼저 코드로 살펴보겠습니다.

test 폴더에 beanDefinition 이라는 패키지를 만들고,

BeanDefinitionTest 라는 클래스를 만들겠습니다.

이렇게 스프링 컨테이너를 만들고, 빈 설정 메타정보 확인이라는 Test를 만들겠습니다. BeanDefinition을 확인하는 테스트예요.

그리고 나서, 먼저 ac에서 getBeanDefinitionNames()를 통해서 BeanDefinition의 이름들을 꺼냅니다. 이전에 했던 거에요.

이렇게 getBeanDefinitionNames()으로, 컨테이너에 등록한 bean의 이름을 배열로 한 이름들을 꺼낼 수 있습니다.
그 다음에 iter + tab 해서,

이 for 문을 돌면서 뭘 할 수 있냐면, ApplicationContext에서 getBeanDefinition()으로 빈 이름에 매치되는 빈 하나의 메타정보를 꺼낼 수가 있어요.

우리가 이전에 모든 빈을 조회할 때 했던 거죠.
그 다음에

이 beanDefinition 정보가 ROLE_APPLICATION 인 경우에만 출력하는 거에요.
ROLE_APPLICATION은 스프링 내부에서 뭔가를 하기 위해서 등록한 빈들이 아니라, 주로 내가 애플리케이션을 개발하기 위해서 직접 등록한 빈들이라고 보시면 됩니다. 아니면 외부 라이브러리나 이런 것들이죠.
자 그래서 이 경우에만 출력을 하게 하는 거에요.
soutv 해서,

beanDefinitionName이랑 beanDefinition 자체를 출력해볼게요. 이제 돌려보면,

beanDefinition을 볼 수 있습니다.

memberService를 보면, 이렇게 scope가 할당이 안되어있으면, 싱글톤이라는 걸로 되어 있다는 겁니다.
그리고 보통 스프링 빈들이 스프링 컨테이너가 뜰 때 다 등록이 되거든요, 근데 그게 아니라 lazyInit은 나중에 실제 사용하는 시점에 스프링 빈을 초기화 하겠다는 의미입니다.
lazyinit 이란?

그래서, lazyinit은 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연 처리 하는지 여부를 나타내는 겁니다.


그리고 autowireMode는 자동 Autowired를 쓰냐, 안쓰냐 에 대한 모드이고, autowireCandidate는 Autowired의 후보가 된다는 정보입니다. 그리고 또

이렇게 되어있는데, 이걸 보고 "아 AppConfig에 있는 memberService 라는 메서드를 호출해서, memberService를 실제 빈으로 생성할 수 있구나" 라고 이해할 수 있습니다.

그리고 초기화 메서드, destroy 메서드 이런 것도 있고, 실제 정의는 hello.core.AppConfig 여기에 되어있다는 것도 알려줍니다.

이 부분을 또 XML로 바꾸면, 다르게 나타납니다.

다르게 나타나는 것을 볼 수 있습니다.
그래서 이러한 정보들이 있습니다.

InitMethodName, DestroyMethodName은 뒤에 빈 라이프 사이클에서 말씀드리겠습니다. 아무튼 그냥 이러한 정보들이 있다는 정도만 이해하면 됩니다.

이렇게 메타정보(beanDefinition)가 있고, 이 메타정보를 기반으로 실제 인스턴스(스프링 빈)를 생성할 수가 있는 거예요.
그래서 정리하면,


BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수도 있어요. 무슨 말이냐면, 내가 직접 new 해서 BeanDefinition 구현체 객체를 만들어가지고, 내가 직접 스프링에 등록할 수도 있어요.
즉, 설정 정보에서 읽어와서 BeanDefinition을 만드는 게 아니라, 내가 프로그래밍을 해서 "이런 빈이 있을거야." 하고 등록할 수도 있어요.
하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없습니다.
그래서 BeanDefinition에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 됩니다.
그런데 가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때, BeanDefinition 이라는 것이 보일 때가 있습니다. 이때 이러한 메커니즘을 떠올리면 돼요.
가끔 스프링 프레임워크 내의 코드나, 외부 라이브러리 중에 스프링을 활용하는 애들 보면, BeanDefinition을 직접 프로그래밍 하는 애들이 있거든요?
그걸 보고 "아 외부 환경 설정 정보가 아니라, 내가 직접 프로그래밍해서 빈에 대한 설정 정보를 스프링한테 직접 등록하는구나" 라고 이해하시면 됩니다. 그리고 어려우신 분들은 그냥 '아 이런게 있나보다' 하고 넘어가셔도 돼요.
이렇게 해서 BeanDefinition까지 알아봤구요.
참고로, 하나 더 짚고 넘어갈 게 있는데

이것처럼 ac를 선언할 때, ApplicationContext로 받아버리면, getBeanDefinition을 못해요. 그래서

둘 다 구현체로 선언한 겁니다.
왜냐면 우리가 실제로 이런 BeanDefinition 정보를 뽑아서 쓸 일이 없기 때문에 그렇죠. 스프링 애플리케이션을 사용하는 입장에서는 이런 getBeanDefinition을 쓸 일이 없기 때문에, ApplicationContext 안에는 이런 복잡한 메서드들이 정의가 안되어 있습니다.

GenericXmlApplicationContext로 바꿔서 돌려보면,

XML로 했을 때는 이런 빈에 대한 정보가 명확하게 등록되어있어요. 그리고 얘는

AnnotationConfigApplicationContext로 했을 때와는 다르게 다 빠져있는데, 무슨 차이가 있냐면

옛날에는 원래 다 XML로 설정을 하면서 이렇게 bean에 대한 클래스가 밖에 다 드러났거든요. 그런데 이게 java config로 바뀌면서 달라졌습니다.
스프링에 빈을 등록하는 방법은 여러가지가 있는데, 진짜 크게 보면 두가지 정도로 정리할 수 있어요. 하나는 직접 스프링 빈을 등록하는 방법이고,

그게 이제 이렇게 직접 스프링 빈을 스프링 컨테이너에 등록하는 방법이 있고,
두번째는 약간 우회해서 하는 방법인데, 팩토리 메서드 라는 걸 쓰는 방법이 있어요.

AppConfig.java 처럼 Java 코드를 가지고 하는 방법이 팩토리 메서드를 통해서 등록하는 방법이라고 해요. 그러니까 팩토리 메서드로 제공한다는 방식은

외부에서 memberService() 메서드를 호출해서 return 뒤에 있는 게 생성되는 방식이에요.
그래서 AnnotationConfigApplicationContext으로 돌렸을 때는

class가 null이죠. 이런 정보가 직접적으로 스프링한테 드러나는 게 아니라,

팩토리 빈의 이름이 appConfig 이고, 팩토리 메서드 이름은 orderService 인 겁니다. 팩토리 빈이라는 애에서 팩토리 메서드를 통해서 orderService가 생성된다고 보면 됩니다.
이 정보들이 AnnotationConfig를 쓰면, 팩토리 빈을 통해서 등록이 되는 방식으로 스프링한테 제공이 됩니다.
이거는 그냥 참고로 알아두시면 되고, 잘 몰라도 상관이 없습니다.
아무튼 정리해보면 스프링은 BeanDefinition 이라는 걸로 스프링 빈의 설정 메타 정보를 추상화한다. 이것만 기억하시면 되구요.
그리고 스프링 빈을 만들 때는 두가지 방법이 있는데, 하나는 직접적으로 스프링 빈을 등록하는 방법과 두번째는 FactoryBean 이라는 걸 통해서 등록하는 방법이 있고,
우리가 일반적으로 Java config를 쓰는 거는 FactoryBean 이라는 걸 통해서 등록하는 방식이라고 이해하시면 됩니다.
그러면 메타정보는 여기서 마치도록 하겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 싱글톤 패턴 (0) | 2024.01.23 |
|---|---|
| 웹 애플리케이션과 싱글톤 (0) | 2024.01.23 |
| 다양한 설정 형식 지원 - 자바 코드, XML (0) | 2024.01.22 |
| BeanFactory와 ApplicationContext (0) | 2024.01.21 |
| 스프링 빈 조회 - 상속 관계 (0) | 2024.01.21 |