Spring/SpringCore - basic

스프링 핵심원리 - 스프링 컨테이너 설계 이해, 스프링의 다양한 설정 형식 지원(JAVA/Xml), 스프링 빈 메타 정보(BeanDefinition)

_Jin_ 2024. 12. 28.

 

Repeat is the best medicine for memory

 

 

앞서, 스프링으로 전환과 더불어 스프링 컨테이너의 작동 원리에 대해서 알아보고 있었다.

그리고 컨테이너 생성에 있어 ApplicationiContext 부모 타입을 사용해왔고, 구현체로는 AnnotationConfigApplicationContext을 사용하였다.

 

이번에는 이러한 컨테이너가 어떻게 설계되었는 가에 대해서 살펴보며, 동시에 다양한 설정 형식을 지원해주는 구조를 지닌 스프링의 설계적 측면을 파악하며 알 수 있는 BeanDefinition의 역할을 알아보자.

 

스프링 컨테이너의 설계 ( 1 ) - BeanFactory

지금까지 사용해온 ApplicationiContext는 사실 BeanFactory라는 부모타입을 상속한 인터페이스이다.

 

이 BeanFactory는 다음과 같은 특징을 지닌 인터페이스에 해당한다. 

✅ 스프링 컨테이너 최상위 인터페이스
✅ 스프링빈을 관리하고 조회하는 역할 담당
✅ 이전에 호출했던 getBean() 메소드와 같이 사용한 대부분의 기능은 빈팩토리에서 제공하는 기능이다.

 

그리고 ApplicationContext는 BeanFactory 기능을 모두 상속받아서 제공하는 컨테이너에 해당한다.

그런데, Bean을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데, ApplicationContext가 상속받아 사용하는 이유는 무엇일까??

 

애플리케이션을 개발함에 필요한 기능들은 단순하게 Bean 을 관리하고 조회하는 것을 넘어서 수 많은 기능들이 필요하다.

ApplicationContext의 내부 코드와 구조를 파악해보면 아래와 같음을 알 수 있다.

package org.springframework.context;

import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

그리고 각 인터페이스에서 정의하는 기능은 다음과 같다.

 

정리하자면, 스프링 컨테이너에 해당하는 ApplicationContext는 BeanFactory와 더불어 다양한 인터페이스로 정의한 부가 기능을 탑재한 스프링 컨테이너이자 인터페이스인 것이다.

 

스프링 컨테이너의 설계 ( 2 ) - 다양한 설정 형식 지원

스프링 컨테이너를 설정함에 다양한 형식을 사용할 수 있는데, 지금까지는 Java 코드를 사용하여 구성했지만 이외에도 Xml 파일이나 더 나아가서는 임의로 만든 설정을 사용할 수도 있다.

 

그리고 다양한 설정을 지원함에는 아래와 같은 스프링 컨테이너의 설계가 있기에 가능하다. 

물론, 최근에는 Java 코드를 사용하는 경우(Annotation)가 대부분이지만 이전에는 Xml을 많이 사용해왔다. 따라서 이를 사용한 환경 설정 방법에 대해서도 알아보자.

 

xml 파일을 사용하는 경우에 대한 테스트 코드를 작성해보자.

먼저, GenericXmlApplicationContext 구현체를 사용해서 스프링 컨테이너를 생성하고 빈을 조회하는 테스트 코드의 부분이다.

 

public class XmlAppContext {

    @Test
    void xmlAppContext() {
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

그리고 xml 파일, 즉 설정 정보가 담긴 appConfig.xml을 만들어 주는데, 자바 코드를 사용하는 경우와 달리 xml 파일은 resources 폴더 하위에 생성하여 사용한다.

해당 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="com.example.demo.member.MemberServiceImpl">
    <constructor-arg name="memberRepository" ref="memberRepository"/>
  </bean>

  <bean id="memberRepository" class="com.example.demo.member.MemoryMemberRepository">
  </bean>
  
  <bean id="orderService" class="com.example.demo.order.OrderServiceImpl">
    <constructor-arg name="memberRepository" ref="memberRepository"/>
    <constructor-arg name="discountPolicy" ref="discountPolicy"/>
  </bean>

  <bean id="discountPolicy" class="com.example.demo.discount.RateDiscountPolicy">
  </bean>


</beans>

 

앞서, AppConfig.java와 비슷하다.

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(MemberRepository());
    }
    @Bean
    public MemberRepository MemberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(MemberRepository(), DiscountPolicy());
    }
    @Bean
    public DiscountPolicy DiscountPolicy() {
        return new RateDiscountPolicy();
    }
}

 

BeanDefinition을 통한 다양한 설정 환경 지원

그렇다면, 스프링은 어떻게 이런 다양한 설정 형식을 지원할까??

 

BeanDefinition이 있기 때문이다. BeanDefinition은 스프링 컨테이너가 설정 파일이 Java 코드이든, XML 이든 상관없이 BeanDefinition만 알면 되도록 추상화 하였다.

 

컨테이너를 생성함에 어떤 구현체를 사용해도 각 구현체는 Reader를 사용해서 BeanDefinition을 생성하고, 이에 따른 컨테이너 설정 정보를 가진다.

 

그리고, 빈 메타 정보들을 살펴보기 위한 테스트 코드는 아래와 같다.

public class BeanDefinitionTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 설정 메타정보 확인")
        //BeanDefinition 확인
    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);
            }
        }
    }
}

 

출력 결과

beanDefinitionName = appConfig  beanDefinition = Generic bean: class [com.example.demo.AppConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.AppConfig

 

위의 출력을 통해 얻을 수 있는 메타 정보는 아래와 같다.

 

댓글