저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
간단한 예를 통해 웹 기반의 데이터 송수신이 어떻게 이루어지는 지 그리고 웹-투-웹의 장단점에 대해서 살펴본다.

가상 인터넷 서점 시나리오

지난 Part 1 에서는 java.net.URL 클래스와 java.net.URLConnection 클래스를 사용하여 웹-투-웹 통신의 기본이 되는 HttpMessage 클래스를 작성해보았다. 이번 Part 2에서는 가상의 기업들을 설정해서 웹-투-웹 프로그래밍이 어떤 식으로 구현되는지에 살펴보도록 하자. 또한, 이 과정에서 우리는 XML을 사용하여 데이터를 주고 받을 때의 장단점에 대해서 살펴볼 것이다.

이 글에서 웹을 기반으로 하여 데이터를 주고 받는 것에 대해서 설명하기 위해 가상의 시나리오를 작성해보자. 이 시나리오에는 크게 고객, 인터넷서점, 출판사, 택배사가 출현한다. 고객은 인터넷 서점을 통해서 서적을 주문하며, 인터넷 서점은 사용자가 서적 주문을 하면(단, 여기서는 신용카드와 같이 구매와 동시에 결재가 된다고 가정한다) 실시간으로 구매 정보를 택배사에 전송한다. 이때, 고객은 인터넷 서점에 웹을 통해서 서적을 주문하게 되며 인터넷 서점은 웹을 기반으로 하여 택배사에 배송 정보를 전송한다. 이 과정을 간단하게 그림으로 표현하면 다음과 같다.


그림1 인터넷 서점과 택배사 사이의 데이터 이동 흐름
그리고, 인터넷 서점은 하루 간격으로 각 출판사의 신간 목록을 읽어오는데, 이 과정 역시 웹 기반의 통신을 통해서 이루어진다. 이때, 출판사는 신간 목록을 인터넷 서점에서 정한 DTD에 알맞도록 생성된 XML 문서를 인터넷 서점에 전송한다. 인터넷 서점은 전송받은 XML 문서를 알맞게 처리하여 자신들의 서적 목록 DB에 추가한다.

여기서 작성한 가상 시나리오는 인터넷 서점과 관련된 간단한 업무 흐름을 웹 기반으로 처리하고 있다. 모든 것이 웹 기반으로 이루어지기 때문에 웹 어플리케이션만을 개발하면 되며, 그 외에 별도의 클라이언트 프로그램은 필요하지 않게 된다. 즉, 모든 사용자들은 웹 브라우저만으로 자신의 업무를 처리할 수 있게 되는 것이다.

시나리오의 구현

가상 인터넷 서점의 구현은 기본적으로 다음과 같은 3 단계로 이루어진다.

  1. HTTP의 파라미터를 사용하여 다른 업체가 요구하는 데이터를 전송한다.(즉, HTTP 요청)
  2. 데이터를 전송받은 업체는 알맞은 프로토콜을 사용하여 전송 결과를 보낸다. (즉, HTTP 응답)
  3. HTTP 응답을 받은 업체는 결과에 알맞게 나머지 처리를 한다.
예를 들어, 사용자가 어떤 서적을 구매한다고 해 보자. 사용자는 인터넷 서점의 웹 사이트에서 구매할 책을 선택한 후 "구매" 버튼을 클릭할 것이다. 그러면 그 "구매" 요청을 처리하는 인터넷 서점의 서블릿은 사용자의 "구매" 요청 정보를 DB에 저장한다. 그런 후 (1) 인터넷 서점의 서블릿은 HTTP 프로토콜을 사용하여 택배 회사에서 발송 요청 정보를 전송한다. 그러면 (2) 인터넷 서점의 서블릿으로부터 요청을 받은 택배 회사의 서블릿은 처리 결과를 인터넷 서점과 미리 결정한 프로토콜에 맞춰 출력 스트림으로 전송한다. 마지막으로 (3) 인터넷 서점은 택배 회사로부터 전달받은 결과값을 사용하여 나머지 처리를 한다. 나머지 처리에 속하는 것으로는 사용자에게 구매한 서적이 발송되었음을 알리는 메일을 발송한다던가 데이터베이스에 기록을 남기는 등이 있을 것이다.

모든 웹-투-웹 기반의 프로그램은 이와 같이 3 단계 과정을 기본으로 하고 있으며, 서로 주고 받을 데이터에 대한 규약만 명확하게 정의해 놓는다면 안정적으로 기업간 통신을 이룰 수 있게 된다. (웹 기반 통신의 장점에 대해서는 이 글의 뒷 부분에서 언급할 것이다.)

발송 요청 및 발송확인 통보

이제부터 본격적으로 구현에 대해서 살펴보자. 먼저 알아볼 내용은 인터넷 서점이 택배 회사에 발송 요청을 하는 것과 택배 회사가 인터넷 서점에 발송 확인 통보를 하는 과정이다.

첫번째로 발송 요청부터 살펴보자. 가장 먼저 해야 할 일은 사용자가 서적을 구매할 경우 인터넷 서점에서 택배회사에 실시간으로 전송되어야 하는 파라미터를 결정하는 것이다. 이 예제에서는 표1에 나타낸 파라미터를 택배회사에 전송할 것이다.

파라미터 이름 의미
orderNo 주문번호
title 책이름
isbn ISBN
quantity 수량
receiver 받는사람
address 받는사람주소
contact 연락처

인터넷 서점의 구매 요청을 처리하는 JSP 페이지는 다음과 같은 순서로 구매 요청을 처리하게 된다.

  1. 사용자가 입력한 내용에 기반하여 주문번호를 생성한다.
  2. 해당하는 주문번호와 관련된 정보를 DB에 저장한다.
  3. 표1에 표시한 파라미터 정보를 저장하고 있는 java.util.Properties 객체를 생성한다.
  4. HttpMessage를 사용하여 3 에서 생성한 파라미터 정보를 택배회사 서블릿에 전송한다.
  5. 택배회사 서블릿으로부터 응답 결과에 따라 알맞은 처리를 한다.
  6. 최종 처리를 사용자에게 보여준다.
이 과정에서 우리가 살펴볼 부분은 과정 3에서 과정 5까지이다. 나머지 과정은 이 글의 목적과 관련성이 적으므로 생략하기로 한다. 먼저 과정 3에 해당하는 코드를 살펴보자. 이 코드는 다음과 같을 것이다.

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
  
     // 책 주문 관련 정보를 OrderBean 이라는 자바빈 컴포넌트에 저장한다.
     ...
     ...
     
     Properties params = new Properties();  // 택배사에 전송될 파라미터를 저장한다.
     params.setProperty("orderNo", orderBean.getOrderNo());
     params.setProperty("title", orderBean.getTitle());
     params.setProperty("isbn", orderBean.getIsbn());
     params.setProperty("quantity", orderBean.getQuantity().toString());
     params.setProperty("receiver", orderBean.getReceiver());
     params.setProperty("address", orderBean.getAddress());
     params.setProperty("contact", orderBean.getContact());
     
     ...
     ...
  }

위와 같이 Properties 객체를 생성한 다음에는 4단계로 들어가면 된다. 4단계 코드는 다음과 같다.

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
     
     // 책 주문 정보를 읽어오고 Properties 객체를 사용하여
     // 택배사 서블릿에 전송할 파라미터의 값을 지정한다.
     ...
     ...
     
     BufferedReader br = null; // 택배사 서블릿으로부터 결과를 읽어올 때 사용되는 스트림.
     try {
        HttpMessage httpMessage = new HttpMessage(url);
        InputStream is = httpMessage.sendPostMessage(params);
        br = new BufferedReader(new InputStreamReader(is));        
        // 응답 결과에 따라 알맞은 처리를 한다.
        ...
        ...
        
     } catch(IOException ex) {
        // 택배사 서버와 통신 과정에서 예외 발생
        ...
     } finally {
        if (br != null)
           try { br.close(); } catch(SQLException ex) {}
     }
     ...
  }

이제 마지막으로 해야 할 일은 택배사의 서블릿이 보내온 결과값을 이용하여 마무리 처리를 최종 처리를 하는 것이다. 여기서 필요한 것이 바로 프로토콜이다. 물론, 인터넷 서점의 서블릿과 택배사의 서블릿이 서로 정보를 몇 차례 주고 받는 것이 아니라 단 한번만 정보를 주고 받는 것이기 때문에(즉 인터넷 서점의 서블릿은 택배사 서블릿에 요청을 전송하고 택배사 서블릿은 그에 대한 응답을 전송한다) 특별히 복잡한 프로토콜은 필요없다. 택배 서블릿은 단순히 인터넷 서점의 택배 발송 요청에 대한 성공 실패 여부 정도만 알려줄 수 있으면 된다. 다음 표는 택배사 서블릿이 인터넷 서점에 결과 값을 전송할 때 사용하는 프로토콜을 나타내고 있다.

프로토콜 메시지 의미
OK [주문번호] 인터넷 서점의 택배 서비스 요청이 성공적으로 이루어졌음을 나타낸다. [주문번호]는 인터넷 서점에서 사용하는 주문 번호를 나타낸다.
ERROR [주문번호] [에러메시지] 택배 발송 서비스 요청 처리 과정에서 에러가 발생했음을 나타낸다. [에러메시지]는 발생한 에러의 원인을 나타낸다.

프로토콜이라는 용어를 사용해서 거창한 것 처럼 느꼈을지도 모르지만, 택배사 서블릿이 요청 결과로서 보내는 프로토콜은 표2와 같이 간단하다. 택배사 서블릿은 택배 서비스 요청 처리 결과를 표2에 표시한 프로토콜에 따라 인터넷 서점에 전송한다. 예를 들어, 택배사 서블릿은 요청한 주문번호에 대하여 이미 택배 서비스 요청이 처리되었을 경우 다음과 같은 메시지를 인터넷 서점에 전송할 것이다.

  ERROR S-2001-878124 이미 서비스 요청이 처리된 주문 번호입니다.

다음 코드는 앞에서 살펴본 코드에 프로토콜 처리 부분을 추가한 것이다.

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
  
     // 책 주문 정보를 읽어오고 Properties 객체를 사용하여
     // 택배사 서블릿에 전송할 파라미터의 값을 지정한다.
     ...
     ...
     
     BufferedReader br = null; // 택배사 서블릿으로부터 결과를 읽어온다.
     try {
        HttpMessage httpMessage = new HttpMessage(url);
        InputStream is = httpMessage.sendPostMessage(params);
        br = new BufferedReader(new InputStreamReader(is));
        
        // 응답 결과에 따라 알맞은 처리를 한다.
        String line = br.readLine(); // 첫줄을 읽어온다.
        int indexOfSpace = line.indexOf(' ');
        if (indexOfSpace > 0) {
           String cmd = line.substring(0, indexOfSpace);
           if (cmd.equals("OK") ) {
              // 택배 서비스 요청 처리가 성공적으로 이루어졌음을 나타낸다.
              // 이 경우 사용자에게 주문 요청이 성공적으로 이루어졌다는
              // 메시지를 보여준다.
              ...
              
           } else if (cmd.equals("ERROR") ) {
              // 택배 서비스 요청 처리 과정에서 에러가 발생했음을 나타낸다.
              // 이 경우 사용자에게 택배 서비스 신청 과정 중 에러가
              // 발생하였음을 알린다.
              ...
           } else {
             // 프로토콜을 따르지 않는 응답 결과를 보내온 것임
             ...
           }
        } else {
           // 프로토콜을 따르지 않는 응답 결과를 보내온 것임
           ...
        }
        ...
        ...
     } catch(IOException ex) {
        // 택배사 서버와 통신 과정에서 예외 발생
        // 이에 대한 알맞은 메시지를 사용자에게 보여준다.
        ...
     } finally {
        if (br != null)
           try { br.close(); } catch(SQLException ex) {}
     }
     ...
  }

위 코드를 보면 웹-투-웹 프로그래밍이 복잡하지 않다는 것을 알 수 있다. 주고 받아야 하는 데이터에 따라 프로토콜이 더 복잡해질 수는 있겠지만, 대부분의 경우 위 코드의 형태를 크게 벗어나지 않는다. 물론, 위 코드에서 프로토콜에 따라 처리하는 부분을 좀더 객체 지향적으로 변경할 수는 있겠지만, 기본 구조는 위 코드와 같다.

택배사가 배송 결과를 다시 알려주는 과정은 인터넷 서점이 택배사에 택배 서비스를 요청하는 것과 크게 다르지 않으므로 그에 대한 설명은 하지 않겠다.

XML을 이용한 데이터 송수신

지금까지 살펴본 내용을 통해서 웹-투-웹 프로그래밍이 어떤식으로 구현된다는 것을 알 수 있었을 것이다. 하지만, 앞에서 살펴본 내용들이 모두 코드의 일부만을 보여줬기 때문에 실제 구현이 어떤식으로 이루어지는 지에 대해서는 여전히 궁금해할 것이라 생각된다. 그래서 이제부터는 웹-투-웹을 기반으로 XML 데이터를 주고 받는 것에 대해서 살펴보도록 하자.

여기서 살펴볼 예제는 앞에서 언급했던 것으로서 인터넷 서점이 출판사로부터 신간 목록을 읽어오는 부분이다. 출판사의 서블릿인 NewBookListServlet은 최근에 추가된 신간 목록을 XML 문서로 출력해준다. 출력되는 XML 문서는 다음과 같은 형식을 갖는다.

  <?xml version="1.0" encoding="euc-kr" ?>
  <book-list>
     <book>
        <isbn>8980781032</isbn>
        <title>JSP Professional</title>
        <publisher>가메</publisher>
        <author>이동훈,최범균</author>
        <page>873</page>
        <price>25000</price>
     </book>
     <book>
        <isbn>8980781024</isbn>
        <title>Windows 2000 Server 완전정복</title>
        <publisher>가메</publisher>
        <author>김형백</author>
        <page>891</page>
        <price>25000</price>
     </book>      
  </book-list>

실제로 NewBookListServlet은 다음과 같다.

  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  
  import com.pub.book.BookMgr;
  import com.pub.book.BookMgrException;
  import com.pub.book.BookBean;
  
  public class NewBookListServlet extends HttpServlet {
  
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        response.setContentType("text/xml; charset=euc-kr");
        PrintWriter out = response.getWriter();
        
        out.println("<?xml version="1.0" encoding="euc-kr" ?>");
        out.println("<book-list>");
        
        try {
           BookMgr mgr = BookMgr.getInstance();
           BookBean[] newBookList = mgr.getNewBookList();
           for (int i = 0 ; i < newBookList.length ; i++) {
              out.println("    <book>");
              out.println("        <isbn>"+newBookList[i].getIsbn()+"</isbn>");
              out.println("        <title>"+newBookList[i].getTitle()+"</title>");
              out.println("        <publisher>"+
                          newBookList[i].getPublisher()+"</publisher>");
              out.println("        <author>"+newBookList[i].getAuthor()+"</author>");
              out.println("        <page>"+newBookList[i].getPage()+"</page>");
              out.println("        <price>"+newBookList[i].getPrice()+"</price>");
              out.println("    </book>");
           }
        } catch(BookMgrException ex) {
           // getInstance()나 getNewBookList()에서 예외가 발생한 경우
           // <error> 태그를 사용하여 에러 메시지를 출력한다.
           out.print("    <error>");
           out.print(ex.getMessage());
           out.println("</error>");
        }
        out.println("</book-list>");
        out.flush();
        out.close();
     }
  }

위 코드에서 BookMgr 클래스는 출판사의 서적을 관리해주는 역할을 맡고 있으며, BookBean 클래스는 책 정보를 저장할 때 사용되는 자바빈이다. BookMgr 클래스의 getNewBookList() 메소드는 어제 하루 동안 나온 신간 목록을 구해주는 메소드로서 NewBookListServlet은 이 메소드를 사용하여 신간 정보를 저장하고 있는 BookBean의 배열을 구한다. 일단 BookBean 배열을 구하게 되면, 그 배열의 정보를 이용하여 신간 목록 정보를 나타내는 XML 문서를 클라이언트에 출력한다. (여기서 BookMgr과 BookBean에 대한 내용은 언급하지 않겠다.)

XML 문서를 출력하기 위해서는 ServletResponse의 setContentType() 메소드를 사용하여 출력될 컨텐츠의 타입이 XML 문서라는 것을 알려주어야 한다. 위 코드를 보면 "text/xml; charset=euc-kr" 로 지정해준 것을 알 수 있다. 뒤에 있는 charset은 출력될 글자의 캐릭터셋이 EUC-KR 이라는 것을 나타낸다. XML 문서를 출력하는 것은 위 코드에서 볼 수 있듯이 서블릿의 Writer 객체에 HTML 문서를 출력하는 것과 완전히 같다.

여러분 중에는 아마도 DOM이나 JDOM을 사용하여 XML 문서를 나타내는 트리를 생성한 후 그 트리를 XML 문서 형태로 스트림에 출력하는 방법을 선택할 사람도 있을 것이다. 물론, 그 방법이 나쁘지는 않으며 또한 코드가 좀더 의미 있어지는 것이 사실이다. 하지만, 이 글은 DOM이나 JDOM을 사용하여 XML 문서를 생성하는 것에 대해서 살펴보는 것이 아니므로 위 코드에처럼 간단한 방법을 사용하기로 한다.

웹브라우저에서 NewBookListServlet으로 HTTP 요청을 보내면 다음 그림과 같은 결과가 출력된다. XML 문서가 요청에 대한 응답으로 출력되는 것을 알 수 있다.


그림2 출판사의 NewBookListServlet의 결과화면

신간 목록을 XML 문서로 출력해주는 출판사의 서블릿을 살펴보았으니 이제 인터넷 서점 부분을 살펴보자. 인터넷 서점은 출판사의 서블릿으로부터 신간 목록을 읽어와 XML 문서를 파싱한 후 알맞게 DB에 정보를 삽입하면 된다. 먼저 간단하게 인터넷 서점에서 신간 목록을 읽어오는 흐름을 살펴보자.

  1. HttpMessage를 이용하여 출판사의 http://host/servlet/NewBookListServlet 에 연결한다.
  2. 출판사 서블릿 NewBookListServlet으로부터의 응답 결과로부터 DOM 트리를 생성한다.
  3. DOM 트리로부터 서적 정보를 읽어와 자바빈에 저장한다.
  4. 인터넷 서점의 예비 신간 서적 DB에 자바빈 정보를 추가한다.
지금까지 살펴본 내용과 크게 다르지 않다. 다른 점이 있다면 프로토콜 부분이 XML 문서로 대체되었다는 것 뿐이다. 이를 구현하기 위해 이 글에서는 인턴넷 서점에서 사용될 세 개의 클래스 NewBookListCollector, NewBook, NewBookServlet을 살펴보기로 하자.

먼저 NewBook 클래스는 앞에서 출판사 부분에서 언급했던 BookBean과 마찬가지로 인터넷 서점의 신간 DB 테이블에 저장할 데이터를 담고 있는 자바빈이다. 그리고 NewBookListCollector는 출판사의 서블릿/JSP/CGI 등으로부터 신간 목록을 저장하고 있는 XML 문서를 읽어와 파싱하여 NewBook 자바빈 인스턴스를 생성해주는 역할을 한다. NewBookServlet은 NewBookListCollector 클래스를 사용하여 신간 목록을 사용자에게 보여주는 역할을 한다. 즉, 전체 구조는 다음과 같다.


그림3 인터넷 서점과 출판사 사이의 업무 흐름

XML 문서를 이용한 정보 교환에서 핵심적인 역할을 맡고 있는 NewBookListCollector 클래스를 살펴보자. 일단 이 클래스의 완전한 소스 코드부터 보도록 하자. 완전한 소스 코드는 다음과 같다. (코드가 좀 길긴 하지만 내부 로직은 복잡하지 않으므로 차분하게 살펴보기 바란다.)

  package com.interbook;
  
  import javax.xml.parsers.DocumentBuilderFactory;  import javax.xml.parsers.DocumentBuilder;  import javax.xml.parsers.ParserConfigurationException;  import org.xml.sax.SAXException;  import org.xml.sax.InputSource;  import org.w3c.dom.*;  import java.io.*;
  import java.util.*;
  import java.net.URL;
  import javacan.http.util.HttpMessage;  
  public class NewBookListCollector {
    /**
     * 지정한 URL로부터 신간 목록이 담긴 XML 문서를 읽어와
     * 파싱한 후 NewBook 배열을 리턴한다.
     */
    public static NewBook[] getNewBook(String url) {
      BufferedReader br = null; // 지정한 URL로부터
      try {
        HttpMessage httpMessage = new HttpMessage(new URL(url));
        InputStream is = httpMessage.sendGetMessage();
        br = new BufferedReader(new InputStreamReader(is));
        
        ArrayList newBookList = new ArrayList(5);
        
        InputSource source = new InputSource(br);        
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = dbFactory.newDocumentBuilder();
        Document document = builder.parse(source);        
        // document 로부터 ROOT 요소를 구한다.(<book-list>)
        Element rootElement = document.getDocumentElement();
        for (Node subNode = rootElement.getFirstChild() ;
             subNode != null ;
             subNode = subNode.getNextSibling() ) {
          
          if (subNode.getNodeType() == Node.ELEMENT_NODE && 
              subNode.getNodeName().compareTo("book") == 0) {
            // <book> 노드일 경우
            NewBook book = new NewBook();            // <book> 요소의 하위 요소를 처리한다.
            NodeList childNodeList = subNode.getChildNodes();
            int len = childNodeList.getLength();
            for (int i = 0; i < len ; i++) {
              Node childNode = childNodeList.item(i);
              // <book> 요소의 하위 노드의 타입이 ELEMENT 일 경우
              if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                String childNodeName = childNode.getNodeName();
                String value = getSubTextNodeValue(childNode).trim();
                if (childNodeName.compareTo("isbn") == 0) {
                  book.setIsbn(value); 
               } else if (childNodeName.compareTo("title") == 0) {
                  book.setTitle(value);
                } else if (childNodeName.compareTo("publisher") == 0) {
                  book.setPublisher(value);
                } else if (childNodeName.compareTo("author") == 0) {
                  book.setAuthor(value);
                } else if (childNodeName.compareTo("page") == 0) {
                  try {
                    book.setPage(Integer.parseInt(value));
                  } catch(NumberFormatException ex) {
                    book.setPage(-1);
                  }
                } else if (childNodeName.compareTo("price") == 0) {
                  try {
                    book.setPrice(Integer.parseInt(value));
                  } catch(NumberFormatException ex) {
                    book.setPage(-1);
                  }
                }
              }
            }
            newBookList.add(book);
          } else if (subNode.getNodeType() == Node.ELEMENT_NODE && 
                       subNode.getNodeName().compareTo("error") == 0) {
            // <error> 요소인 경우 알맞은 처리
            // 이 예제에서는 처리하지 않는다.
            return null;
          }
        }
        
        NewBook[] bookList = new NewBook[newBookList.size()];
        newBookList.toArray(bookList);
        return bookList;      } catch(Exception ex) {
        // 처리하는 과정에서 예외가 발생한 경우 null을 리턴.
        ex.printStackTrace();
        return null;      } finally {
        if (br != null) try { br.close(); } catch(IOException ex) {}
      }
    }
    
    private static StringBuffer buff = new StringBuffer(512);
    
    private static String getSubTextNodeValue(Node node) {
      NodeList subNodeList = node.getChildNodes();
      int len = subNodeList.getLength();
      
      if (buff.length() > 0) buff.delete(0, buff.length());
      
      for (Node subNode = node.getFirstChild() ;
           subNode != null ;
           subNode = subNode.getNextSibling() ) {
        if (subNode.getNodeType() == Node.TEXT_NODE ) {
          buff.append(subNode.getNodeValue());
        } else if (subNode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
          buff.append(subNode.getFirstChild().getNodeValue().trim());
        }
      }
      return buff.toString();
    }
  
  }

위 코드를 보면 HttpMessage.sendGetMessage() 메소드를 사용하여 출판사의 /serlvet/NewBookListSerlvet에 접속한 후, NewBookListServlet이 전송하는 XML 문서를 Reader를 통해서 읽어들인다. XML 문서를 파싱하기 위해서 org.xml.sax.InputSource 클래스를 사용하고 있는데 InputSource는 DocumentBuilder가 다양한 자원으로부터 XML 문서를 읽어와 DOM 트리인 Document 객체를 생성할 수 있도록 해 준다. 일단 Document 객체가 생성되면 이제 그 Document 객체로부터 필요한 정보를 추출해내기만 하면 된다.

위 코드에서 XML 문서를 처리하는 부분이 있는데 조금 복잡해 보이지만 JAXP API와 DOM API를 사용하는 것에 대해서 조금만 공부한다면 쉽게 이해할 수 있을 것이다. 자바에서 DOM API를 사용하는 것에 대한 내용은 자바와 XML(한빛출판사)을 참고하기 바란다. 또는, 자바캔의 일반 강좌 게시판에 김인희씨가 올린 글을 참조해도 된다.

이제 인터넷 서점은 출판사로부터 XML 문서로 된 신간 목록을 읽어올 수 있게 되었다. 남은 작업은 서블릿을 사용하여 인터넷 서점 관리자에게 인터페이스를 제공하는 것이다. 이는 그림 3에서 보듯이 인터넷 서점의 NewBookServlet을 통해서 이루어진다. 이 글에서는 간단하게 출판사로부터 읽어온 신간 목록을 화면에 보여주도록 NewBookSerlvet을 구현해보았다. 다음은 NewBookServlet의 코드이다.

  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  
  import com.interbook.NewBook;
  import com.interbook.NewBookListCollector;
  
  public class NewBookServlet extends HttpServlet {
  
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        
        NewBook[] list = NewBookListCollector.getNewBook(
                 "http://localhost:8080/servlet/NewBookListServlet");        
        response.setContentType("text/html; charset=euc-kr");
        PrintWriter out = response.getWriter();
        
        out.println("<html><head><title>신간 목록</title></head>");
        out.println("<body>");
        if (list == null) {
           out.println("신간 목록을 읽어오는 과정에서 에러가 발생하였습니다.");
        } else {
           out.println("신간 목록");
           
           for (int i = 0 ; i < list.length ; i++) {
              out.println("<hr>");
              out.println("도서명:"+list[i].getTitle());
              out.println("출판사:"+list[i].getPublisher());
              out.println("저자:"+list[i].getAuthor());
              out.println("가격:"+list[i].getPrice());
           }
        }
        out.println("</body></html>");
        out.flush();
        out.close();
     }
  }

물론, 실제로 인터넷 서점에서 사용되는 NewBookServlet은 위와 같이 단순하진 않을 것이며 다양한 기능을 제공할 것이다. 하지만 이 글는 여러분에게 XML과 웹-투-웹을 이용한 정보 교환을 보여주는 것에 중점을 두고 있으므로 다른 부차적인 기능은 살펴보지 않기로 한다.

실제로 웹브라우저를 통해서 NewBookServlet에 접근하면 다음과 같은 결과가 출력된다.


그림4를 보면 인터넷 서점의 관리자가 출판사로부터 읽어온 신간 목록을 간단하게 웹 브라우저를 통해서 읽어올 수 있다는 것을 알 수 있다. 이런 방법으로 인터넷 서점은 여러 출판사로부터 동시에 신간 목록을 읽어올 수 도 있을 것이다.(물론, 모든 출판사는 동일한 DTD/스키마를 사용하여 XML 문서를 생성한다.) 기존의 어떤 방법보다도 간편하게 신간 목록을 추가할 수 있다는 것을 알 수 있다.

Web-to-Web의 장단점

이제야 비로서 웹-투-웹 프로그래밍의 장단점에 대해서 알아볼 때가 되었다. 1주에서 장단점에 대해서 언급할 수도 있었으나 간단하게나마 예제를 살펴본 후에 논하는 것이 더 알맞을 것 같아서 이렇게 2부에서 웹-투-웹의 장단점에 대해서 언급하도록 한다. 먼저 장점부터 살펴보도록 하자.

  • 구현이 비교적 쉽다.
    앞의 예제에서 보았듯이 웹을 기반으로 데이터를 주고 받는 것은 매우 간단하게 구현된다. 일종의 웹 프로그래밍이기 때문에 JSP/서블릿, ASP, PHP 등과 같은 것으로 웹 사이트를 구축하듯 구현하면 된다. 게다가 JSP나 ASP와 같은 스크립트 언어는 CGI나 펄에 비해 쉽게 구현할 수 있다는 장점도 갖고 있다.

  • 복잡한 프로토콜이 필요없다.
    데이터의 전달은 파라미터를 통해서 이루어지며 결과는 HTTP 프로토콜을 응답을 통해서 이루어지기 때문에 데이터 송수신과 관련된 복잡한 프로토콜을 정의할 필요가 없다. 특히 XML을 응답 결과로서 사용할 경우 XML 문서와 관련된 DTD와 스키마만 서로 공유하면 되며 또한 XSL/T를 사용하여 응답 결과를 알맞은 형태로 어렵지 않게 변경할 수 있다.

  • 특정한 프로그래밍 언어에 구애받지 않아도 된다.
    웹을 기반으로 하는 통신에서는 서로 HTTP 프로토콜을 사용하여 정보를 주고 받는다. 따라서 자바든 VB 스크립트든 아니면 C 언어이건간에 서비스를 제공할 웹 페이지를 구현할 때 사용한 언어는 아무런 문제가 되지 않는다. 단지, 정해진 프로토콜에 따라 알맞게 정보만 주고 받으면 된다.

  • HTTPS를 사용하여 주고 받는 데이터를 쉽게 암호화할 수 있다.
    HTTPS는 HTTP 프로토콜에서 주고 받는 데이터를 암호화한 것이다. 비록 1부에서 작성한 HttpMessage 클래스가 HTTPS를 지원하고 있진 않지만 자바와 관련된 보안 관련 확장 API를 사용하면 어렵지 않게 구현할 수 있다. 또한, JDK 1.4 부터는 보안 관련 API가 기본적으로 포함되어 있기 때문에 별도의 라이브러리를 설치할 필요도 없어진다.

장점에 대해서 살펴봤는데 모든 게 그렇듯이 장점만 있을리가 없다. 이제 단점에 대해서 살펴보자.

  • 복잡한 흐름제어는 불가능하다.
    HTTP 프로토콜은 기본적으로 '연결 -> 요청전송 -> 응답받음 -> 연결해제'의 간단한 과정을 거친다. 몇번에 걸쳐서 서로 요청/응답을 반복적으로 수행하는 SMTP나 FTP와 같은 프로토콜과 달리 HTTP 프로토콜은 한번의 요청과 그 요청에 대한 한번의 응답으로 구성되어 있다. 따라서, 흐름제어와 같이 여러번의 요청/응답을 필요로 하는 업무에는 적용하기 힘들다.

한번의 연결에서 여러 차례의 요청/응답이 이루어지는 구조는 단순한 HTTP 프로토콜로는 구현이 불가능하다. 물론, 여러번에 걸쳐서 연결을 할 경우 이 단점이 극복될 수는 있지만 연결을 열었다 끊었다를 반복하는 것이 그리 좋은 방법은 아닐 것이다.

결론

이번 2부에서는 웹 기반의 정보 교환이 실제로 어떻게 적용될 수 있는 지 간단한 예를 통해서 알아보았으며, 웹 기반 통신의 장/단점에 대해서 알아보았다. 비록 여기서 살펴본 것이 웹 기반의 정보 교환을 위한 유일한 방법은 아니지만, HTTP 프로토콜을 이용하여 기업 사이에 정보를 주고 받는 것이 현실적으로 사용할 수 있는 알맞은 방법이라는 것은 확실하다. 물론 엔터프라즈 환경에 알맞은 자바 메시지 서비스(Java Message Service; JMS)를 사용하여 정보 교환을 할 수도 있겠지만, 여전히 많은 기업들은 아파치(또는 IIS) 웹서버와 JSP/ASP/PHP/펄CGI 등을 사용하여 웹 사이트를 구축하고 있으며 이러한 상황에서 기업간 정보 교환을 손쉽게 하기 위한 대안으로서 HTTP 프로토콜을 이용한 웹 기반의 통신을 제시해 보았다. (필자 역시 현재 진행하고 있는 프로젝트에서 다른 기업과의 공유를 위해서 웹 기반의 통신을 하고 있다.)

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 황타 2011.11.21 10:49 신고  댓글주소  수정/삭제  댓글쓰기

    OpenAPI를 사용하면서 제대로 이해도 못하고 openConnection을 사용했었는데,
    1부, 2부 강의를 보니 이해가되네요~ㅎ
    좋은글 감사합니다~

  2. 자바초보 2013.07.15 19:09 신고  댓글주소  수정/삭제  댓글쓰기

    님완전 개쩌는듯 .... 감사해요

  3. 오우 2017.11.21 09:29 신고  댓글주소  수정/삭제  댓글쓰기

    쓸일이 있었는데 유용하게 보았습니다 ^^
    감사합니다.

데이터의 교환 수단이 아닌 데이터의 표현 수단으로서 XML을 응용해본다.

풀다운 메뉴와 XML

XML은 이제 개발자들이 알아야할 하나의 기술이 되어가고 있는 것 같다. XML은 표준화된 데이터를 표현할 수 있는 특징을 갖고 있으며, XSL/T를 사용하여 손쉽게 다른 포맷으로 변경할 수 있다는 장점을 갖고 있기 때문에, XML-EDI와 같은 B2B 분야에서 그 진가를 조금씩 발휘하고 있다.

하지만, B2B와 같이 데이터를 교환하는 수단으로서만 XML이 가치 있는 것은 아니다. XML의 가장 기본적인 특징은 XML이 데이터를 표현하는 언어라는 점이다. 이 글에서는 XML을 데이터로 사용하여 웹 사이트에서 최근에 많이 사용되고 있는 풀다운 메뉴를 구현하는 방법에 대해서 살펴볼 것이다.

풀다운 메뉴는 이미 모든 컴퓨터 사용자에게 익숙한 GUI(Graphical User Interface)이다. 우리가 사용하고 있는 대부분의 응용프로그램은 사용자와의 인터페이스를 위해 대부분 풀다운 메뉴를 사용하고 있으며, 마이크로소프트의 웹 사이트를 비롯한 많은 웹 사이트가 풀 다운 메뉴를 사용하고 있다.

풀 다운 메뉴의 구조적 특징은 계층 구조를 갖는다는 점이다. 예를 들어, 다음 그림은 많이 사용되고 있는 웹브라우저인 인터넷 익스플로러의 메뉴 화면을 캡쳐한 화면이다.


위 그림을 보면 '편집'이라는 메뉴 밑에 '복사', '붙여넣기' 등의 메뉴 아이템이 존재하는 것을 알 수 있다. '파일', '보기'와 같은 다른 메뉴 역시 여러 메뉴 아이템을 포함하고 있다. 여기서 전체 메뉴 시스템은 계층적인 구조를 갖고 있다는 점을 알 수 있다. 계층적인 구조는 XML의 기본적인 특징으로서 XML은 태그를 중첩시킴으로써 계층적인 데이터를 표현할 수 있다. 예를 들어, 위 그림에 나타나 있는 메뉴의 구조를 XML을 사용하여 표현하면 다음과 같을 것이다.

<?xml version="1.0" encoding="euc-kr"?>
<root>
   <Menu>
      <MenuName>파일(F)</MenuName>
      <MenuItem>
         <MenuItemName>열기</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>다른 이름으로 저장</MenuItemName>
      </MenuItem>
   </Menu>
   <Menu>
      <MenuName>편집</MenuName>
      <MenuItem>
         <MenuItemName>잘라내기</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>복사</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>붙여넣기</MenuItemName>
      </MenuItem>
   </Menu>
</root>

위 XML 문서를 살펴보면 <Menu> 요소는 메뉴 바에 있는 '파일', '편집'과 같은 메뉴를 나타내고, <Menu> 요소에 중첩되어 있는 <MenuName> 요소는 <Menu> 요소가 나타내는 메뉴의 이름을 나타낸다. 또한, <MenuItem> 요소는 그 요소가 중첩되어 있는 <Menu> 요소가 나태는 메뉴의 메뉴 아이템을 나타낸다. 여기서, XML이 계층적 구조를 쉽게 나타낼 수 있기 때문에 XML을 이용하여 풀다운 메뉴를 매우 간단하게 표현할 수 있다는 것을 알았을 것이다.

웹에서 사용될 풀다운 메뉴를 위한 XML 문서의 구조

웹에서 풀다운 메뉴를 구현할 경우, 각각의 메뉴와 메뉴 아이템은 관련된 링크를 갖게 된다. 이 링크를 나타내기 위해서는 단순히 앞에서 살펴본 XML 구조에 <HyperLink>와 같은 태그를 추가하기만 하면 된다. 예를 들면 다음과 같다.

<?xml version="1.0" encoding="euc-kr"?>
<root>
   <Menu>
      <MenuName>HOME</MenuName>
      <HyperLink>/main/index.jsp</HyperLink>
   </Menu>
   <Menu>
      <MenuName>Help</MenuName>
      <HyperLink>/help/index.jsp</HyperLink>
      <MenuItem>
         <MenuItemName>FAQ</MenuItemName>
         <HyperLink>/help/faq.html</HyperLink>
      </MenuItem>
      <MenuItem>
         <MenuItemName>About JavaCan</MenuItemName>
         <HyperLink>/help/aboutJavaCan.html</HyperLink>
      </MenuItem>
   </Menu>
   
</root>

위 XML을 살펴보면 <Menu> 요소와 <MenuItem> 요소는 각각 <HyperLink> 요소를 중첩하고 있으며, 각각의 <HyperLink>는 관련된 URL을 값으로 갖고 있는 것을 알 수 있다. DTD를 사용하여 위 구조를 명시적으로 정의할 수도 있지만, 이 글에서는 생략하기로 한다.

XMLMenu 클래스

이제 XMLMenu 클래스를 작성해보자. 이 클래스의 역할은 풀다운 메뉴를 나타내는 XML 문서를 읽어와 그것을 알맞게 HTML로 만들어주는 것이다. XMLMenu는 XML 문서를 DOM API를 통해서 읽어온다. DOM API는 XML 문서를 메모리상에 트리 구조로 읽어온다. 트리 구조는 그 자체가 계층적 구조이기 때문에 메뉴를 나타내는 XML 문서를 처리하기에 알맞다.

XMLMenu 클래스에서 핵심적인 메소드는 createMenu(String systemURI) 메소드와 generateHTML() 메소드이다. createMenu() 메소드는 파라미터로 메뉴 데이터를 저장하고 있는 XML 문서를 나타내는 URI를 받으며, 그 XML 문서를 로딩하여 분석한다. XMLMenu.createMenu() 메소드는 다음과 같다.

public void createMenu(String systemURI) throws SAXException, IOException {
   Document document = null;
   DOMParser parser = new DOMParser();   
   parser.parse(systemURI);
   document = parser.getDocument();
   
   // <Menu> 노드 목록을 구한다.
   NodeList nodeList = document.getDocumentElement().getChildNodes();
   Node node; // 현재 처리하고 있는 <Menu> 노드
   NodeList childNodes; // <Menu>의 자식 노드
   Node cNode; // 현재 처리하고 있는 <Menu>의 자식 노드
   Node cNode2; // 현재 처리하고 있는 <MenuName>의 자식 노드
   
   for (int i = 0 ; i < nodeList.getLength() ; i++) {
      node = nodeList.item(i);
      if ( node.getNodeType() == Node.ELEMENT_NODE &&
           node.getNodeName().compareTo("Menu") == 0) {
         // <Menu> element일 경우
         childNodes = node.getChildNodes();
         cNode = childNodes.item(1); // <MenuName> 노드
         String menuName = getNodeValue(cNode);
         cNode = childNodes.item(3); // <HyperLink> 노드
         String hyperLink = getNodeValue(cNode);
         
         menuList.add(menuName);
         menuLinkList.add(hyperLink);
         
         if (childNodes.getLength() > 5) {
            // <MenuItem> 노드가 있는 경우
            StringBuffer sb = new StringBuffer( (childNodes.getLength() - 5) * 250);
            
            for (int j = 5 ; j < childNodes.getLength() ; j++) {
               cNode = childNodes.item(j);
               if ( cNode.getNodeType() == Node.ELEMENT_NODE &&
                    cNode.getNodeName().compareTo("MenuItem") == 0) {
                  cNode2 = cNode.getChildNodes().item(1); // <MenuItemName>
                  String menuItemName = getNodeValue(cNode2);
                  cNode2 = cNode.getChildNodes().item(3); // <HyperLink>
                  String menuItemLink = getNodeValue(cNode2);
                  
                  String tempString =
"<span id=\""+menuName+"\" class=\""+menuItemSpanClass+"\">"+
"<a href=\""+menuItemLink+"\" class=\""+menuItemLinkClass+"\">"+menuItemName+
"</a></span><br>\n";
                  sb.append(tempString);
               }
            }
            menuItemList.add(sb.toString());
         } else {
            menuItemList.add("");
         }
      }
   }
   generateHTML();
}

createMenu() 메소드는 다음과 같은 순서로 작업을 진행한다.

  1. DOMParser를 사용하여 XML 문서를 읽어온다.
  2. <Menu> 노드의 목록을 구한 후, 이 노드의 자식 노드인 <MenuName>과 <HyperLink>의 값을 각각 menuList와 menuLinkList에 저장한다.
  3. <Menu> 노드가 <MenuItem> 노드를 갖고 있다면, <MenuItem> 노드를 읽어와 알맞은 처리를한다.
위 코드를 볼 때 주의해야 할점이 있다면, 공백문자(' ')나 '\n'과 같은 글자 역시 자식 노드가 된다는 점이다. 예를 들어, 다음과 같은 형태의 XML 문서가 있다고 해 보자.

   <Menu>
      <MenuName>HOME</MenuName>
      <HyperLink>/main/index.jsp</HyperLink>
   </Menu>

이 경우 <Menu> 노드는 <MenuName>노드와 <HyperLink> 노드를 자식 노드로 갖지만, <Menu>와 <MenuName>사이에 있는 '\n\t\t' 역시 자식 노드로 갖는다. 따라서 <Menu> 노드의 자식 노드중에서 <MenuName>노드를 구하기 위해서는 두번째 자식 노드를 참조해야만 한다. createMenu() 메소드에서 <Menu> 노드의 자식 노드인 <MenuName>과 <HyperLink> 노드를 구하는 코드를 살펴보면 이런 점을 알 수 있다.

createMenu() 메소드는 <MenuItem> 노드가 있을 경우, 그 자식 노드인 <MenuItemName>과 <HyperLink> 태그의 값을 이용하여 다음과 같은 형태의 HTML 태그를 생성한다.

<span id="[MenuName 노드의 값]" class="menuitemspan">
<a href="[HyperLink 노드의 값]" class="menuitemlink">[MenuItemName 노드의 값]</a></span><br>

위와 같은 형태의 태그는 menuItemList에 저장된다. createMenu() 메소드는 XML 문서에 있는 모든 노드를 알맞게 처리하면, generateHTML()메소드를 호출한다. generateHTML() 메소드는 createMenu() 메소드에서 분석한 결과를 이용하여 메뉴바와 각 메뉴와 관련된 메뉴 아이템 목록을 HTML 형식으로 출력한다. 메뉴바와 메뉴 아이템 목록은 HTML에서 레이어를 표현할 수 있도록 해 주는 <div> 태그를 사용하여 표시된다. 풀다운 메뉴를 구현하기 위해서는 마우스가 특정한 영역에 들어오거나 그 영역을 벗어나는 경우 알맞게 메뉴를 보여주어야 하기 때문에, 자바 스크립트를 사용해야 한다. 이 때 사용되는 자바 스크립트에 대한 내용은 관련링크에서 제공되는 소스 코드를 참조하기 바란다.

다음은 generateHTML() 메소드이다.

private void generateHTML() {
   menuList.trimToSize();
   menuLinkList.trimToSize();
   menuItemList.trimToSize();
   Object[] menuArray = menuList.toArray();
   Object[] menuLinkArray = menuLinkList.toArray();
   Object[] menuItemArray = menuItemList.toArray();
   
   StringBuffer output = new StringBuffer(1024*8); // buffer 8K
   output.append("<div id=\"menuBar\" class=\"").append(menuBarClass).append("\">");
   for (int i = 0 ; i < menuArray.length ; i++) {
      output.append(" <span id=\"m").append(i).append("\">")
            .append("<a href=\"").append(menuLinkArray[i].toString())
            .append("\" class=\"").append(menuLinkClass).append("\" ");
      
      if ( !menuItemArray[i].toString().equals("") ) {
         output.append("onMouseOver=\"viewMenu('m").append(i)
               .append("', 'menu").append(i).append("')\" ")
               .append("onMouseOut=\"checkBound('m").append(i)
               .append("', 'menu").append(i).append("')\"");
      }
      output.append(">");
      output.append(menuArray[i].toString()).append("</a> </span>");
      
      if ( i < menuArray.length - 1 )
         output.append(" | ");
   }
   output.append("</div>");
   
   menuBar = output.toString();   output.delete(0, output.length());
   
   for (int i = 0 ; i < menuItemArray.length ; i++) {
      String temp = menuItemArray[i].toString();
      if (!temp.equals("")) {
         output.append("<div id=\"menu").append(i).append("\" ")
               .append("class=\"").append(menuItemDivClass).append("\" ")
               .append("onMouseOut=\"checkBound('', 'menu")
               .append(i).append("')\">\n");
         output.append(temp);
         output.append("</div>");
      }
   }
   menuItemLayer = output.toString();   output.delete(0, output.length());
}

XMLMenu 클래스를 컴파일하기 위해서는 알맞은 XML 파서가 필요하다. 필자는 여기서 아파치의 Xerces 파서를 사용하였다. 아파치 Xerces 파서는 http://xml.apache.org에서 구할 수 있다. 사용가능한 파서에 대한 내용은 자바캔의 또 다른 기사인 'XML의 소개와 XML 관련 기술'를 참고하기 바란다.

XMLMenu의 사용

XMLMenu.generateHTML() 메소드를 살펴보면 HTML로 변환한 결과를 각각 menuBar와 menuItemLayer에 저장하는 것을 알 수 있다. menuBar는 메뉴바와 관련된 HTML 코드를 갖고 있으며, menuItemLayer는 각각의 메뉴와 관련된 HTML 레이어를 값으로 갖게 된다. 이 두 값은 각각 XMLMenu.getMenuBar() 메소드와 XMLMenu.getMenuItemLayer() 메소드를 통해서 구할 수 있다.

JSP 페이지나 서블릿에서 XMLMenu 클래스를 사용하는 예제는 다음과 같다.

<%@ page language="java" session="false" %>
<%@ page import="javacan.xml.xmlmenu.XMLMenu" %>
<%
   XMLMenu xmlMenu = new XMLMenu();
   String filepath = "file:///" + getServletContext().getRealPath("xmlmenu.xml");   try {
      xmlMenu.setCSSClassName("menubar", "menulink", 
        "menuitemdiv", "menuitemspan", "menuitemlink");
      xmlMenu.createMenu(filepath);   } catch(Exception ex) {
      ex.printStackTrace();
   }
   String menuBar = xmlMenu.getMenuBar();   String menuItemLayer = xmlMenu.getMenuItemLayer();%>
<html>
<head>
<title>XML Hierarchical Menus</title>
<link rel=STYLESHEET type="text/css" href="xmlmenu.css">
<Script Language="JavaScript" src="xmlmenu.js"></script>
</head>
<body id="body" bgcolor="#FFFFFF" 
         TOPMARGIN="0" LEFTMARGIN="0" MARGINWIDTH="0" MARGINHEIGHT="0" >
<center>
<table width="500">
<tr>
   <td><%= menuBar %></td>
</tr>
<tr>
   <td>위 메뉴 시스템은 xmlmenu.xml 문서를 XMLMenu 클래스를
          사용하여 만들어진 것입니다.</td>
</table>
</center><%= menuItemLayer %></body>
</html>

위 JSP 페이지를 웹 브라우저에서 출력한 결과는 다음과 같다.


위 그림에서 메뉴바는 XMLMenu.getMenuBar() 메소드의 리턴 값을 출력해서 생성되며, 각각의 메뉴에 마우스가 올라갈 때 나타내는 메뉴 아이템들은 XMLMenu.getMenuItemLayer() 메소드의 리턴값을 출력해서 생성된다.

위 코드에서 사용된 자바스크립트와 스타일쉬트 파일은 "xmlmenu.js"와 "xmlmenu.css"는 관련 링크에 있는 소크 코드를 통해서 구할 수 있다.

결론

이 글에서는 XML을 사용하여 웹 페이지의 풀다운 메뉴를 표현하는 것에 대해서 알아보았다. 이처럼 XML을 사용하여 메뉴를 생성할 경우의 장점은 메뉴를 변경하기 위해서 JSP나 서블릿의 코드를 변경할 필요가 없다는 점이다. 출력되는 모양의 경우 CSS 파일만 변경해주면 되며, 이벤트에 대한 반응을 다르게 하고 싶을 경우 자바스크립트 파일만 변경해주면 된다. 즉, 풀다운 메뉴 자체가 거의 완벽하게 모듈화되는 것이다.

XML의 응용 분야는 무수히 많다고 할 수 있다. 특히, 데이터를 표현하는 분야에서는 XML을 손쉽게 활용할 수 있다. 이 글에서는 그러한 활용 중의 하나만을 선보였을 뿐이다. 독자 여러분 스스로도 XML을 다양한 곳에서 활용하는 방법을 찾아보기 바란다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

XML과 XML 관련 기술에 대해서 간략하게 알아보며, 자바와 XML 사이의 관계에 대해서 알아본다.

XML은 무엇인가?

XML! XML! XML! 만약 여러분이 IT 세상의 흐름과 함께 하고 있다면 XML에 대한 많은 것들에 대해서 들어보았을 것이며, 이제 막 개발자의 세계에 발을 들여놓았다 하더라도 적어도 한번 정도는 'XML'이란 단어를 들어보았을 것이다. 현재 마이크로소프트, 썬 마이크로시스템즈, 오라클, IBM과 같은 IT 분야를 대표하는 대부분의 기업들은 XML을 분석(parsing)하고 변환(transformation)할 수 있는 여러 도구들을 제공하고 있으며, 최근에 각 기업이 내 놓고 있는 제품들은 XML을 여러 형태로 사용하고 있다. 이러한 XML 영역의 증가추세는 앞으로 더욱 가증될 것으로 예상된다. 이번 Article에서는 초보 개발자 뿐만 아니라 아직까지 XML이 무엇인지에 대해서 자세하게 알지 못하는 개발자들을 위해 XML이 무엇인지, 어디서 XML을 사용하는지 그리고 XML을 왜 사용하는 지에 대한 전반적인 내용을 간략하게 알아볼 것이다.

XML의 정의

XML은 'Extensible Markup Language'이다. 우리말로 하자면 '확장가능한 마크업 언어' 정도가 될 것이다. XML은 앞서 나온 SGML과 마찬가지로 다른 언어를 정의할 때 사용되는 메타언어(metalanguage)이다. 하지만, XML은 SGML 보다 훨씬 더 간단하다. 또한, 오늘날 가장 많이 사용되고 있는 마크업 언어인 HTML의 확장성이 거의 없는 반면에 XML은 거의 무한의 확장성을 갖고 있다. 이러한 XML의 확장성은 XML이 문법(grammar)이나 '태그집합(tag set)'을 규정하지 않은 마크업 언어이기 때문에 가능하다. 간단히 비교를 하기 위해 HTML과 비교해보자. 현재 나와 있는 HTML 규약은 사용자가 사용할 수 있는 태그 및 속성의 종류를 제한하고 있다. 다시 말해서, HTML은 미리 정의된 태그 집합과 문법을 갖고 있다. 예를 들어, HTML에서 <table> 태그를 사용할 수는 있지만, <furniturelist> 태그를 사용할 수는 없다. HTML 문서를 사용하는 어플리케이션(거의 웹 브라우저일 것이다)에 대해 <table> 태그는 특정한 의미를 지니며 표의 시작을 나타낼 때 사용되는 반면에, <furniturelist> 태그는 HTML 문서를 사용하는 어플리케이션에 대해 어떤 의미도 갖지 않으며 웹 브라우저는 이 태그를 처리하지 않고 무시할 것이다. 이는 HTML을 정의할 때, HTML 규약에 사용가능한 태그 집합을 정의했기 때문에 그렇다. 따라서 새로운 태그를 추가하거나 불필요한 태그를 삭제하기 위해서는 새로운 버전의 HTML 규약을 발표해야만 한다. 또한, HTML은 언어에 정의된 태그의 올바른 사용법을 정의한 '문법(grammar)'을 갖고 있다. 예를 들어, <tr> 태그는 반드시 <table> 태그에 중첩되어야 하며, <table> 태그는 width, border, cellpadding과 같은 속성의 값을 지정할 수는 있지만 type이라는 속성을 지정할 수는 없다.

반면에 XML은 미리 정의된 태그 집합이나 문법을 규정하고 있지 않기 때문에 HTML과는 달리 완전한 확장성을 가진다. XML 문서 작성자는 원하는 태그를 사용할 수 있으며, 태그에 원하는 속성을 지정할 수 있으며, 원하는 형태로 태그를 중첩시킬 수 있다. 즉, 자신만의 태그 집합과 문법을 만들 수 있다는 것이다. 예를 들어 다음의 간단한 XML 문서를 살펴보자.

<?xml version="1.0" encoding="euc-kr"?>
<furniture-list>
    <table type="B" class="보급형">
        <productName>XX 책상</productName>
        <drawer>4</drawer>
        <hasBookshelf>true</hasBookshelf>
        <hasLamp>true</hasLamp>
        <target>학생</target>
        <price>130000</price>
    </table>
    <chair class="고급형">
        <productName>듀오 의자</productName>
        <target>모두</target>
        <price>35000</price>
    </chair>
    <bed>
        <productName>에이스 침대</productName>
        <size>2</size>
        <target>모두</taget>
        <price>280000</price>
    </bed>
</furniture-list>

예제 XML 문서를 보면 HTML과는 많이 다르다는 것을 알 수 있다. 여기서 사용된 <table>, <size>, <target>과 같은 태그는 문서 작성자가 만든 것이며, 각 태그간의 중첩 관계 역시 작성자에 의해 구성된 것이다. 이것이 바로 XML의 힘이다. 여러분은 XML 규약이 요구하는 일반적인 구조에 따라 XML 문서를 만드는 한, 다양한 방법으로 데이터의 내용을 정의할 수 있으며, 이에 따라 데이터를 표현하는 데 있어 HTML로는 불가능한 유연성을 갖게 된다.

이러한 XML의 유연성은 XML의 가장 큰 장점중의 하나이면서 동시에 단점이 되기도 한다. 한 가지 목적을 위해 여러 다양한 방법을 사용할 수 있기 때문에, 데이터의 표현과 변환을 처리하기 위한 많은 다른 규약들이 존재하며, 이러한 규약을 통해서 XML은 유연성에 의해 발생하는 단점을 보완하고 있다. 실제로 XML 기술이라고 하면, 단순히 XML을 의미하기 보다는 XML 및 XML 관련 기술을 의미하는 경우가 더 많다.

XML을 HTML과 비교할 때, 또 하나의 큰 차이점은 XML은 표현(presentation)을 위한 데이터가 아닌 내용을 위한 데이터라는 점이다. HTML의 경우 <code>와 <strong>은 그 태그의 값이 각각 프로그래밍 코드와 강조된 것이라는 것을 나타내는 내용 기반의 태그인 반면에 <b>와 <i>는 태그는 내용을 어떻게 출력하라는 표현에 중점을 태그이다. 즉, 표현과 내용이 하나의 문서에 혼합되어 있는 것이다. 따라서, XML 문서를 작성할 때 표현을 어떻게 할 것인가에 대해서는 전혀 생각할 필요가 없으며, 단지 내용을 어떻게 XML 문서로 나타낼 것이가엔 대해서만 생각하면 된다.

XML에 대해서 이해하기 위해서는 XML 뿐만 아니라 XML 관련 기술에 대해서도 이해하고 있어야 한다. 이제부터 XML과 관련된 기술들에 대해 간단하게 알아보도록 하자.

XML

XML은 모든 XML 관련 기술의 핵심이다. XML은 핵심 언어 자체를 정의하고 메타데이터 타입의 구조를 정의한다. XML에 기반한 모든 다양한 기술을 통해서 개발자와 콘텐트 관리자들은 데이터 관리와 전송 측면에 있어서 전에 없던 유연성을 제공받게 되었다. 현재 1.0 규약의 권고안(Recommendation)이 나온 상태이다. XML 1.0 규약은 http://www.w3.org/TR/REC-xml에서 참조할 수 있다.

XML 문서는 처리 지시어(Processing Instruction; PI)와 DTD(Document Type Definition; 문서 타입 정의)를 가질 수 있다. PI는 XML 문서를 사용하는 어플리케이션이 특정한 작업을 하도록 지시하는 일종의 명령어이다. DTD는 XML 문서에서 사용할 태그가 따라야 할 문법(사용가능한 태그, 사용가능한 속성, 가능한 태그의 중첩)을 정의한다. 즉, XML 문서는 DTD에 의해 제약받게 된다. 만약 XML 문서가 DTD를 참조하고 있다면, 그 문서는 반드시 DTD에서 지정한 문법에 지정되어 있는 태그와 속성만을 사용해야 하며, DTD에 정의되어 있는 순서대로 각 태그의 순서를 지켜야 하며, DTD에 정의된 중첩 순서대로 각 태그의 중첩 순서를 정해주어야 한다. DTD를 통해서 XML 문서는 모호함을 없앨 수 있게 된다. 예를 들어, 앞의 예제 XML 문서에서 <table> 태그가 책상을 의미하는지 혹은 표를 의미하는지, 그리고 class 속성이 가질 수 있는 값이 어떤 것이 있는 지 어떻게 결정할 수 있는가? DTD를 통해서 이러한 결정들을 쉽게 할 수 있게 된다. 또한, DTD는 XML을 사용하여 데이터를 주고 받는 어플리케이션 사이에서 중요한 역할을 한다. 왜냐면, 두 어플리케이션은 서로를 이해하기 위해서 각 시스템 사이에 협의된 포맷팅(formatting)과 구문을 필요로 하며, DTD가 바로 이러한 것을 제공하기 때문이다.

참고로, DTD는 XML 형식이 아닌 그것만의 규약을 갖고 있다. 예를 들면 DTD는 다음과 같은 형태로 구성되어 있다.

<!ELEMENT furniture-list (table | chair | bed)+>
<!ELEMENT table (productName, drawer, hasBookshelf, hasLamp, target, price) >
<!ATTLIST tale
            type CDATA #REQUIRED
            class (보급형, 고급형) "보급형">
<!ELEMENT productName #PCDATA>
<!ELEMENT drawer #PCDATA>
.....

완전히 XML과 다른 형태로 XML 문서의 문법을 지정하는 것을 알 수 있다. 이에 따라 DTD는 몇 가지 한계점을 갖고 있으며, 이는 다음과 같다.

  • 계층(hierarchy) 개념이 없다. (즉, 개층 개념이 없다!)
  • 이름공간을 유연하게 처리하기 어렵다.
  • XML 문서 사이에 연관성을 줄 수 있는 방법을 갖고 있지 않다.
이러한 한계점이 발생하게 된 원인은 DTD 규약을 처음 작성할 때 지금처럼 많은 곳에서 XML이 사용될 것이라고 예상하지 못했기 때문이며, 따라서 지금처럼 한계를 갖게 되는 것은 어쩌면 당연한 것이다. 하지만, 이러한 한계점은 개발자들을 괴롭히는 요인이 되기도 한다. 따라서 이러한 한계점을 없애야 할 필요성이 생겼으며, 그것들을 해결하기 위해 나온 규약이 바로 XML 스키마(Schema)이다. XML 스키마에 대해서는 잠시 뒤에 알아보기로 하자.

이름공간(Namespace)

이름공간은 요소(element; 일반적으로 태그를 element라고 하며, 요소라고 번역한다)의 접두어(prefix)와 URI 사이의 매핑(mapping)이다. 이름공간은 일반적으로 태그가 속한 이름공간에 따른 이름 충돌 문제를 해결할 때 사용된다. 예를 들어, 앞의 XML 예제에서 <table>, <chair>, <bed> 태그가 상점에서의 판매 가격과 공장도 가격을 표시해야 한다고 해 보자. 이 경우 여러분은 어떤 태그를 사용할 것인가? 이미 <price> 태그가 사용되고 있기 때문에, <factory-price>와 같은 새로운 태그를 사용해야 할 것이다. 만약 할인가격이나 도매가격과 같은 또 다른 형태의 가격이 필요하다면? 필요한 만큼의 <xxx-price> 형식의 태그를 만들어야 할 것이다. 이름공간은 접두어를 사용하여 같은 이름을 갖는 태그를 사용할 수 있도록 해 줌으로써 이러한 문제점을 해결해준다. 예를 들어, 이름공간을 사용하면 상점 판매가, 공장도가격, 도매가격을 각각 <shop:price>, <factory:price>, <wholesale:price>의 태그로 표시할 수 있다. 즉, 같은 이름('price')을 가지는 세 개의 태그를 별도의 이름공간에 속하게 함으로써 충돌없이 같은 이름을 가진 태그(즉, 같은 의미를 갖는 태그)를 사용할 수 있는 것이다. 여기서 세미콜론 앞에 있는 이름이 접두어이며, 각 접두어는 특정한 URI와 연관되어 있다. 이름공간은 XML 문서에서 자주 사용고 있으며 XML 스타일쉬트, XML 스키마를 비롯한 많은 XML 관련 규약에서도 사용되고 있다. 이름 공간 관련 규약은 http://www.w3.org/TR/REC-xml-names/에서 참조할 수 있다.

XSL와 XSLT

XSL은 'Extensible Stylesheet Language'을 의미하며, 한 형식의 XML 데이터를 다른 형식으로 변환하고자 할 때 사용된다. 예를 들어, 하나의 XML 문서를 HTML, PDF, PS 형태로 변환해야 한다고 가정해보자. 이 경우 우리는 XML 문서를 일일이 복사하여 각 포맷에 알맞게 변환해야 할 것이다. XSL은 이렇게 일일이 복사할 필요없이, 이러한 종류의 작업을 수행해주는 스타일쉬트를 정의해주는 방식을 제공한다. 다시 말하면, XSL이 XML 데이터를 표현을 위한 포맷으로 변경해준다는 것이다. 앞에서 XML 문서는 내용을 위한 데이터라고 했던 점을 기억할 것이다. 그렇다면 XML 문서를 어떻게 웹 브라우저와 같은 클라이언트 프로그램에서 표현할 수 있을 것인가? 바로 XSL을 통해서 가능하게 되며, XSL은 내용과 표현을 완전히 분리해준다. 문서를 변환하기 위해 XSL 문서는 '포맷팅 객체(formatting object)'를 포함할 수 있다. 포맷팅 객체는 특정한 이름의 태그이며, 이 태그는 변환할 문서의 타입에 맞는 알맞은 내용으로 변경될 수 있다. 예를 들어 XML 문서를 PDF로 변환할 경우 포맷팅 객체에 해당하는 태그는 PDF에 알맞은 정보로 변경될 것이다.

XSLT는 'Extensible Stylesheet Language Transformation'를 의미하며, 포맷팅 객체가 아닌 완전한 텍스트 기반의 변환을 나타낸다. 일반적으로 XML 문서의 변환은 텍스트 위주로 이루어지기 때문에, 따라서 XSLT가 많이 사용된다.

XML 문서를 다른 형식으로 변환하는 것은 보통 XML 문서에 있는 특정 요소 A를 변환될 문서의 특정 요소 B로 바꾼다는 것을 의미한다. 이러한 변환을 처리하기 위해서는 어떤 요소를 변형할 지, 그리고 요소의 어떤 속성의 값을 처리할 지, 혹은 각 요소의 값에 따라 어떤 형식으로 변환할지를 결정할 수 있어야 한다. 이러한 요소의 선택 문제는 XPath를 통해서 이루어지며 XPath에 대해서는 잠시 뒤에 알아본다.

XSL 1.0 규약은 http://www.w3.org/TR/xsl/에서 참조할 수 있으며, XSLT 1.0 규약은 http://www.w3.org/TR/xslt에서 참조할 수 있다.

XML 스키마(Schema)

앞에서 DTD는 그 자체가 XML로 되어 있지 않으며, 뿐만 아니라 그에 따른 여러가지 한계점들이 발생한다고 하였다. DTD가 XML의 계층 구조를 공유하지 않는 다는 것은 이미 앞에서 DTD의 한계점에서 언급한 바 있다. 이 외에도 DTD는 XML과 같은 방법으로 데이터를 표시할 수 조차 없다. 반면에 DTD 이외에 XSL, XHTML, 이름공간 등의 다른 XML 관련 규약들은 그것의 목적을 표시하기 위해서 XML의 요소, 속성 등을 사용한다. 이러한 상황은 DTD를 다소 이상한 것으로 만들었으며, XML 문서를 어떻게 작성해야 한다는 것을 정의하기 위해 일반적으로 DTD를 사용하기 때문에 어떤 혼동을 일으키기도 했다.

XML 스키마는 XML 문서를 어떻게 작성해야 한다는 것을 정의하기 위해 XML 그 자체를 사용함으로써 DTD가 안고 있던 많은 한계점을 해결하였다. "데이터에 대한 데이터를 정의하는" 방법으로서 XML 그 자체를 사용함으로써 XML 스키마는 계층적 구조를 사용할 수 있으며, 확장성을 갖게 되었으며, 이름공간의 처리 역시 손쉽게 할 수 있게 되었다.

XML 스키마의 요구안을 http://www.w3.org/TR/NOTE-xml-schema-req에서 참조할 수 있다.

XPath

앞에서 XSLT에 대해서 언급할 때, XPath를 사용하여 변환할 대상을 선택하였다. XPath 규약은 XML 문서에 있는 특정한 항목을 어떻게 위치시킬지를 정의하고 있으며, XML 문서에 있는 어떤 '노드(node)'를 참조함으로써 이것을 하게된다. XPath는 XML 문서를 트리로 간주하며, 따라서 여기서 노드는 요소, 속성 또는 텍스트 데이터를 포함한 XML 데이터의 일부를 나타낸다. 실제로 노드를 위치시키기 위해서 XPath는 표현식을 사용한다. 이 표현식이 어떻게 구성되는 지에 대한 내용은 http://www.w3.org/TR/xpath에서 참고할 수 있다.

XQL

XQL은 'Extensible Query Language'를 의미하며, Query에서 알 수 있듯이 XQL은 XML 문서 형식을 사용하여 쉽게 데이터베이스 질의(query)를 표현할 수 있도록 하기 위해 설계된 질의 언어(query language)이다. XQL은 질의(query) 언어를 표현하기 위해 XPath 개념을 사용하고 있다. 왜 XPath 개념을 사용하는지 알아보기 위해 데이터베이스의 특정한 테이블로부터 데이터를 읽어오는 SQL 문장을 생각해보자.

select id, name, password from member_table where id = 'madvirus'

위의 SQL 문장을 보면 member_table이라는 테이블로부터 id 값이 madvirus인 행의 id, name, password 필드값을 읽어오는 것을 알 수 있다. 여기서 중요한 것은 id, name, password나 member_table과 같은 것들이 모두 XML 문서의 특정한 노드로간주될 수 있다는 점이다. (데이터베이스와 XML과의 매핑을 한번 생각해보면 그럴 것이라는 것을 알 수 있을 것이다). 또한, XQL은 질의의 결과 집합을 표준 XML을 사용하여 표시한다. 이때, XML 문서는 XQL에 특정한 태그 집합을 통해서 표현된다.

XSP

XSP라는 단어를 보면서 JSP나 ASP와 비슷한 기술이 아닐까라는 생각을 할 지도 모르겠다. 혹시 XSP가 JSP나 ASP와 비슷하게 서버사이드 스크립트 언어를 나타내는 것이 아닐까라고 생각했다면, 어느 정도 맞게 추측한 것이다. XSP는 'Extensible Server Pages'를 의미한다. XSP는 XML에 기반하고 있으며, 따라서 언어에 독립적이고 웹 페이지와 웹 사이트를 만드는 데 있어서 스크립트 언어 대신 사용될 수 있다. 표현과 내용의 구분에 있어 완전하지 못한 JSP에 비해, XSP는 완전하게 이 둘을 구분해준다. JSP는 JSP 페이지 내에 로직 부분을 담고 있는 반면에 XSP는 로직 부분을 로직쉬트(logicsheet)라는 것에 정의한다. 로직쉬트는 스타일쉬트와 비슷하며, 이를 통해 XSP는 표현의 내요을 완전히 분리해준다. 이러한 구분은 개발자들이 동적이나 동적으로 내용의 생성에만 집중할 수 있도록 해 주고, 반면에 XML과 XSL 제작자들은 XSP 페이지에 적용할 XSL 스타일 쉬트를 변경함으로써 표현과 스타일만을 처리할 수 있도록 해 준다.

XSP는 현재 웹 출판 프레임워크(Web Publishing Framework)인 아파치 코쿤(Cocoon)에 속해 있다. XSP에 대해 자세히 알고자 하는 사람은 http://xml.apache.org/cocoon/xsp.html을 참조하기 바란다.

지금까지 XML과 관련된 기술에 대해서 알아보았다. 이 외에도 XLink, XLL과 같은 많은 XML 관련 규약들이 존재하지만, 여기서는 자바와 관련해서 많이 사용되는 또는 사용될 기술들에 대해서만 알아보았다.

XML의 활용

XML을 어떻게 사용하는가

XML이 아무리 좋은 개념을 갖고 있다고 해도 개발자들이 익숙환 프로그래밍 환경에서 사용할 수 없다면 쓸모 없는 기술에 불과할 것이다. 다행스럽게도 프로그래밍에서 손쉽게 XML을 분석하고, 처리하고, 변환할 수 있도록 해주는 몇몇 API가 발표되었으며, 자바 개발자들은 이러한 API 중에서 알맞은 것을 선택해서 XML을 이용한 자바 프로그래밍을 손쉽게 할 수 있다. 이러한 API에는 SAX, DOM, JAXP, JDOM 등이 있다.

SAX

SAX는 'Simple API for XML'을 의미하며, 그 이름 그대로 XML을 위한 간단한 API를 제공한다. SAX는 XML 데이터를 분석하기 위한 이벤트 기반의 구조를 제공하며, 이러한 구조는 크게 문서를 읽어나가는 과정과 데이터를 사용할 수 있는 부분으로 분리된다. 이벤트는 XML 문서를 순차적으로 처리하는 동안 각 단계에서 발생하며, SAX는 각 이벤트가 발생할 때 호출되는 메소드를 정의하고 있다. 예를 들어, 한 요소의 여는 태그를 만날 경우 startElement() 메소드를 호출하며, 끝 태그를 만날 경우 endElement() 메소드를 호출한다.

SAX는 문서를 읽어나가는 과정에서 발생하는 이벤트를 위한 인터페이스 뿐만 아니라, 잘못된 문서나 비적격(non well-formed) 문서와 같이 XML을 분석하는 과정에서 발생할 수 있는 다양한 상황을 처리할 수 있도록 해 주는 에러와 경고 집합을 정의하고 있다.

DOM

DOM은 'Document Object Model'을 의미한다. SAX가 단지 XML 문서의 데이터에 접근하기 위한 방법을 제공한다면, DOM은 그러한 데이터를 처리하는 방법을 제공하기 위해 설계되었다. DOM은 XML 문서를 트리 형태로 표현한다. 자바를 비롯한 프로그래밍 언어에서는 트리 구조를 쉽게 순회하고 처리할 수 있기 때문에, DOM 트리(XML 문서를 DOM으로 표현한 것을 DOM 트리고 부른다)를 쉽게 처리할 수 있다. SAX와 달리 DOM은 전체 XML 문서를 메모리에 읽어온 후에 DOM 트리를 구성하기 때문에, 한번 문서를 읽으면 매우 빠르게 전체 문서에 접근할 수 있다.

DOM이 전체 XML 문서를 메모리에 읽어온 후에 DOM 트리를 작성한다는 것이 빠르게 XML 문서의 각 요소에 접근할 수 있다는 장점을 제공하긴 하지만, 반면에 결정적인 단점을 제공하기도 한다. DOM은 XML 문서의 크기에 비례한 메모리를 필요로 하기 때문에, XML 문서의 크기가 커질수록 많은 메모리를 요구하게 된다. XML 문서의 매우 클 경우 이는 매우 많은 양의 시스템 자원을 사용하게 되며, 따라서 시스템의 전체적인 성능 저하 현상을 일으키기도 한다.

JAXP

JAXP는 썬이 자바에서 XML 분석을 위해 내 놓은 API이다. JAXP는 SAX와 DOM API를 대신하거나 완성시킨 것은 아니지만 JAXP는 자바 개발자들이 XML API를 좀더 쉽게 사용할 수 있도록 하기 위해 만든 편리한 메소드를 제공하고 있다. JAXP는 이름공간을 지원할 뿐만 아니라 SAX와 DOM 권고안을 따르고 있다. 또한, JAXP는 교체가능(pluggability) 계층을 통해서 XML을 따르는 모든 파서를 사용할 수 있도록 해 준다.

현재 EJB 1.1 규약과 Tomcat은 XML 형식의 설정 및 배치(deployment) 파일을 사용하고 있으며, 앞으로 나올 J2EE 1.3이나 J2SE 1.4에 JAXP가 추가될 것으로 예상된다.

JDOM

현재 나와 있는 XML API 중에서 자바 개발자들에게 가장 흥미를 끌고 있는 API가 있다면, 바로 JDOM이다. JDOM은 일반적으로 SAX와 DOM을 대체할 수 있는 자바 중심적이고 고성능의 API를 제공하고 있으며, DOM이나 SAX에 기반하지 않은 대신 개발자가 DOM의 특징 없이 트리 형태로 XML 문서를 처리할 수 있도록 해 준다. 또한 SAX와 같은 고성능을 제공하기 때문에 분석과 출력을 매우 빠르게 할 수 있도록 해 준다. 또한, DOM과 달리 속성이나 요소 집합을 나타내기 위해서 자바 2의 콜렉션 클래스를 사용한다. (참고로, DOM은 속성이나 요소 집합을 나타내기 위해서 Attributes 또는 Nodelist와 같은 별도의 클래스를 사용한다).

JDOM은 자바에 맞춰서 개발된 API이기 때문에, SAX나 DOM과 달리 자바에 최적화되어 있다. 그 하나의 예로 자바 2의 콜렉션 API를 사용하는 것을 들 수 있다. 또한, JDOM은 이미 증명된 자바 디자인 패턴에 따라 설계되었으며, 직접적으로 클래스의 인스턴스를 생성함으로써 JDOM의 구성 요소(요소, 주석, 속성, 기타 등등)를 생성할 수 있도록 하고 있다.

XML을 어디에 사용하는가

아직까지 XML을 미션 크리티컬한 어플리케이션에서 사용하지는 않고 있다. 하지만, 자바와 비교해보면 XML의 발전 속도는 매우 빠른 편이며, 점차적으로 XML을 사용하는 분야가 증가하고 있다. 실예로, 앞으로 나올 ASP+나 JSP 차기 버전의 경우 페이지 자체를 XML로 작성할 수 있도록 하고 있다. 또한, XML에 있어서 중요한 점은 자바와 찰떡궁합을 이룬다는 점이다. 이에 대해서는 이 Article의 마지막 부분에서 살펴볼 것이다.

XML을 현재 어느 분야에서 사용하고 있는 지에 대해서 살펴보도록 하자.

표현에서의 XML

XML의 가장 큰 장점은 내용과 표현을 분리한다는 점이다. 이는 오늘날과 같이 클라이언트의 종류가 다양한 환경에서 큰 힘을 발휘하게 된다. 예를 들어, 클라이언트의 종류가 웹 브라우저, 휴대전화와 같은 무선 기기, 자바 애플리케이션이라고 해보자. 기존의 방법을 사용하려면 각각의 클라이언트에 대해서 각각 HTML, WML 그리고 자바 애플리케이션에 알맞은 어떤 형태로 제공해야 한다. 내용이 변경되거나 표현부분이 변경되는 경우 모두 각각의 문서를 변경해주어야 한다. 내용을 변경하는 경우에도 이 각각의 문서를 변경해야 한다는 것은 매우 귀찮은 일일 수 있으며, 지원해야 하는 클라이언트의 종류가 세 가지 이상으로 늘어날 경우 이는 관리에 있어서 어려움을 제공하는 원인이 되기도 한다. 또한, 하나의 문서를 변경하기 위해서는 개발자와 페이지 디자이너가 모두 필요하다는 것도 문제가 된다.

XML을 사용하면 이러한 문제의 상당히 많은 부분을 해결할 수 있게 된다. 앞에서 XSL/XSLT에 대해서 설명할 때, XSL/XSLT는 한 형식의 문서를 다른 형식으로 변환해 준다고 하였다. 개발자는 단순히 내용을 저장하고 있는 XML 문서만을 생성하면 되며, 페이지 디자이너는 XML 문서를 HTML, WML 그리고 자바 애플리케이션에 알맞은 형태로 변환해주는 XSL/XSLT를 작성하기만 하면 된다. 만약 사용자가 웹 브라우저를 통해서 접속했다면 XML+HTML로 변환해주는 XSLT를 통해서 HTML 문서를 제공해주며, 휴대전화로 접속했다면 XML+WML로 변환해주는 XSLT를 통해서 WML 문서를 제공해줄 것이다. 즉, 하나의 XML 문서를 통해서 여러개의 표현을 만들어낼 수 있는 것이다. 현재 이러한 기능을 제공해주는 출판 프레임워크가 개발되고 있으며, 대표적인 아파치 코쿤을 예로 들 수 있다.

통신에서의 XML

어플리케이션 사이에서 정보를 주고 받기 위해서 XML을 사용할 수 있다. 각각의 어플리케이션은 자신만의 문서 형식을 작성할 필요가 없으며, 단지 두 어플리케이션이 알고 있는 DTD나 스키마에 맞춰서 XML 문서를 작성하기만 하면 된다. 뿐만 아니라 XML로 정보를 표현하기 때문에 특별한 어플리케이션에 종속되지 않으며 따라서 DTD를 따르는 모든 애플리케이션에서 같은 정보를 사용할 수 있게 된다. 또한, XML로 표현된 정보를 XSL/XSLT를 사용하여 손쉽게 어플리케이션에 특정한 형식으로 변환할 수도 있다.

이러한 XML의 응용 범위는 오늘날 인터넷 비니지스에 있어서 핵심으로 떠오르고 있는 B2B로 확장될 것으로 예상된다. 즉, 어플리케이션 사이에서 뿐만 아니라 기업간에 XML을 통해서 정보를 주고 받을 것이다. 이미 많은 곳에서 XML을 이용하여 기업간에 정보를 주고 받을 수 있는 어플리케이션을 개발하고 있으며 몇몇 제품은 이미 판매되고 있다. 오늘날 기업간에 정보를 주고 받을 때 주로 사용되는 EDI에 비해 XML은 더욱 더 다양한 형태로 정보를 주고 받을 수 있도록 해 준다.

설정에서의 XML

앞에서도 말했듯이 XML은 설정에 있어서 유용하게 활용할 수 있다. 이미 EJB 1.1 규약과 앞으로 정식으로 발펴될 EJB 2.0 규약에서 XML을 사용하여 설정 및 배치 기술자를 정의하고 있으며 서블릿 2.2 역시 XML을 사용하여 설정과 배치 부분을 기술하고 있다. 앞으로 이러한 설정 및 배치와 관련된 곳에서 XML의 사용범위는 점차적으로 증가할 것으로 예상된다.

자바 & XML

마지막으로 자바와 XML과의 관계에 대해서 간략하게 알아보자. 이 두 기술의 관계를 다음의 문구로 간단하게 표현할 수 있다.

Java + XML = Portable Code + Protable Data

자바의 이식성은 별다른 설명이 필요 없을 정도로 자명하다. 자바는 중간 코드인 바이트코드와 JVM을 통해서 거의 완벽한 이식성을 제공하고 있으며, 쓰레드와 Native 메소드와 같은 몇가지 문제점을 제외하고는 거의 모든 플랫폼에서 특별한 문제없이 같은 자바 코드를 사용할 수 있게 되었다.

XML의 이식성은 자바의 이식성보다 더욱 더 완벽에 가깝다. 자바를 실행하기 위해서 단지 플랫폼에 알맞은 JVM이 있으면 되듯이, XML을 사용하기 위해서는 표준 XML을 지원하는 파서, 처리기(Processor) 등이 있으면 된다. XML 데이터 자체는 플랫폼에 어떠한 플랫폼에도 영향을 받지 않는다.

자바는 XML을 사용할 수 있는 풍부한 API를 제공하고 있으며, 따라서 자바와 XML의 조화는 어플리케이션과 데이터에 있어서 완벽한 이식성을 제공해주며, 이는 앞으로 개발될 어플리케이션(특히 엔터프라이즈 어플리케이션)에서 큰 힘이 될 것이다.

XML 파서(Parser)와 처리기(Processor)

XML을 실제 어플리케이션 환경에서 사용하기 위해서는 XML을 분석할 수 있는 파서(parser)와 XSL/XSLT를 사용하여 XML을 변환할 수 있는 XML 처리기가 필요하다. 여기서는 XML을 분석하고 처리할 수 있도록 해 주는 파서와 처리기의 종류를 나열할 것이다.

파서

현재 사용할 수 있는 파서에는 다음과 같은 것들이 있다.

처리기(Processor)

현재 사용할 수 있는 처리기에는 다음과 같은 것들이 있다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요