본문 바로가기

Backend/Spring

[Spring] 스프링 컨테이너와 빈 3) 다향한 설정 형식 지원(xml)

728x90

 

목차

  • 스프링 컨테이너 생성
  • 컨테이너에 등록된 모든 빈 조회
  • 스프링 빈 조회 - 기본
  • 스프링 빈 조회 - 동일한 타입이 둘 이상
  • 스프링 빈 조회 - 상속 관계
  •  BeanFactory와 ApplicationContext
  • 다양한 설정 형식 지원 - 자바 코드, XML
  • 스프링 빈 설정 메타 정보 - BeanDefinition

 

 

스프링의 유연성

 

스프링은 자바코드 뿐 아니라 다양한 형식의 AppConfig를 지원한다.

xml, 자바코드, Groovy등

지금까지는 어노테이션 기반의 자바코드로 설정정보(Config)를 넘겼다.

다른방식으로도 유연하게 활용가능하다.

그중 xml은 요즘에는 사용하지 않지만, 아직 많은 레거시 프로젝트 들이 xml로 된 경우가 있기 때문에 한번 정도 알고 넘어가도록한다.

 

 

 

Xml을 이용한
AppConfig

 

자바코드가 아닌 xml로 AppConfig.xml을 제작해본다.

자바 코드가 아닌 코드는 resources폴더 아래에 생성하면된다.

 

 

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="disountPolicy" ref="disCountPolicy"/>
    </bean>

    <bean id="disCountPolicy" class="hello.core.discount.RateDiscountPolicy"/>

</beans>

 

형식만 다를 뿐 내용은 완전히 동일하다. 

xml형식의 appConfig
java코드 형식의 AppConfig

 

 

 

이제 xml로 작성된 appConfig를 실제로 테스트해보기 위해 

xml패키지를 만들고 하위에 클래스를 만든다. 

 

XmlAppContext

package hello.core.xml;

import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

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

public class XmlAppContext {


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

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

    }


}

 

테스트는 간단하게 memberService 빈을 가져오는 것으로 하고, 

ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

annotation이 아닌 xml형식으로 ApplicationContext를 생성해준다.

 

실행결과 xml파일을 이용해서도 정상적으로 설정정보를 읽어와 스프링 빈이 잘 등록됨을 알 수 있다.

 

 

xml파일을 이용해도, 자바 파일을 이용해도 설정정보를 올바르게 읽어와 애플리케이션 실행에는 문제가 없음을 확인할 수 있다.

어떻게 이것이 가능할까?

이렇게 다양한 형식을 지원가능한 이유는 무엇일까? 바로, AppConfig를 추상화하여 저장해두기 때문이다.

 

 

 

스프링 빈 설정 메타정보
BeanDefinition

 

 

BeanDefinition : 빈을 추상화해서 역할과 구현을 개념적으로 나누어 저장한 것

다음 그림과 같이, 설정정보(AppConfig)가 자바코드로 들어오든, xml로 들어오든, 그외의 것으로 들어오든 

이 내용을 읽어서 BeanDefinition을 만든다.

스프링 컨테이너는 설정정보 코드를 직접 보지 않고, 어떤 형식인지도 알지 못하며 오직 BeanDefinition을 보고 그 기반으로 스프링 빈을 생성한다.

스프링 컨테이너는 BeanDefinition에만 의존 (추상화에만 의존)

 

BeanDefinition을 빈 설정 메타정보 라고 하고,

java에서는 @Bean annotation / xml에서는 <bean></bean>태그 하나당 하나씩 메타정보고 생성된다.

 

 

코드레벨에서 살펴보면,

ApplicationContext를 반환하는 

각각의 AnnotationConfigApplicationContext / GenericXml ...등 코드의 내부에는

OOReader이 내장되어있다.

이 Reader는 해당 형식의 appConfig를 읽어, BeanDefinition을 생성하는 역할을 한다.

 

만약, 내가 스프링에 존재하지 않는 새로운 형식(OOO)의 appConfig를 사용하고 싶을때는 OOOApplicationContext를 만들고 내부에 OOOBeanDefinitionReader만 구현해내면, 해당 형식의 설정 정보로도 이제 스프링컨테이너를 사용할 수 있는 것이다. 

 

 

 

BeanDefinition

 

실제로 코드로 BeanDefinition을 출력하며 자세히 살펴보자

 

BeanDefinitionTest

package hello.core.beandefinition;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanDefinitionTest {

    //참고 : ApplicationContext에는 getBeanDefinition이 제공되지 않기 때문에, 타입으로 AnnotationConfigApplicationContext를 사용함 
    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);

            }
        }
    }

}

다음과 같이 getRole메소드를 이용해, 스프링자체에서 필요로 생성하는 빈은 제외하고 출력해주었다.

 

결과는 다음과 같이 출력된다 (우리가 appConfig에 정의한 4가지 @Bean과 AppConfig)

beanDefinitionName = appConfig beanDefinition = Generic bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$35971627]; 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 = 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 = 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 = disountPolicy beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=disountPolicy; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
  • BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
    • factoryBeanName(appConfig)에 있는 factoryMethodName(memberService)를 호출하면 이 것을 생성할 수 있음
  • Scope: 싱글톤(기본값)
  • lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부
  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명 
  • DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명 
  • Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음) 

 

 

<정리>

BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. (사용할 일이 별로 없음 가끔 오픈소스에 등장할 때 이런 매커니즘이라는 것만을 이해 ) 

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

 

 

 

스프링 빈 등록
XML vs javacode 

 

*별로 중요하지는 않음 

스프링 빈을 등록하는 방법에는 매우 여러가지가 있지만 , 크게 보면 두가지가 있다. 

  • 직접 스프링 빈을 등록하는 방법(XML config)
  • 팩토리 메소드를 써서 우회하여 등록하는 방법 (java code Config)

둘의 차이는 BeanDefinitionTest의 출력값으로 알아볼 수 있다. 

 

스프링 빈을 직접 등록하는 방법

xml config를 사용하면, 스프링 빈이 직접 등록된다.

등록 방식은 <bean>태그를 이용하는 방법으로, 다음과 같이 표현된다.

    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
    </bean>

 

 

 

GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

XML Config를 사용해 출력한 경우 

class는 특정한 값이 정해져있고,

factoryBeanName 과 factoryBeanMethod에는 null임을 볼 수 있다.

이는 빈을 직접 등록한 것임을 알 수 있는 정보이다.

 

 

팩토리 메소드(factoryBeanMethod)를 써서 우회하여 등록하는 방법

이는 java code(annotation이용시) Config를 사용하는 방법으로, 

 

@Bean annotation이 달린 메소드를 호출해서 스프링빈을 등록하는방식이 바로 팩토리 메소드를 써서 스프링 빈이등록된다고 한다.

다음과 같이 표현된다.

    @Bean
    public MemberService memberService(){ // 메소드 명 : 스프링 빈의 이름 (key)
        return new MemberServiceImpl(memberRepository()); // 리턴 값 : 스프링 빈의 value
    }

 

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

java code Config를 사용해 출력한 경우 

class는 null이고,

factoryBeanName 과 factoryBeanMethod에는 값이 있음을 볼 수 있다.

이는 빈을 직접 등록한 것이 아닌 factoryBeanMethod를 통해 우회하여 등록했음을 알 수 있는 정보이다.

 

728x90