IBM Korea Skip to main content
       IBM 홈    |  제품 & 서비스  |  고객지원 & 다운로드  |  회원가입  

IBM developerWorks > 자바
developerWorks

자바에서의 XML : 자바 문서 모델 사용법
상이한 자바 XML 문서 모델들이 작동하는 방식

Dennis M. Sosnoski
사장, Sosnoski Software Solutions, Inc.
2002년 2월

이 글에서 XML 툴 전문가인 Dennis Sosnosk는 몇 가지 자바 문서 모델들의 용법을 비교한다. 여러분이 한 모델을 선택할 때 얻는 것과 잃는 것이 무엇인지가 항상 명확한 것은 아니며, 나중에 마음을 바꿀 경우 엄청난 재코딩이 필요할 수 있다. 필자는 모델 API들을 분석하고 이와 관련한 샘플 코드를 제공하여 어떤 모델이 실제로 여러분의 작업을 쉽게 만들어 줄지에 대한 권장 사항을 제공한다. 다섯 개의 각기 다른 문서 모델들의 메소드를 보여주는 코드 샘플이 포함되어 있다.

(DOM 대 언어에 독자적인 모델에 관한 다른 견해를 보려면 첨부되어 있는 Joe Kesselman의 "문서 모델에 대한 몇 마디"를 참조한다.)

이 시리즈의 첫번째 글에서 나는 자바로 된 몇 가지 선두적인 XML 문서 모델들의 성능을 살펴 보았다. 그러나 성능은 이러한 유형의 기술을 선택할 때 고려해야 하는 사항의 일부분일 뿐이다. 사용의 용이성은 적어도 성능만큼 중요하며, 언어에 독립적인 DOM보다 자바에 국한된 모델의 사용을 지지하는 주요 인자 중 하나이다.

실제로 어떤 모델을 지지할 것인지에 대한 그림을 마저 그리기 위해 우리는 이 모델들이 사용의 편리성 면에서 어떻게 등급이 매겨지는지를 볼 필요가 있다. 이 글에서 나는 이 작업을 시도할 것인데, 각 모델에서 일반적인 작업 유형들이 어떻게 코딩되는지를 보여주는 샘플 코드를 가지고 출발하겠다. 나는 그 결과를 요약하면서 이 글을 끝낼 것이고, 한 표현법을 다른 것보다 사용하기 쉽게 만드는 다른 몇 가지 요인들도 제시하겠다.

이번 비교에서 사용될 모델들의 실제 버전 넘버를 포함해 배경 정보를 보려면 이전 글 (참고 자료나 이 글의 목차 아래에 있는 링크 참조)을 보기 바란다. 또한 소스 코드를 다운로드 받거나 모델의 홈페이지에 연결하려 할 때, 혹은 기타 관련 정보를 얻으려면 참고 자료를 참조한다.

코드 비교

이번 다른 문서 표현법들의 사용 기법 비교에서 나는 세 개의 기본적인 작업 유형들이 이 모델들에서 어떻게 수행되는지를 보여주겠다.:

  • 입력 스트림으로부터 문서 작성하기
  • 몇 가지 변경을 수행하면서 element와 컨텐트 처리하기
    • 텍스트 컨텐트에서 앞뒤의 스페이스를 지운다.
    • 결과로 나오는 텍스트 컨텐트가 비어 있으면 이를 삭제한다.
    • 그렇지 않으면 부모 element의 이름 공간에 "text"라는 이름을 가진 새로운 element로 이를 포장한다.
  • 수정된 문서를 출력 스트림에 작성한다.

이 예제들의 코드는 내가 이전 글에서 사용했던 벤치마크 프로그램에 기반하고 있으며, 일부 단순화시켰다. 벤치마크의 포커스는 각 모델이 최상의 성능을 낼 때를 보여 주는 것이다.; 이 글에서 나는 각 모델에서 작업을 수행하는 가장 쉬운 방법들을 보여주고자 한다.

나는 각 모델에 대한 예제를 두개의 개별적인 코드 부분으로 구성하였다. 첫번째 부분은 문서를 읽고 수정 코드를 호출하며 수정된 문서를 작성하기 위한 코드를 제공한다. 두 번째 부분은 문서의 표현들을 실제로 처리하고 변경을 수행하기 위한 반복적 메소드이다. 혼란을 피하기 위해 코드에서 예외 처리는 무시하였다.

여러분은 이 페이지 아래쪽에 있는 참고 자료 부분에 링크된 다운로드 페이지에서 모든 샘플에 대한 완전한 코드를 얻을 수 있다. 샘플의 다운로드 버전은 테스트 드라이브를 포함하고 있고, element, 삭제 및 추가를 헤아림으로써 다른 모델의 작업을 체크하는 일부 코드도 추가되었다.

여러분이 DOM 구현을 사용할 의도가 없다 하더라도 DOM 용법에 대한 설명을 훑어 보는 것이 유용할 것이다. DOM 예제는 뒤에 나오는 모델들서보다 예제의 몇몇 이슈와 구조에 관해 좀 더 상세히 설명하기 위해 내가 처음 사용하는 것이기 때문이다. DOM에 관해 읽어 보면 다른 모델로 바로 갈 경우 여러분이 놓치기 쉬운 몇 가지 세부 사항들을 알게 될 것이다.

DOM

DOM 사양은 문서의 표현들을 조작하는 모든 유형을 다루고 있지만, 문서 분석과 텍스트 산출물 생성과 같은 문제는 다루지 않는다. 성능 테스트에 포함되었던 두 가지 DOM 구현들(Xerces와 Crimson)은 이러한 작업을 위해 서로 다른 기법을 사용한다. Listing 1은 Xerces 상위 레벨 코드의 한 형태를 보여준다.

Listing 1. Xerces DOM 상위 레벨 코드

 1  // parse the document from input stream ("in")
 2  DOMParser parser = new DOMParser();
 3  parser.setFeature("http://xml.org/sax/features/namespaces", true);
 4  parser.parse(new InputSource(in));
 5  Document doc = parser.getDocument();

 6  // recursively walk and modify document
 7  modifyElement(doc.getDocumentElement());

 8  // write the document to output stream ("out")
 9  OutputFormat format = new OutputFormat(doc);
10  XMLSerializer serializer = new XMLSerializer(out, format);
11  serializer.serialize(doc.getDocumentElement());

내가 코멘트로 나타냈듯이, Listing 1에서 코드의 첫번째 블록 (1-5행)은 입력 스트림을 parsing하여 문서 표현을 구축한다. DOMParser 클래스는 Xerces에 의해 정의되며 Xerces parser의 출력으로부터 문서를 구축한다. InputSource 클래스는 SAX 사양의 일부분이고 SAX parser가 사용할 몇 가지 입력 유형 중 어떤 것이라도 적용할 수 있다. 실제 parsing과 문서 작성은 하나의 호출로 수행되는데, 이것이 성공적으로 완료되면 작성된 Document는 애플리케이션에 의해 검색되고 사용될 수 있다.

두 번째 코드 블록 (6-7행)은 문서의 루트 element를 내가 곧 설명할 반복적 수정 메소드로 전달한다. 이 코드는 이 글에 나오는 모든 문서 모델에서 본질적으로 동일하기 때문에, 나머지 예제들에서는 설명하지 않고 넘어가도록 하겠다.

세 번째 코드 블록(8-11행)은 텍스트로 된 산출물 스트림으로 문서를 작성하는 부분을 처리하고 있다. 여기에서, OutputFormat 클래스는 생성되는 텍스트의 포맷팅을 위한 다양한 선택을 제공하며 문서를 에워싼다. XMLSerializer 클래스는 실제 산출물 텍스트의 생성을 처리한다.

Xerces에 대한 수정 메소드는 표준 DOM 인터페이스만 사용하기 때문에, 다른 DOM 구현과도 호환 가능하다. Listing 2에 코드가 나와 있다.

isting 2. DOM 수정 메소드


 1  protected void modifyElement(Element element) {

 2    // loop through child nodes
 3    Node child;
 4    Node next = (Node)element.getFirstChild();
 5    while ((child = next) != null) {

 6      // set next before we change anything
 7      next = child.getNextSibling();

 8      // handle child by node type
 9      if (child.getNodeType() == Node.TEXT_NODE) {

10        // trim whitespace from content text
11        String trimmed = child.getNodeValue().trim();
12        if (trimmed.length() == 0) {

13          // delete child if nothing but whitespace
14          element.removeChild(child);

15        } else {

16          // create a "text" element matching parent namespace
17          Document doc = element.getOwnerDocument();
18          String prefix = element.getPrefix();
19          String name = (prefix == null) ? "text" : (prefix + ":text");
20          Element text = 
21            doc.createElementNS(element.getNamespaceURI(), name);

22          // wrap the trimmed content with new element
23          text.appendChild(doc.createTextNode(trimmed));
24          element.replaceChild(text, child);

25        }
26      } else if (child.getNodeType() == Node.ELEMENT_NODE) {

27        // handle child elements with recursive call
28        modifyElement((Element)child);

29      }
30    }
31  }

Listing 2에 나와 있는 메소드가 사용하는 기본적인 접근 방식은 모든 문서 표현법에서 동일하다. 이것은 한 element와 함께 호출되며 그 element의 자식 element들을 순서대로 처리한다. 하나의 텍스트 컨텐트 자식이 발견되면, 텍스트는 삭제되거나 (공백으로만 구성된 경우) 포함 element와 동일한 이름 공간에 'text"라는 이름을 가진 새로운 element로 둘러싸여진다 (공백이 아닌 문자가 있는 경우). 만일 element 자식이 발견되면 메소드는 자식 element와 함께 그 자신을 반복적으로 호출한다.

DOM 구현에서 나는 childnext라는 한 쌍의 참조를 사용하여 children 목록에서 내 위치를 추적한다. 다음 child 노드에 대한 참조는 현재 child에 대한 모든 처리가 수행되기 전에 로딩된다. (7행) 이는 목록에서 내 위치를 잃어 버리지 않고 현재 child를 삭제하거나 교체할 수 있게 해준다.

DOM 인터페이스는 공백이 아닌 텍스트 컨텐트 (16-24행)를 둘러 싸기 위해 새로운 element를 만들 때 약간 혼란스러워지기 시작한다. element를 만들기 위해 사용하는 메소드는 문서와 전체적으로 연관되어 있기 때문에, 나는 내가 현재 처리하고 있는 element 의 주인 문서를 검색해야 한다 (17행). 나는 새로운 element가 기존의 부모 element와 동일한 이름 공간에 있기를 바라고, DOM에서 이것은 내가 요건을 갖춘 element 이름을 만들어야 함을 의미한다. 이것은 이름 공간 접두사의 존재 여부 (18-19행)에 따라 다르게 수행된다. 새로운 element에 대한 요건을 갖춘 이름과 기존 element의 이름 공간 URI를 가지고 나면 나는 새로운 element를 만들 수 있다. (20-21행)

일단 새로운 element가 만들어지면 내용 String을 둘러쌀 텍스트 노드를 만들고 추가할 수 있고, 원래 텍스트 노드를 새롭게 만들어진 element로 바꿀 수 있다. (22-24행)

Listing 3. Crimson DOM 상위 레벨 코드

 1  // parse the document from input stream
 2  System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
 3      "org.apache.crimson.jaxp.DocumentBuilderFactoryImpl");
 4  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 5  dbf.setNamespaceAware(true);
 6  DocumentBuilder builder = dbf.newDocumentBuilder();
 7  Document doc = builder.parse(in);

 8  // recursively walk and modify document
 9  modifyElement(doc.getDocumentElement());

10  // write the document to output stream
11  ((XmlDocument)doc).write(out);

Listing 3에 나와 있는 Crimson DOM 예제 코드는 parsing을 위해 JAXP 인터페이스를 사용한다. JAXP는 XML 문서를 parsing하고 변환하기 위한 표준화된 인터페이스를 제공한다. 이 예제에 나온 parsing 코드는 앞에서 제시한 Xerces에 국한된 예제 코드 대신 Xerces에서도 사용될 수 있다 (문서 builder 클래스 명의 특성 설정을 적절하게 변경해야 한다).

이 예제에서, 나는 먼저 구축될 DOM 표현법의 builder factory 클래스를 선택하기 위해 2-3 행에서 시스템 특성을 설정한다. (JAXP는 이 글에서 설명한 것들 중 DOM 표현법 구축만 직접 지원한다.) 이 단계는 JAXP가 사용할 특정 DOM을 선택하고 싶을 때만 필요하다.; 그렇지 않다면 JAXP는 기본 구현을 사용한다. 나는 완벽성을 위해 이 특성 설정을 코드에 포함시켰지만, JVM 명령행 매개변수로 설정하는 것이 훨씬 더 일반적이다.

4-6행에서 나는 builder factory의 인스턴스를 생성하고 그 factory 인스턴스를 사용해 구축된 builder에 대한 이름 공간 지원을 가능케 하였으며, builder factory로부터 문서 builder를 생성하였다. 마지막으로 (7행) 문서 builder를 사용해 입력 스트림을 parsing하고 문서 표현을 구축하였다.

문서 작성을 위해 나는 Crimson에 내부적으로 정의된 기본 메소드를 사용한다. 이 방식이 Crimson의 향후 버전에서 지원될지는 보장되지 않지만, 문서를 출력하기 위해 JAXP 변환 코드를 사용하는 쪽을 택하면 Xalan과 같은 XSL 프로세서가 필요하다. 이 부분은 이 글의 범위를 넘어서지만, 상세 사항을 보려면 Sun의 JAXP 튜토리얼을 참조할 수 있다.

JDOM

JDOM을 사용하면 상위 레벨 코드가 DOM 구현보다 조금 더 단순하다. 문서 표현법을 구축하기 위해 (1-3행), 나는 매개변수 값에 의해 검증 작업이 비활성화되어 있는 SAXBuilder를 사용한다. 수정된 문서를 출력 스트림에 작성하는 것도 마찬가지로 간단한데 (6-8행), 제공된 XMLOutputter 클래스를 사용한다.

Listing 4. JDOM 상위 레벨 코드

 1  // parse the document from input stream
 2  SAXBuilder builder = new SAXBuilder(false);
 3  Document doc = builder.build(in);

 4  // recursively walk and modify document
 5  modifyElement(doc.getRootElement());

 6  // write the document to output stream
 7  XMLOutputter outer = new XMLOutputter();
 8  outer.output(doc, out);

Listing 5의 JDOM에 대한 수정 메소드도 DOM에서의 동일 메소드보다 간단하다. element의 모든 컨텐트 목록을 얻고 살펴 보아 텍스트 (String 객체로)와 element가 있는지 체크한다. 목록은 "살아 있고", 따라서 나는 부모 element에 대한 메소드를 호출할 필요 없이 목록을 직접 수정할 수 있다.

Listing 5. JDOM 수정 메소드

 1  protected void modifyElement(Element element) {

 2    // loop through child nodes
 3    List children = element.getContent();
 4    for (int i = 0; i < children.size(); i++) {

 5      // handle child by node type
 6      Object child = children.get(i);
 7      if (child instanceof String) {

 8        // trim whitespace from content text
 9        String trimmed = child.toString().trim();
10        if (trimmed.length() == 0) {

11          // delete child if only whitespace (adjusting index)
12          children.remove(i--);

13        } else {

14          // wrap the trimmed content with new element
15          Element text = new Element("text", element.getNamespace());
16          text.setText(trimmed);
17          children.set(i, text);

18        }
19      } else if (child instanceof Element) {

20        // handle child elements with recursive call
21        modifyElement((Element)child);

22      }
23    }
24  }

새로운 element를 작성하는 기법 (14-17행)은 매우 간단하고, DOM 버전과 달리 부모 문서로 접근하지 않아도 된다.

dom4j

dom4j의 상위 레벨 코드는 JODM보다 조금 더 복잡하지만, 매우 간단한 행을 따라 작동한다. 이 코드의 주된 차이점은 dom4j 문서 표현을 구축하기 위해 사용하는 DocumentFactory를 저장하고 (5행) 수정된 문서 텍스트의 결과를 만들어 낸 후 writer 객체의 flush() 메소드를 이용하여 내용을 보낸다는 것이다. (10행).

Listing 6. dom4j 상위 레벨 코드

 1  // parse the document from input stream
 2  SAXReader reader = new SAXReader(false);
 3  Document doc = reader.read(in);

 4  // recursively walk and modify document
 5  m_factory = reader.getDocumentFactory();
 6  modifyElement(doc.getRootElement());

 7  // write the document to output stream
 8  XMLWriter writer = new XMLWriter(out);
 9  writer.write(doc);
10  writer.flush();

Listing 6에서 볼 수 있듯이, dom4j는 parse으로부터 구축된 문서 표현에 포함된 객체를 구축하기 위해 factory 방식을 사용한다. 각 컴포넌트 객체는 인터페이스 측면에서 정의되고, 따라서 이 인터페이스들 중 하나를 구현하는 모든 객체 유형이 표현법에 포함될 수 있다.(이는 구체적인 클래스를 사용하는 JDOM과 반대된다.: 일부 경우에 클래스들은 하위 클래스로 나누어져 확장될 수 있지만, 문서 표현에 사용되는 어떤 클래스라도 원 JDOM 클래스에 기반하고 있어야 한다.) 여러분은 dom4j 문서 구축을 위해 다른 factory들을 사용함으로써 다른 컴포넌트 군으로 구축된 문서들을 얻을 수 있다.

샘플 코드(5행)에서, 나는 문서 구축에 사용되는 (기본) 문서 factory를 검색하고 이를 인스턴스 변수 (m_factory)에 저장하여 수정 메소드가 사용할 수 있도록 하였다. 이 단계는 꼭 필요한 것은 아니다. -- 다른 factory들로부터 만들어진 컴포넌트들이 문서에서 함께 사용될 수 있고, 혹은 여러분이 factory를 무시하고 컴포넌트들의 인스턴스를 직접 만들 수도 있다.-- 그러나 이 경우 나는 문서의 나머지 부분에서 사용되는 것과 동일한 컴포넌트 유형을 만들고 싶어하고, 동일한 factory를 사용하면 이를 보증할 수 있다.

Listing 7. dom4j 수정 메소드

 1  protected void modifyElement(Element element) {

 2    // loop through child nodes
 3    List children = element.content();
 4    for (int i = 0; i < children.size(); i++) {

 5      // handle child by node type
 6      Node child = (Node)children.get(i);
 7      if (child.getNodeType() == Node.TEXT_NODE) {

 8        // trim whitespace from content text
 9        String trimmed = child.getText().trim();
10        if (trimmed.length() == 0) {

11          // delete child if only whitespace (adjusting index)
12          children.remove(i--);

13        } else {

14          // wrap the trimmed content with new element
15          Element text = m_factory.createElement
16            (QName.get("text", element.getNamespace()));
17          text.addText(trimmed);
18          children.set(i, text);

19        }
20      } else if (child.getNodeType() == Node.ELEMENT_NODE) {

21        // handle child elements with recursive call
22        modifyElement((Element)child);

23      }
24    }
25  }

Listing 7에 나와 있는 dom4j 수정 메소드는 JDOM에서 사용되는 것과 매우 비슷하다. instanceof 연산자를 사용해 컨텐트 아이템의 유형을 체크하는 대신, 나는 Node 인터페이스 메소드인 getNodeType를 통해 유형 코드를 얻을 수 있다. (instanceof도 사용할 수 있지만, 유형 코드 방식이 더 명확해 보인다.) 새로운 element 생성 기법은 (15-16행) element명을 표시하기 위해 QName 객체를 사용하고 저장된 factory의 메소드를 호출하여 element를 구축한다는 점에서 틀리다.

Electric XML

Listing 8에 나와 있는 Electric XML (EXML)의 top-level 코드는 예제들 중 가장 간단한데, 문서 읽기와 쓰기 각각에 대해 하나의 메소드 호출을 사용한다.

Listing 8. EXML 상위 레벨 코드

 1  // parse the document from input stream
 2  Document doc = new Document(in);

 3  // recursively walk and modify document
 4  modifyElement(doc.getRoot());

 5  // write the document to output stream
 6  doc.write(out);

Listing 9의 EXML 수정 메소드는 DOM 방식과 가장 유사하지만, JODM과 마찬가지로 instanceof 체크 사용이 필요하다. EXML에서는 이름 공간에 유효한 이름을 가진 element를 생성할 방법이 없기 때문에, 나는 대신 새로운 element를 생성하여 같은 효과를 얻도록 그 이름을 설정하였다.

isting 9. EXML 수정 메소드


 1  protected void modifyElement(Element element) {

 2    // loop through child nodes
 3    Child child;
 4    Child next = element.getChildren().first();
 5    while ((child = next) != null) {

 6      // set next before we change anything
 7      next = child.getNextSibling();

 8      // handle child by node type
 9      if (child instanceof Text) {

10        // trim whitespace from content text
11        String trimmed = ((Text)child).getString().trim();
12        if (trimmed.length() == 0) {

13          // delete child if only whitespace
14          child.remove();

15        } else {

16          // wrap the trimmed content with new element
17          Element text = new Element();
18          text.addText(trimmed);
19          child.replaceWith(text);
20          text.setName(element.getPrefix(), "text");

21        }
22      } else if (child instanceof Element) {

23        // handle child elements with recursive call
24        modifyElement((Element)child);

25      }
26    }
27  }

XPP
XPP (Listing 10)의 상위 레벨 코드는 예제들 중 가장 길고, 다른 모델보다 상당히 많은 설정이 필요하다.

Listing 10. XPP top-level 코드

 1  // parse the document from input stream
 2  m_parserFactory = XmlPullParserFactory.newInstance();
 3  m_parserFactory.setNamespaceAware(true);
 4  XmlPullParser parser = m_parserFactory.newPullParser();
 5  parser.setInput(new BufferedReader(new InputStreamReader(in)));
 6  parser.next();
 7  XmlNode doc = m_parserFactory.newNode();
 8  parser.readNode(doc);

 9  // recursively walk and modify document
10  modifyElement(doc);

11  // write the document to output stream
12  XmlRecorder recorder = m_parserFactory.newRecorder();
13  Writer writer = new OutputStreamWriter(out);
14  recorder.setOutput(writer);
15  recorder.writeNode(doc);
16  writer.close();

JAXP 인터페이스와 마찬가지로, 나는 먼저 parser factory의 인스턴스를 생성하고 이름 공간 처리를 가능하게 한 후에 parser 인스턴스를 생성해야 한다 (2-4행). 일단 parser 인스턴스를 가지게 되면 parser에 입력사항을 설정하고 실제로 문서 표현을 구축할 수 있지만 (5-8행), 다른 모델에서보다 많은 단계가 필요하다.

출력 처리 (11-16행) 또한 다른 모델보다 많은 단계가 필요한데, 주로 XPP가 출력 타겟으로 Stream을 직접 받아들이기보다는 Writer를 요구하기 때문이다.

Listing 11의 XPP 수정 메소드는 JDOM 방식과 가장 유사하지만, 새로운 element 생성에 더 많은 코드가 필요하다(13-21행). 이름공간 처리는 조금 성가신 측면이다. 먼저 element에 대해 유효한 이름을 만든 후 (15-16행) element를 생성하고 마지막으로 이름과 이름공간 URI를 나중에 설정한다. (18-21행)

Listing 11. XPP 수정 메소드

 1  protected void modifyElement(XmlNode element) throws Exception {

 2    // loop through child nodes
 3    for (int i = 0; i < element.getChildrenCount(); i++) {

 4      // handle child by node type
 5      Object child = element.getChildAt(i);
 6      if (child instanceof String) {

 7        // trim whitespace from content text
 8        String trimmed = child.toString().trim();
 9        if (trimmed.length() == 0) {

10          // delete child if only whitespace (adjusting index)
11          element.removeChildAt(i--);

12        } else {

13          // construct qualified name for wrapper element
15          String prefix = element.getPrefix();
16          String name = (prefix == null) ? "text" : (prefix + ":text");

17          // wrap the trimmed content with new element
18          XmlNode text = m_parserFactory.newNode();
19          text.appendChild(trimmed);
20          element.replaceChildAt(i, text);
21          text.modifyTag(element.getNamespaceUri(), "text", name);

22        }
23      } else if (child instanceof XmlNode) {

24        // handle child elements with recursive call
25        modifyElement((XmlNode)child);

26      }
27    }
28  }

결론

JDOM, dom4j 및 Electric XML은 이 코드 샘플들에서 모두 사용 용이성이 거의 비슷한 것으로 보인다. 아마도 EXML이 가장 쉽고 dom4j가 더 어렵다고 할 수 있는데, 작은 차이이다. DOM은 언어에 독립적이라는 매우 실질적인 이점을 제공하지만, 여러분이 자바 코드로만 작업하고 있다면 자바에 특화된 모델들과 비교했을 때 조금 더 성가신 것처럼 보인다. 나는 이 점이 자바 코드에서 XML 문서 처리를 단순화시킨다는 목표를 달성하는데 자바에 특화된 모델이 일반적으로 더 성공적이라는 것을 보여준다고 생각한다.

기본을 넘어서- 실세계에서의 사용의 용이성

코드 샘플은 JDOM과 EXML이 기본적인 문서 조작 (element, 속성, 텍스트를 사용한 작업)을 위한 간단하고 말끔한 인터페이스를 제공함을 보여준다. 내 경험에 따르면 이들의 방식은 전체 문서 표현법을 사용하는 프로그래밍 작업에는 잘 맞지 않는다. 이런 유형의 작업에 대해서는 속성에서 이름공간까지 모든 문서 컴포넌트가 몇 가지 공통된 인터페이스를 구현하는, DOM과 dom4j의 컴포넌트 방식이 훨씬 적합하다.

이러한 경우로 내가 최근에 JDOM과 dom4j에 구현한 XML Streaming (XMLS) 인코딩을 들 수 있다. 이 코드는 전체 문서에 적용되고 각 컴포넌트를 인코딩한다. JDOM 구현은 dom4j 구현보다 훨씬 더 복잡한데, 이는 주로 JDOM이 각 컴포넌트를 나타내는 공통 인터페이스가 없는 독특한 클래스를 사용하기 때문이다.

JDOM은 공통 인터페이스가 없기 때문에 Document 객체와 작업하기 위한 코드가 Element 객체와 작업하기 위한 코드와 달라야 한다. 둘 다 일부 같은 유형의 컴포넌트를 자식으로 가질 수 있지만 말이다.다른 유형의 자식에 대비하여 Namespace 컴포넌트를 검색하기 위한 특수 메소드 역시 필요하다. 컨텐트로 간주되는 자식 컴포넌트 유형을 처리할 때에도 여러분은 컴포넌트 유형에 깔끔하고 빠른 switch 문 대신에 instanceof 체크를 가진 여러 개의 if 문을 사용해야 한다.

JDOM의 원래 목표 중 하나가 인터페이스에 상당히 기반하고 있는 자바 Collections 클래스를 사용하는 것이었다는 점이 아니러니컬하다. 인터페이스를 라이브러리에 사용하면 복잡성이 약간 추가되는 대신 유연성이 상당히 높아지고, 이것은 재사용 가능하도록 설계된 코드에서는 할만한 거래이다. 이 점은 또한 dom4j가 JDOM보다 훨씬 빨리 성숙되고 안정된 상태에 도달하게 하는데 상당히 기여했다.

그러나 DOM은 여러 언어들로 작업하는 개발자들에게 여전히 많이 선택되고 있다. DOM 구현은 다양한 프로그래밍 언어들에서 사용될 수 있다. 또한 많은 다른 XML 관련 표준의 기초가 되고 있다. 따라서 여러분이 자바에 특화된 모델을 사용하더라도 DOM에 대해 적어도 간단하게나마 알고 있어야 할 필요가 있을 것이다. DOM은 공식적인 W3C 권고안이기 때문에 (다른 비표준 자바 모델과 달리) 특정 유형의 프로젝트에서 요구되기도 한다.

사용의 용이성 카테고리에서 경쟁하는 세 모델 중에 dom4j는 여러 계층으로 된 상속을 가진 인터페이스 기반의 방식을 사용한다는 점에서 나머지 것들과 다르다. 이 점은 API JavaDocs를 따르는 것을 좀 더 어렵게 만들 수 있다. 예를 들어, 여러분이 찾고 있는 메소드 (우리의 dom4j 수정 메소드 예제의 3행에 사용된 content()와 같은)는 Element 인터페이스 자체의 부분이라기보다 Element가 확장하는 Branch 인터페이스의 부분일 수 있다. 그러나 이러한 인터페이스 기반 설계는 많은 유연성을 더해준다 (보조 자료 기본을 넘어서 : 실세계에서의 사용의 용이성 참조). dom4j의 성능, 안정성, 기능면에서의 이점을 볼 때 여러분은 이를 대부분의 프로젝트에 대한 강력한 후보로 고려할 수 있을 것이다.

JDOM은 자바에 특화된 문서 모델 중 아마도 가장 큰 사용자 층을 가지고 있고 분명히 사용이 가장 쉬운 것 중 하나일 것이다. 그러나 프로젝트 개발용으로 선정될 때 JDOM은 아직 유동적이고 버전마다 변화가 큰 API를 가지고 있다는 점 때문에 불리하다. 또한 성능 비교에서도 낮은 점수를 받았다. 현재 나와 있는 구현으로 볼 때 나는 새로운 프로젝트를 시작하는 사람들에게 JDOM보다는 dom4j를 권한다.

XPP를 제외하고는 다른 모델들보다 훨씬 적은 자원을 필요로 하고 훌륭한 사용 용이성을 제공하는 EXML의 이점을 생각할 때 여러분은 분명히 jar 파일 사이즈가 중요한 요소인 애플리케이션에 EXML의 사용을 고려해야 한다. 그러나 EXML의 XML 지원 기능의 한계와 제한된 라이선스는 대단위 파일에서의 상대적으로 낮은 성능과 함께 많은 애플리케이션에서 EXML을 불리하게 만드는 요인이 되고 있다.

XPP는 parsing와 텍스트 문서 작성, 이름 공간 작업에 많은 단계가 필요하다는 점 때문에 불리하다. XPP가 이러한 몇 가지 일반적인 사항들을 처리하는데 좀 더 편리한 방법을 추가한다면 아마도 비교에서 더 나은 평가를 받을 것이다. 현재로써는 지난번 글에서 성능의 선두 주자였지만 이번 글의 사용 용이성 분야에서는 패자가 되었다. 그러나 성능상의 이점을 고려할 때 적은 jar 파일 사이즈가 필요한 애플리케이션에서 EXML에 대한 대안으로 여전히 고려해 볼만하다.

다음 회에는...

지금까지 두 편의 글에서 나는 자바로 된 XML 문서 모델들의 성능과 사용 용이성을 다루었다. 이 시리즈의 다음 두 글에서 나는 자바 기술에서의 XML 데이터 바인딩 방식들을 살펴보겠다. 이들은 문서 모델 방식과 많은 유사점을 가지고 있지만, XML 문서를 실제 애플리케이션 데이터 구조로 매핑시킴으로써 한 단계 더 나아간다. 우리는 이들이 사용 용이성과 성능 분야에서 얼마나 잘 성과를 올리는지 살펴볼 것이다.

developerWorks를 체크하여 자바 코드에 XML 데이터를 바인딩시키는 것에 관한 글들을 보기 바란다.

참고 자료

목 차:
코드 비교
DOM
JDOM
dom4j
Electric XML
XPP
결론
다음 회에는...
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
자바에서 XML 문서 모델의 성능
Effective DOM with Java
DOM 이해하기 튜토리얼
자바에서의 XML 프로그래밍 튜토리얼
Subscribe to the developerWorks newsletter
US 원문 읽기
Also in the Java zone:
Tutorials
Tools and products
Code and components
Articles
필자소개
author photo -- Dennis Sosnosky Dennis Sosnoski는 Seattle 소재 자바 컨설팅 업체인 Sosnoski Software Solutions, Inc의 창업자이자 수석 컨설턴트이다. 그의 전문적인 소프트웨어 개발 경험은 30년이 넘고, 지난 7년간은 servlets, Enterprise JavaBeans 및 XML을 포함한 서버측 자바 기술에 포커스를 맞추어 왔다. 그는 자바의 성능 관련 사항들과 서버측 자바 기술에 대한 많은 프리젠테이션을 수행하였고, Seattle Java-XML SIG의 의장이기도 하다.
이 기사에 대하여 어떻게 생각하십니까?

정말 좋다 (5) 좋다 (4) 그저그렇다 (3) 수정보완이 필요하다(2) 형편없다 (1)

  회사소개  |  개인정보 보호정책  |  법률  |  문의
odifyElement(Element element) {