레퍼런스 Spring 2.0의 XML확장기능 (2)

황제낙엽 2007.08.15 07:38 조회 수 : 73 추천:102

sitelink1  
sitelink2  
sitelink3  
extra_vars6  
http://toby.epril.com/?p=277

Spring 설정파일은 결국 bean을 어떻게 등록할 것인가를 결정한다. 따라서 설정파일의 확장기능은 bean등록방법을 효과적으로 확장한 기능이다. 어찌됐든 결과적으로 bean이 등록이 되므로 XML확장기능을 잘 사용하려면 Spring의 기본적인 bean등록기능을 알아야 한다.

Spring에 등록되는 bean은 다양한 기준으로 구분될 수 있다.

가장 대표적인 구분은 bean이 root bean인가 child bean인가하는 것과 top leve bean인가 inner bean인가 이다. 각각 조합하면 4가지가 가능하겠다.

이 기준에 따라 custom tag 한개를 기준으로 등록되는 빈이 top level이 몇개인지, 각각의 빈이 root인지 child인지로 구분해서 생각해볼 수 있다.

또 bean의 property가 primitive type으로만 되어있는지 다른 bean에 대한 ref.를 가지고 있는지도 생각해볼 수 있다.

Tag당 bean이 여러개라면 그 그룹안에서 서로 의존관계가 있는지 parent/child관계가 있는지 의존관계가 top level끼리인지 top/inner사이에 있는지 아니면 해당 tag로 등록되는 bean들외에 다른 bean에 대한 참조를 가질 수 있는지 등으로 구분해서 생각해볼 수 있다.

복잡해보이지만 사실 Spring의 bean의 종류를 잘 알면 구분하는 것이 어렵지 않다.

 

BeanDefinitionParser

새로운 tag를 추가할 때 필요한 코드의 핵심은 BeanDefinitionParser이다. BeanDefinitionParser는 Spring설정 XML을 분석해서 bean의 메타정보인 BeanDefinition을 만들어내는 역할을 담당한다.

public interface BeanDefinitionParser { 
  BeanDefinition parse(Element element, ParserContext parserContext);
}

파라메터로 넘어오는 것은 XML element 정보와 현재 XML의 parsing을 담당하고 있는 파서에 대한 컨텍스트정보이다. 이를 이용해서 생성해야 할 bean에 대한 등록정보인 BeanDefinition을 넘겨준다.

BeanDefinitionParser를 구현한 Spring내의 클래스는 sandbox에 있는 것과 inner class를 포함해서 30개 가까이 된다. BeanDefinitionParser를 직접 구현하는 것이 어렵지는 않지만 개발자가 직접 custom tag를 개발할 때 유용한 몇가지 abstract class등은 알아두는 것이 좋다.

이중 가장 손쉽게 활용할 수 있는 것은 AbstractBeanDefinitionParser이다.

AbstractBeanDefinitionParser는 단일 top-level  빈을 가진 한개 또는 여러개의 빈을 생성할 때 사용할 수 있다. 단일 top-level을 가졌다는 뜻은 bean id를 직접 부여할 수 있는 것이 한개뿐이라는 것이다. 물론 top-level이지만 id가 없을 수도 있다. 해당 custom tag에서는 top-level이지만 다른 빈에 중첩되는 것은 가능하다는 뜻이다. 나머지는 inner bean으로 자동등록된다. 사실 inner bean이라고 id가 없는 것은 아니다. 단지 개발자가 지정하지 못하고 자동생성될 뿐이다.

AbstractBeanDefinitionParser의 서브클래스중 AbstractSingleBeanDefinitionParser는 딱 한개의 top-level 빈만을 생성하는 경우 사용할 수 있다.

더 나가서 AbstractSimpleBeanDefinitionParser는 애트리뷰트와 프로퍼티를 일치하게 하는 (결국 XML파싱과 pvs설정의 수고를 좀 덜어주는) 방식을 사용하는 가장 간단한 케이스에 사용할 수 있는 클래스이다.

각 클래스의 사용법은 친절한 API와 Spring에서 사용한 케이스를 분석해보면 사용법을 간단히 알 수 있다.

 

AbstractSingleBeanDefinitionParser

Spring Reference에 나오는 케이스를 간단히 보자.

만들려고 하는 tag는 SimpleDateFormat 타입의 빈을 생성하고 애트리뷰트로 pattern을 지정할 수 있는 <x:dateformat /> 태그이다.

네임스페이스는 myns로 설정하고 작업한 예를 보면

<myns:dateformat id=”myDateFormat” pattern=”yyyy-MM-dd” />

글로벌한 설정이 아니라 구체적으로 다른 빈에서 사용해야할 빈이기 때문에 id를 직접 지정할 수 있게 한다.

이 XML element를 읽어서 BeanDefinition을 돌려주는 코드는 다음과 같다.

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

  public void doParse(Element element, BeanDefinitionBuilder bean) {
    String pattern = element.getAttribute(”pattern”);
    bean.addConstructorArg(pattern);
    String lenient = element.getAttribute(”lenient”);
    if (StringUtils.hasText(lenient)) {
      bean.addPropertyValue(”lenient”, Boolean.valueOf(lenient));
    }
  }

  protected Class getBeanClass(Element arg0) {
     return SimpleDateFormat.class;
  }
}

단일 빈을 생성할 때 사용하는 AbstractSingleBeanDefinitionParser를 이용하면 builder패턴을 적용해서 BeanDefinition을 손쉽게 다룰 수 있는 BeanDefinitionBuilder를 파라메터로 넘겨준다. 남은 할 일은 element에서 tag정보(<myns:dateforamt… />)를 읽어서 bean 메타정보를 추가해주는 것이다. 재밌게도 SimpleDateFormat은 생성자를 이용해서 패턴정보를 받는다. 따라서 addConstructorArg()를 이용해서 constructor setter설정을 추가한다. BeanDefinitionBuilder를 사용하는 것은 <bean id … class ..>를 코드에 의해서 설정한다고 생각하고 하면 아주 간단하다.

 

AbstractBeanDefinitionParser

AbstractBeanDefinitionParser은 한개 또는 여러개 빈을 설정할 때 사용한다. 단 top-level(id부여가능) 빈은 하나뿐이다.

이를 이용한 bean definition parser가 구현해야 할 것은 다음의 template method이다.

protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);

XML element정보와 parser context를 받는 것은 동일하다. 리턴하는 것은 단일 BeanDefinition정보이다. 여기서 리턴하는 것이 top-level의 bean이다. 만약 해당 custom tag에 id를 부여했을 경우 이 id를 통해서 가져오는  바로 그 빈이다.

그럼 다른 부가적인 빈이 있다면? 그것은 inner bean의 형태로 등록이 된다. inner bean이 된다는 것은 top-level빈의 property로 inner bean이 설정된다는 것이다. inner bean의 등록에는 중첩정도나 갯수의 제한이 없다.

테스트하려고 만든 간단한 bean설정을 보자.

BeanA는 BeanB에 의존적인 간단한 bean이다.

public class BeanA {
  public String value;
  BeanB beanB;
  ..
}

BeanB는 아주 간단한 빈이다.

public class BeanB {
  String value;
  …
}

다음과 같은 태그를 써서 id가 부여된 BeanA가 만들어지고 동시에 BeanB도 하나 만들어져 inner bean으로 BeanA에 연결이 되게 하려고 한다. 동시에 BeanA와 BeanB의 value값은 직접 다 설정할 것이다. 결과는 다음과 같은 tag 사용이 가능하다.

<myns:beanab id=”myBeanA” valuea=”A” valueb=”B” />

Custom tag를 사용하지 않는다면

<bean id=”myBeanA” class=”…BeanA”>
  <property name=”value” value=”A” />
  <property name=”beanB”>
     <bean class=”…BeanB”>
        <property name=”value” value=”B” />
     </bean>
  </property>
</bean>

이라고 써야할 것이다.

bean설정을 직접 만들 때는 BeanDefinition을 구현한 클래스의 인스턴스를 생성하면 된다.

BeanDefinition에는 크게 RootBeanDefinition과 ChildBeanDefinition이 있다. Child는 parent를 가진 상속 bean이라고 보면 된다.

여기서는 당연히 RootBeanDefinition을 사용하면 된다.

생성방법은 1) RootBeanDefinition 인스턴스를 만들고 2) Source를 세팅하고 3) 필요한 프로퍼티를 추가한다. 아주 간단하다.

BeanB를 생성하는 코드는

RootBeanDefinition beanBDef = new RootBeanDefinition(BeanB.class);
beanBDef.setSource(parserContext.extractSource(element));
beanBDef.getPropertyValues().addPropertyValue(”value”, element.getAttribute(”valueb”));

마지막 줄의 addPropertyValue를 통해서 이 생성된 빈의 실제 프로퍼티값을 추가한다. 값은 XML attribute에서 가져오면 된다. 이렇게 BeanB에 대한 생성이 완료됐다. 하지만 아직 이 빈이 BeanFactory에 등록된 것은 아니다. 메타정보만 생성한 것이다.

다음은 BeanA이다.

RootBeanDefinition beanADef = new RootBeanDefinition(BeanA.class);
beanADef.setSource(parserContext.extractSource(element));
beanADef.getPropertyValues().addPropertyValue(”value”, element.getAttribute(”valuea”));
beanADef.getPropertyValues().addPropertyValue(”beanB”, beanBDef);

BeanB와 설정방법은 비슷하다. 다만 BeanA는 BeanB타입의 레퍼런스 프로퍼티를 가지고 있다. 이 것을 설정하는 것은 위에서 만든 BeanB설정정보를 addPropertyValue에 넣어주면 된다. 역시 간단하다.

이렇게 해서 BeanA, BeanB의 설정을 마쳤다. 여기서 top-level인 BeanA의 설정을 리턴해주면 된다.

이렇게해서 완성된 코드는

public class BeanABBeanDefinitionParser extends AbstractBeanDefinitionParser {
protected AbstractBeanDefinition parseInternal(Element element,
ParserContext parserContext) {
  // beanB
  RootBeanDefinition beanBDef = new RootBeanDefinition(BeanB.class);
  beanBDef.setSource(parserContext.extractSource(element));
  beanBDef.getPropertyValues().addPropertyValue(”value”, element.getAttribute(”valueb”));

 // beanA
  RootBeanDefinition beanADef = new RootBeanDefinition(BeanA.class);
  beanADef.setSource(parserContext.extractSource(element));
  beanADef.getPropertyValues().addPropertyValue(”value”, element.getAttribute  (”valuea”));
  beanADef.getPropertyValues().addPropertyValue(”beanB”, beanBDef);

  return beanADef;
}
}

parseInternal()을 호출한 AbstractBeanDefinitionParser의 코드에서는 리턴된 beanA의 설정정보를 BeanFactory에 등록을 한다.

그럼 BeanB는?

BeanB는 AbstractBeanDefinitionParser의 shouldFireEvents 프로퍼티가 세팅되어있는 경우에만 등록을 한다.

개념은 이렇다. BeanA는 해당 custom tag의 top-level bean이다. 만약 태그 하나의 두개 이상의 빈이 동시에 등록되는 것이라면 이 빈들은 하나의 component같은 개념으로 묶일 수 있다. 이에 따라 Spring 2.0에는 BeanComponentDefinition이라는 새로운 그룹핑된 빈설정 클래스를 추가했다. shouldFireEvents 프로퍼티는 top-level의 모든 inner bean과 ref. bean을 조사해서 그것을 bean component개념으로 묶어주고 전체 component에 속한 bean을 모두 BeanFactory에 등록해준다.

어쨌든 역시 심플하게 만들 수 있다. Parser를 만드는 것보다 사실 tag/bean관계를 설계하는 것이 더 어렵고 중요한 작업일 것이다.

AbstractBeanDefinitionParser의 기본등록작업을 처리하는 코드를 잘 살펴보면 BeanDefinition에 id정보를 추가해서 BeanDefinitionHolder에 넣고 이를 BeanDefinitionReaderUtils.registerBeanDefinition()에 넘겨서 최종적으로 등록을 한다. 등록할 registry(bean factory)정보를 참조하기 위해서 parser context에서 registry정보를 가져와 함께 넘긴다.

BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());

이 작업이 모든 inner bean, ref bean에 일어나는 과정을 미리 만들어 둔 것이 AbstractBeanDefinitionParser이다.

그렇다면 하나의 태그로 여러개의 빈을 만드는데 서로 ref.관계가 없는 id를 가진 top-level빈을 여러개 만들어야 한다면 어쩔 것인가? 이때는 AbstractBeanDefinitionParser를 쓸 수 없다. 이 경우라면 직접 BeanDefinitionParser인터페이스를 구현해서 작업을 해야 한다.

직접 등록까지 마쳐야 하니 매 빈에 걸쳐서 다음의 작업을 해야한다.

  1. BeanDefinition 생성
  2. ID를 부여하고 BeanDefinitionHolder를 생성
  3. Registry에 BeanDefinitionHolder를 등록

물론 inner bean등이 있다면 좀 더 복잡한 등록작업을 해야한다.

사실 하나의 태그로 직접 연관관계가 없는 빈들을 등록하는 것은 일반적인 케이스가 아니기 때문에 이를 수월하게 해줄 Spring 클래스는 없다. 이런 경우는 게으른 개발자(나)가 convention이 잘 부여된 비슷한 이름이나 클래스를 가진 여러개의 빈 등록을 한방에 하기 위해서 사용할 때 쓰게 될 것이다.

샘플 예를 보자.

이번엔 BeanC를 정의했다.

public class BeanC {
  String value; 
  …
}

 

BeanDefinitionParser 직접 이용하기

하나의 태그로 BeanC타입의 빈을 각각 다른 아이디를 부여해서 두개를 만드는 tag를 만들어보자.

<myns:twobeanswithid id1=”beanC1″ id2=”beanC2″ value1=”A” value2=”B” />

이렇게 정의하면

<bean id=”beanC1″ class=”sample.namespace.BeanC”>
   <property name=”value” value=”A” />
</bean>
<bean id=”beanC2″ class=”sample.namespace.BeanC”>
   <property name=”value” value=”B” />
</bean>

이렇게 만들어지는 것이다.
 
Parser는 다음과 같이 만들면 된다.

public class TwoBeansWithIdBeanDefinitionParser implements BeanDefinitionParser {

public BeanDefinition parse(Element element, ParserContext parserContext) {
  RootBeanDefinition bean1 = new RootBeanDefinition(BeanC.class);
  bean1.setSource(parserContext.extractSource(element));
  bean1.getPropertyValues().addPropertyValue(”value”, element.getAttribute(”value1″));
  BeanDefinitionHolder beanDefinitionHolder1 =
    new BeanDefinitionHolder(bean1, element.getAttribute(”id1″));
  BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder1,
    parserContext.getRegistry());

  RootBeanDefinition bean2 = new RootBeanDefinition(BeanC.class);
  bean2.setSource(parserContext.extractSource(element));
  bean2.getPropertyValues().addPropertyValue(”value”, element.getAttribute(”value2″));
  BeanDefinitionHolder beanDefinitionHolder2 =
    new BeanDefinitionHolder(bean2, element.getAttribute(”id2″));
  BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder2,
    parserContext.getRegistry());
  return bean1;
}
}

리팩토링을 해서 더 간결하게 할 수 있지만 원리를 보기 위해 그냥 두개의 등록과정을 그대로 사용했다. 역시 간단하다.


 

RuntimeBeanReference

 

마지막으로 BeanDefinition의 프로퍼티를 설정할 때 만약 ref관계에 있는 다른 id만 아는 bean의 reference를 넣어주어야 할 경우가 있다.

<myns:mytag id=”abc” … cref=”myBeanC” />
<bean id=”myBeanC” … />

이런케이스이다. 이 경우 mytag에 해당하는 BeanDefinition을 만들고 addPropertyValue를 해줄 때 두번째 파라메터에 다른 bean definition대신에 runtime bean definition정보를 가져와서 설정하게 해주면 된다.

myTagBeanDef.getPropertyValues().addPropertyValue(”beanC”,
new RuntimeBeanReference(element.getAttribute(”cref”)));

이렇게 하면 cref attribute의 값에 해당하는 id를 가진 다른 bean을 검색해서 이 레퍼런스 정보를 프로퍼티에 세팅해준다.

 

이정도면 BeanDefinitionParser를 사용하는 모든 케이스를 다 알아봤다. 남은 것은 다양한 스타일의 빈을 설계하고 이를 설정하는 코드들을 만들어보는 것.

Spring에 이미 등록되어있는 각종 BeanDefinitionParser들의 소스를 분석해보는 것이 많은 도움이 될 것이다. 또 이를 잘 살펴보면 BeanFactory와 BeanDefinition같은 Spring IoC Container의 핵심구현방법에 대해서도 이해할 수 있게 될 것이다.


번호 제목 글쓴이 날짜 조회 수
공지 (확인전) [2021.03.12] Eclipse에서 Spring Boot로 JSP사용하기(Gradle) 황제낙엽 2023.12.23 0
공지 [작성중/인프런] 스프링부트 시큐리티 & JWT 강의 황제낙엽 2023.12.20 6
63 [Spring3.1.1][4] RestTemplate 한글 문제 황제낙엽 2018.08.08 89
62 [Spring3.1.1][3] RestTemplate 한글 문제 황제낙엽 2018.08.08 237
61 [Spring3.1.1][2] RestTemplate 한글 문제 황제낙엽 2018.08.08 113
60 [Spring3.1.1][1] RestTemplate 한글 문제 황제낙엽 2018.08.08 683
59 [Spring3.1.1] Eclipse 에 Spring Framework 환경 구축하기 file 황제낙엽 2018.08.08 90
58 웹 개발의 변화와 스프링 황제낙엽 2008.03.19 132
57 Spring MVC 가 아닌 환경에서 Spring Pojo Bean 사용하기 (Pure Java App 또는 Servlet App) 황제낙엽 2009.10.22 233
56 NamedParameterJdbcDaoSupport 몇가지 장점 황제낙엽 2007.11.27 101
55 프로젝트의 기본이 되는 Logging, Exception 처리 전략 황제낙엽 2007.01.30 85
54 Spring AOP - Pointcut 황제낙엽 2007.10.02 129
53 <spring:checkbox> tip! 황제낙엽 2007.10.01 378
52 SimpleFormController 정리 황제낙엽 2007.09.19 206
51 Spring의 Exception 황제낙엽 2007.09.17 194
50 스프링 2와 JPA 시작하기 (한글) 황제낙엽 2007.08.27 142
49 스프링 개발팁 황제낙엽 2007.08.17 223
48 유효성체크 (org.springframework.validation.Validator) 황제낙엽 2007.08.17 129
47 Spring 2.0의 XML확장기능 (3) 황제낙엽 2007.08.15 32
» Spring 2.0의 XML확장기능 (2) 황제낙엽 2007.08.15 73
45 Spring 2.0의 XML확장기능 (1) 황제낙엽 2007.08.15 33
44 CSS와 XHTML을 사용한 효율적인 View 개발 전략 황제낙엽 2007.01.30 104