jhhan의 블로그

스프링 코어(3) - 스프링 컨테이너 & 빈 본문

Spring

스프링 코어(3) - 스프링 컨테이너 & 빈

jhhan000 2021. 1. 24. 00:48

저번 포스트까지는 스프링 빈을 조회하는 것에 대해 알아봤습니다.

 

이번에는 개념적인 부분에 대해서 좀 더 알아보겠습니다.

 

ApplicationContext를 지금까지 자주 써왔는데

이것보다 더 근본적인 녀석이 있습니다.

바로 BeanFactory입니다.

상속 관계입니다.

  • BeanFactory는 인터페이스입니다.
  • BeanFactory를 상속하는 ApplicationContext가 있습니다. 역시 인터페이스입니다.
  • 그리고 나중에 ApplicationContext를 구현하는 AnnotationConfigApplicationContext가 등장합니다.

이런 상속관계를 가집니다.

그럼 BeanFactory에 대해 좀 더 알아보겠습니다.

  • 스프링 컨테이너의 최상위 인터페이스
  • 스프링 빈 - 관리 & 조회 가능  ex) getBean 메서드 제공
  • ApplicationContext를 정의하고 사용했었는데, 대부분이 BeanFactory에 정의된 기능들입니다.

 

  • BeanFactory와 ApplicationContext를 가리켜서 스프링 컨테이너라고 합니다.
  • ApplicationContext는 BeanFactory를 상속받기 때문에 
  • BeanFactory의 빈 관리 & 조회 기능 뿐만 아니라 그 외의 부가기능들도 제공합니다.
  • 그렇기 때문에 BeanFactory를 사용하지 않는 것입니다.
  • BeanFactory를 직접 사용하는 일은 거의 없다고 생각해도 될 것 같습니다.

 

그리고 ApplicationContext에 대해서 조금 더 알아보면

ApplicationContext는 다양한 인터페이스를 상속받습니다.

  1. MessageSource: 국제화 기능. 한국에서는 한국어로, 영어권에서는 영어로 출력하게 한다.
  2. EnvironmentCapable: 실제 개발 환경에서는 local, development, production 등으로 구분해서 진행하는데,
                                그 단계 구분 & 처리를 할 수 있게 한다.
  3. ApplicationEventPublisher: 이벤트를 발행 & 구독하는 모델을 편리하게 지원한다.
  4. ResourcePatternResolver: file, classpath 등에서 리소스를 편리하게 조회하는 기능 지원
  5. ListableBeanFactory: BeanFactory를 상속받음
  6. HierarchicalBeanFactory: 계층 별로 나누어서 특정 계층에 포커싱할 수 있음

해석을 간단히 해서 더 알고 싶다면 따로 검색해보시면 될 것 같습니다..

 

이렇게 스프링 컨테이너에 대해 조금 알아봤습니다.

 

다음으로는 XML 설정에 대해서 알아보죠

  • 스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 되어 있습니다.
  • ex) Java, XML, Groovy 등 형식이 다르더라도 설정이 가능합니다.
  • XML은 스프링 부트가 도입된 후 잘 사용하지 않는 방식입니다.
  • 알아두면 좋을 것 같습니다.
  • 아직 xml로 올라오는 블로그도 많고, 오래 전에 작성한 코드들 역시 xml로 작성된 것이 많기 때문에
  • xml 설정을 알아두는 것도 괜찮습니다.

먼저 프로젝트 구조입니다.

resources/appConfig.xml 파일을 만들 것이고,

test/java/hello.core.xml/XmlAppContext 파일 역시 만듭니다.

 

먼저 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 코드는 조금은 복잡합니다.

<beans> 태그는 복사해서 사용하는 것이 좋을 것 같습니다... 너무 길어서요..

그 안에는 4가지의 빈이 설정되어 있습니다.

  • memberService, memberRepository, orderService, discountPolicy → 4개를 설정했습니다.
  • constructor-arg의 경우 빈 주입을 받을 때 사용됩니다.
  • 조금만 자세히 살펴보면, AppConfig.java 파일과 다를 것이 하나도 없습니다!
@Configuration
public class AppConfig {
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
}

실제 AppConfig.java 파일입니다.

이제 XmlAppConfig 파일을 보시죠

import static org.assertj.core.api.Assertions.*;

public class XmlAppContext {

    @Test
    void xmlAppContext() {
        /**
         *  설정정보가 class 파일 -> xml 파일로 바뀌었을 뿐 다른건 없다.
         *  나머지 코드는 동일함.
         */
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

코드를 살펴보면 appConfig.xml 파일로 바뀌었다는 것 말고는 달라진 것이 없습니다.

그리고 이 코드를 실행해보면 정상 작동을 한다는 것을 알 수 있습니다.

 

xml 설정을 하면 좋은 점은 -> 컴파일 없이 Bean 설정 정보를 변경할 수 있다는 것입니다.

그래도 xml 설정은 최근에 하지 않는 편입니다. 주로 java code로 설정을 진행합니다.

혹시 더 알고 싶다면 스프링 공식 문서를 확인하시면 됩니다.

spring.io/projects/spring-framework

 

 

그럼 여기서 들 수 있는 생각이 스프링은 어떻게 다양한 설정 형식을 지원하는 것일까? 입니다.

그 이유는 BeanDefinition이라는 추상화를 통해서 가능!

  • 다시 또 역할과 구현이라는 개념으로 들어갑니다.
  • xml 코드를 통해서 BeanDefinition을 만듭니다.
  • java code를 통해서 BeanDefinition을 만듭니다.
  • BeanDefinition을 빈 설정 메타정보 라고도 함.
    → @Bean or <bean> 을 통해 메타 정보가 생성됨.
    → 스프링 컨테이너가 이 메타정보를 통해 스프링 빈 생성

결국 스프링 컨테이너는 추상화에만 의존합니다.

BeanDefinition에는 다양한 정보가 들어있습니다.

  1. BeanClassName: 생성할 빈의 클래스 명
  2. factoryBeanName: 팩토리 역할의 빈을 사용할 경우  ex) appConfig
  3. factoryMethodName: 빈을 생성할 팩토리 메서드 지정  ex) memberService
  4. scope: 싱글톤(default)
  5. lazyInit: 실제 빈을 사용할 때까지 최대한 생성을 지연처리 하는지 여부
  6. InitMethodName: 빈 생성, 의존관계 적용 뒤 호출되는 초기화 메서드 이름
  7. DestroyMethodName: 빈 제거 직전 호출되는 메서드 이름
  8. Constructor argument: 의존관계 주입시 사용.  팩토리 역할의 빈 사용시 없음

그 외에 더 담겨있는 것들이 있습니다.

더 자세히 알고 싶다면 BeanDefinition에 대해 더 검색을 진행하시면 될 것 같습니다...

 

BeanDefinition 파일을 만듭니다.

public class BeanDefinitionTest {

    @Test
    @DisplayName("Bean 설정 메타정보 확인 - java config")
    void findApplicationBean1() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

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

    @Test
    @DisplayName("Bean 설정 메타정보 확인 - xml")
    void findApplicationBean2() {
        GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

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

코드를 위와 같이 적습니다.

그리고 실행해본다면..

굉장히 나오는 내용이 많습니다...

그리고 좀 자세히 살펴보시면 나오는 내용이 java와 xml에 따라 조금씩 다릅니다.

특히 java config의 경우 FactoryBean을 이용해서 등록하는 방식이기 때문에

factoryBeanName과 factoryMethodName이 null로 적히지 않습니다.

xml로 설정할 경우 2개가 null로 나오게 됩니다. 직접 등록하기 때문입니다.

 

BeanDefinition 마무리

  • BeanDefinition은 스프링의 빈 설정 메타정보를 추상화 한다.
  • 스프링 빈을 만드는 2가지 방법
    1. 직접 등록 - xml
    2. FactoryBean을 이용해서 등록 - java config
  • BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 있습니다.
  • 하지만 실제로는 그럴 일은 없다고 봐도 됩니다.
  • 그래서 모른다면 모르는데로 넘어가도 됩니다.
  • BeanDefinition으로 추상화해서 사용한다 라는 정도만 이해하면 될 것 같습니다.
  • 스프링 관련 오픈소스 를 볼 때 BeanDefinition을 사용한 것을 볼 수도 있음. 

 

이렇게 BeanDefinition까지 살펴봤습니다.

 

어렵네요. 그래도 실제 개발에서는 사용되지 않아서 다행입니다.

 

 

이렇게 스프링 & 빈 포스트를 마치겠습니다.

 

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한