본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring basic

스프링 핵심원리 : 기본편 - BeanDefinition 과 ApplicationContext

by 방구석 대학생 2022. 2. 13.

"인프런의 스프링 핵심원리 - 기본편 강의를 듣고 작성한 글 입니다."

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

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

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

www.inflearn.com

 

BeanFactory 와 ApplicationContext 에 대해 알아보자.

BeanFactory

- 스프링 컨테이너의 최상위 인터페이스이다.

- 스프링 빈을 관리하고 조회하는 역할을 담당하며, getBean() 을 제공한다.

- 지금까지 사용했던 대부분의 기능은 BeanFactory 가 제공하는 기능이다.

 

ApplicationContext

- BeanFactory 기능을 모두 상속 받아서 제공한다.

- 빈을 관리하고 검색하는 기능을 BeanFactory 가 제공해주는데, 그러면 둘의 차이가 뭘까?

- 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.

 

* ApplicationContext 가 제공하는 부가기능

- 메시지 소스를 활용한 국제화 기능

: 에를 들어서, 요청이 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력한다.

- 환경 변수

: 로컬, 개발, 운영 등을 구분해서 처리해준다.

- 어플리케이션 이벤트

: 이벤트를 발행하고 구독하는 모델을 편리하게 지원한다.

- 편리한 리소스 조회

: 파일, 클래스 패스, 외부 등에서 리소스를 편리하게 조회할 수 있다.

 

정리

- ApplicationContext 는 BeanFactory 의 기능을 상속 받는다.

- ApplicationContext 는 빈 관리 기능 + 편리한 부가 기능을 제공한다.

- BeanFactory 를 직접 사용할 일은 없다. 부가기능이 포함된 ApplicationContext 를 사용한다.

- BeanFactory 나 ApplicationContext 를 스프링 컨테이너라 한다.

 

 

스프링 컨테이너는 다양한 설정 형식을 지원한다. - 자바 코드, XML

- 스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있도록 유연하게 설계되어 있다.

- 자바 코드, XML, Groovy 등등을 지원해준다.

어노테이션 기반의 자바 코드 설정 사용

- 지금까지 해왔던 것들이다.(new AnnotationConfigApplicationContext(AppConfig.class); )

- AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로 된 설정 정보를 넘기면 된다.

 

XML 설정 사용

- 최근에는 스프링 부트를 많이 사용하면서 XML 기반의 설정은 잘 사용하지 않는다. 그러나 아직 많은 레거시 프로젝트 들이 XML 로 되어있고, 또 XML 을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있으므로 한번쯤 배워두는 것도 괜찮다.

- GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다.

 

test 폴더에서 hello.core.xml 패키지 아래에 XmlAppContext 클래스를 만들고 다음과 같이 코드를 작성한다.

- XmlAppContext.java

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 기반의 스프링 빈 설정 정보는 아래와 같다.

경로는 src/main.resources/appConfig.xml 이다.

- appConfig.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>

- xml 기반의 appConfig.xml 스프링 설정 정보와 자바 코드로된 AppConfig.java 설정 정보를 비교해보면 거의 비슷하다는 것을 알 수 있다.

- xml 기반으로 설정하는 것은 최근에 잘 사용하지 않으므로 이정도로 마무리하고, 필요하면 스프링 공식 레퍼런스 문서를 확인하자.

https://spring.io/projects/spring-framework 

 

Spring Framework

 

spring.io

 

 

스프링 빈 설정 메타 정보 BeanDefinition

스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 BeanDefinition 이라는 추상화가 있다.

쉽게 이야기해서, 역할과 구현을 개념적으로 나눈것이다.

- XML 을 읽어서 BeanDefinition 을 만들면 된다.

- 자바 코드를 읽어서 BeanDefinition 을 만들면 된다.

- 스프링 컨테이너는 자바 코드인지 XML 인지 몰라도 된다. 오직 BeanDefinition 만 알면 된다.

 

* BeanDefinition 을 빈 설정 메타 정보라고 한다.(BeanDefinition 자체가 인터페이스 이다.)

- @Bean, <bean> 당 각각 하나씩 메타 정보가 생성된다.

- 스프링 컨테이넌는 이 메타정보를 기반으로 스프링 빈을 생성한다.

 

코드 레벨로 좀 더 깊이있게 들어가보자.

- AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성한다.

- GenericXmlApplicationContext 는 XmlBeanDefinitionReader 를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성한다.

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

 

test 폴더 아래 hello.core.beandefinition 패키지에 BeanDefinitionTest 클래스를 만들고 다음과 같이 코드를 작성하자.

- BeanDefinitionTest.java

public class BeanDefinitionTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @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);
            }
        }
    }
}

AppConfig 클래스를 기반으로 스프링 컨테이너를 만들고, 만들어진 스프링 컨테이너에서 빈 객체들의 이름을 기준으로 하는 문자열 배열을 반환받는다.

이후 반복문에서 각 빈 객체 이름을 토대로 해당 빈의 메타 정보를 가져온 다음, 해당 정보의 타입이 ROLE_APPLICATION(개발자가 직접 정의한 객체) 이면 출력문에 작성한 대로 해당 빈 객체의 이름과 메타 정보 값을 출력해준다.

 

결과 :

beanDefinitionName = appConfig beanDefinition = Generic bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$2dfd6684]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

beanDefinitionName = memberService 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

beanDefinitionName = orderService 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

beanDefinitionName = memberRepository 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

beanDefinitionName = discountPolicy 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

 

BeanDefinition 살펴보기

* BeanDefinition 정보

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

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

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

- Scope : 싱글톤(값이 할당되지 않았을 경우 기본값)

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

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

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

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

 

정리

- BeanDefinition 을 직접 생성해서 스프링 컨테이너에 등록할 수도 있다. 하지만 실무에서 BeanDefinition 을 직접 정의하거나 사용할 일은 거의 없다. -> 어려우면 그냥 넘어가자.

- BeanDefinition 에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition 으로 추상화해서 사용하는 것 정도만 이해하면 된다.

- 가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때, BeanDefinition 이라는 것이 보일 때가 있다. 이때 이러한 메커니즘을 떠올리면 된다.