상세 컨텐츠

본문 제목

[ajax] 응용프로그렘에서 데이타교환

IT 세상/자바세상

by 이현민 (지후지율아빠) 2007. 12. 13. 18:35

본문

저자 - Andrei Cioroianu
XML과 JavaScript Object Notation을 사용하여 Ajax 클라이언트와 Java 서버 사이에서 데이타를 전송하는 방법을 배우십시오
게시일: 2006년 6월
XMLHttpRequest로 알려져 있는 Ajax 코어 API는 오로지 웹 브라우저와 서버 사이에서 데이타를 교환하려는 목적으로 HTTP 요청을 보내는 것에 관한 것입니다. 웹 페이지에서 실행되는 JavaScript 코드는 XMLHttpRequest를 사용하여 요청 매개변수를 Servlet이나 JSP 페이지와 같은 서버 측 스크립트로 보낼 수 있습니다. 호출된 Servlet이나 JSP는 사용자가 전체 페이지를 새로 고치지 않고 볼 수 있는 콘텐츠를 갱신(업데이트)하는 데 일반적으로 사용되는 데이타가 포함된 응답을 되돌려 보냅니다. 이 방법을 사용하면 네트워크 트래픽이 감소되고 웹 UI가 거의 데스크톱 GUI처럼 동작하기 때문에 성능 및 사용 가능성 모두에서 이점이 있습니다.
하지만 클라이언트 측에서는 JavaScript를 사용하고 서버 측에서는 Java(또는 그와 동등한 것)를 사용하여 데이타 교환, 검증 및 처리를 구현해야 하기 때문에 이러한 사용자 인터페이스를 개발하는 것은 쉬운 작업이 아닙니다. 하지만 Ajax 기반의 인터페이스가 귀하의 사용자에게 가져다 주는 이익을 고려하면, Ajax 기반의 인터페이스를 만들기 위해 필요한 추가 노력이 가치가 있는 경우가 많이 있습니다.
이 기사에서 저는 전통적인 웹 응용프로그램 모델과 Ajax 모델을 비교하면서 Ajax 클라이언트와 서버 사이에서 데이타를 교환할 때 사용하는 주요 메소드를 제시할 것입니다. 또한 클라이언트와 서버 양쪽에서 데이타를 처리하기 위한 기법을 설명할 것입니다.
먼저 JavaScript를 사용하여 클라이언트 측에서 요청 오브젝트의 매개변수를 인코딩하는 방법을 배울 것입니다. 웹 브라우저에서 사용하는 디폴트 인코딩인 이른바, URL 인코딩을 사용하거나 요청 매개변수를 XML 문서에 포함시킬 수 있습니다. 서버는 요청을 처리하고 인코딩해야 할 데이타가 포함된 응답을 반환할 것입니다. 이 기사에서는 응답의 데이타 포맷에 대한 주 옵션인 JavaScript Object Notation(JSON) 및 XML을 설명할 것입니다.
Ajax 응용프로그램에서 일반적으로 사용하는 XML 관련 API에 기사 내용의 상당 부분을 할애하였습니다. 클라이언트 측에서 제공되는 XML API는 매우 한정되어 있지만 그것으로 충분합니다. 대부분의 경우 XMLHttpRequest로 원하는 모든 것을 처리할 수 있지만 JavaScript를 사용하여 웹 브라우저에서 XML 문서의 구문을 분석하고 DOM 트리를 직렬화(시리얼라이즈)할 수도 있습니다. 서버 측에는 XML 문서를 처리할 수 있는 API와 프레임워크가 많이 있습니다. 이 기사에서는 XML Schema, XPath, DOM, 그리고 다른 많은 유용한 표준에 대한 지원을 제공하는 XML용 표준 Java API를 사용하여 기본 태스크를 구현하는 방법을 보여줍니다.
이 기사의 목적은 최고의 기법과 최신 API를 사용하여 Ajax 응용프로그램에서 데이타 교환을 구현할 때 필요한 모든 것을 제공하는 것입니다. 포함된 샘플 코드 는 util, model, 그리고 feed의 세 가지 패키지로 구성되어 있습니다. util 패키지에는 XML 구문 분석, Schema 기반의 검증, XPath 기반의 쿼리, DOM 직렬화 및 JSON 인코딩에 대한 메소드를 제공하는 클래스가 포함되어 있습니다. model 패키지에는 XML 문서로부터 초기화한 다음에 JSON 형식으로 전환할 수 있는 예제 데이타 모델이 포함되어 있습니다. 또한 model 디렉터리에서 (XML 검증에 사용되는) Schema 예제를 찾을 수 있을 것입니다. feed 패키지에는 웹 페이지를 새로 고치기 위해 Ajax를 사용하여 5초마다 정보를 검색하는 데이타 공급을 시뮬레이션하는 클래스가 포함되어 있습니다. 이 기사에서는 완료되지 않은 Ajax 요청을 중단하고 사용을 마친 XMLHttpRequest 객체(오브젝트)를 제거하여 웹 브라우저에서 메모리 누수를 피할 수 있는 방법을 설명합니다.
이 웹 디렉터리에는 JSP 및 JavaScript 샘플이 포함되어 있습니다. ajaxUtil.js에는 Ajax 요청을 보내고, 요청을 중단하고, HTTP 오류를 처리하는 데 사용하는 유틸리티 함수가 포함되어 있습니다. 또한 ajaxUtil.js에는 XML 및 URL 인코딩, XML 구문 분석, 그리고 DOM 직렬화에 사용할 수 있는 JavaScript 유틸리티도 포함되어 있습니다. ajaxCtrl.jsp 파일은 Ajax 컨트롤러 역할을 수행하여, 모든 Ajax 요청을 받고, 이를 처리하기 위해 매개변수를 데이타 모델이나 공급으로 전달하고, Ajax 응답을 반환합니다. 나머지 웹 파일은 유틸리티 메소드를 사용하는 방법을 보여주는 샘플입니다.

클라이언트 측에서 요청 만들기
데이타를 웹 서버로 전송하는 가장 쉬운 방법은 요청 매개변수를 쿼리 문자열에 인코딩하는 것으로, 쿼리 문자열은 사용하는 HTTP 메소드에 따라 URL에 추가하거나 요청의 본문에 포함시킬 수 있습니다. 복잡한 데이타 구조를 보내야 하는 경우라면 정보를 XML 문서 안에 인코딩하는 것이 더 좋은 방법입니다. 이 섹션에서 두 가지 메소드를 모두 설명할 것입니다.
요청 매개변수 인코딩. 전통적인 웹 응용프로그램을 개발하는 경우라면 사용자가 데이타를 전송할 때 웹 브라우저에서 자동으로 폼 데이타를 인코딩하기 때문에 사용자는 폼 데이타 인코딩과 관련하여 걱정할 필요가 없습니다. 하지만 Ajax 응용프로그램의 경우에는 사용자가 요청 매개변수를 인코딩해야 합니다. JavaScript에는 URL의 구성 부분이 될 수 없는 모든 문자를 %HH(HH는 16진수 코드)로 대체하는 escape()라는 매우 유용한 함수가 있습니다. 예를 들어 모든 공백 문자는 %20으로 바뀝니다.
샘플 코드 다운로드에는 buildQueryString()이라는 유틸리티 함수가 포함되어 있는데, 이는
각각의 매개변수의 이름과 값을 =로 구분하고 이름-값 쌍의 사이에는 & 문자를 두는 방식으로, 어레이(배열)에서 추출한 매개변수를 결합합니다.
function buildQueryString(params) {
    var query = "";
    for (var i = 0; i < params.length; i++) {
        query += (i > 0 ? "&" : "")
            + escape(params[i].name) + "="
            + escape(params[i].value);
    }
    return query;
}

다음의 매개변수를 인코딩한다고 가정하겠습니다.
var someParams = [
    { name: "name",  value: "John Smith" },
    { name: "email", value: "john@company.com" },
    { name: "phone", value: "(123) 456 7890" }
];

buildQueryString(someParams)을 호출하면 아래와 같은 내용이 포함된 결과가 나올 것입니다.
name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890
GET 메소드를 사용하고 싶다면 쿼리 스트링을 URL의 ? 문자 이후에 추가해야 합니다. POST를 사용하는 경우에는 setRequestHeader()로 Content-Type 헤더를 application/x-www-form-urlencoded로 설정하고 쿼리 스트링을 XMLHttpRequest의 send() 메소드로 전달해야 합니다. 이 send() 메소드가 HTTP 요청을 서버로 전송할 것입니다.
XML 문서 만들기. JavaScript로 XML 문서를 만드는 가장 쉬운 방법은 문자열(스트링)을 사용하여 속성과 데이타를 가진 요소(엘리먼트)를 만드는 것입니다. 이 방법을 채택한다면 &, <, >, " 및 ' 문자를 이스케이핑 하기 위한 다음과 같은 유틸리티 메소드를 사용해야 합니다.
function escapeXML(content) {
    if (content == undefined)
        return "";
    if (!content.length || !content.charAt)
        content = new String(content);
    var result = "";
    var length = content.length;
    for (var i = 0; i < length; i++) {
        var ch = content.charAt(i);
        switch (ch) {
            case '&':
                result += "&";
                break;
            case '<':
                result += "<";
                break;
            case '>':
                result += ">";
                break;
            case '"':
                result += """;
                break;
            case ''':
                result += "&apos;";
                break;
            default:
                result += ch;
        }
    }
    return result;
}

더 쉽게 작업하려면 다음과 같은 다른 유틸리티 메소드도 필요할 것입니다.
function attribute(name, value) {
    return " " + name + "="" + escapeXML(value) + """;
}

다음 예제는 symbol, shares, 그리고 paidPrice라는 세 개의 속성을 가진 객체 배열로부터 XML 문서를 만듭니다.
function buildPortfolioDoc(stocks) {
    var xml = "<portfolio>";
    for (var i = 0; i < stocks.length; i++) {
        var stock = stocks[i];
        xml += "<stock ";
        xml += attribute("symbol", stock.symbol);
        xml += attribute("shares", stock.shares);
        xml += attribute("paidPrice", stock.paidPrice);
        xml += "/>";
    }
    xml += "</portfolio>";
    return xml;
}

DOM 사용을 선호한다면 웹 브라우저의 API를 사용하여 XML의 구문을 분석하고 DOM 트리를 직렬화할 수 있습니다. IE를 사용하면 새 ActiveXObject("Microsorft.XMLDOM")가 포함된 빈 문서를 만들 수 있습니다. 그 다음 loadXML() 또는 load() 메소드를 사용하여 각각의 문자열 또는 URL에서 XML의 구문을 분석할 수 있습니다. IE를 사용하는 경우에는 노드 및 해당 노드의 모든 하위 노드를 XML로 표현할 수 있는 xml이라는 이름의 속성이 각 노드에 있습니다. 따라서 XML 문자열의 구문을 분석하고 DOM 트리를 수정한 다음, 다시 DOM을 XML로 직렬화할 수 있습니다.
Firefox와 Netscape 브라우저를 사용하면 document.implementation.createDocument(…)가 포함된 빈 문서를 만들 수 있습니다. 그 다음 createElement(), createTextNode(), createCDATASection() 등을 사용하여 DOM 노드를 만들 수 있습니다. Mozilla 브라우저 또한 DOMParser 및 XMLSerializer라는 이름의 두 API를 제공합니다. DOMParser API에는 parseFromStream() 및 parseFromString() 메소드가 포함되어 있습니다. XMLSerializer 클래스에는 DOM 트리를 직렬화하는 데 사용하는 serializeToStream()과 serializeToString() 메소드가 있습니다.
다음 함수는 XML 문자열의 구문을 분석하여 DOM Document를 반환합니다.
function parse(xml) {
    var dom;
    try {
        dom = new ActiveXObject("Microsoft.XMLDOM");
        dom.async = false;
        dom.loadXML(xml);
    } catch (error) {
        try {
            var parser = new DOMParser();
            dom = parser.parseFromString(xml, "text/xml");
            delete parser;
        } catch (error2) {
            if (debug)
                alert("XML parsing is not supported.");
        }
    }
    return dom;
}

다음 함수는 DOM Node 및 해당 노드의 모든 하위 노드를 직렬화한 후에 XML을 문자열로 반환합니다.
function serialize(dom) {
    var xml = dom.xml;
    if (xml == undefined) {
        try {
            var serializer = new XMLSerializer();
            xml = serializer.serializeToString(dom);
            delete serializer;
        } catch (error) {
            if (debug)
                alert("DOM serialization is not supported.");
        }
    }
    return xml;
}

또한 XMLHttpRequest를 구문 분석기(파서) 또는 시리얼라이저로 사용할 수도 있습니다. Ajax 요청에 대한 응답은 서버에서 받을 때 자동으로 구문 분석됩니다. XMLHttpRequest의 responseText 및 responseXML 속성을 사용하여 텍스트 형태 및 DOM 트리 모두에 액세스할 수 있습니다. 또한 DOM 트리는 send() 메소드로 전송될 때 자동으로 직렬화됩니다.
요청 보내기. 이전 기사(영문)에서는 XMLHttpRequest API와 sendHttpRequest()라는 이름의 유틸리티 함수를 소개했습니다(이 함수는 다운로드 가능한 샘플의 ajaxUtil.js에 포함되어 있습니다). 이 함수는 네 개의 매개변수(HTTP 메소드, URL, 매개변수 배열, callback)를 받고, XMLHttpRequest 객체를 만들고, 객체의 속성을 설정하고, send() 메소드를 호출합니다. callback 매개변수가 제공된 경우라면 요청은 비동기식으로 보내지고, 응답을 받을 때 callback 함수가 호출됩니다. 그렇지 않은 경우, 요청은 동기식으로 보내지며, sendHttpRequest()가 반환을 하자마자 응답을 처리할 수 있습니다.
알다시피, XMLHttpRequest를 사용할 때는 몇 가지 중대한 선택을 해야 합니다.
사용할 HTTP 메소드(GET 또는 POST)
요청 매개변수를 인코딩하는 데 사용할 형식(포맷)(이 기사의 앞 부분에서 XML 및 URL 인코딩을 설명하였습니다)
동기식으로 호출할 것인지(응답을 기다림) 비동기식으로 호출할 것인지(callback 사용)의 여부
XML, XHTML, HTML 또는 JavaScript Object Notation(JSON) 같은 응답 형식(이 기사의 뒤에서 설명합니다)
사용자가 데이타 공급에서 일부 주가를 알고 싶어 한다고 가정하겠습니다. 이 정보는 사용자의 간섭 없이 주기적으로 새로 고쳐질 것입니다. 이러한 경우에는 정보를 검색하는 중에 사용자 인터페이스를 막아서는 안 되기 때문에 비동기식으로 HTTP 요청을 전달해야 합니다. 요청 매개변수는 URL에서 인코딩할 수 있는 기호의 배열입니다. 요청을 자주 하는 경우에는 서버에 과부하가 걸릴 수 있기 때문에 XML 문서를 보내고 싶지 않을 것입니다. 사용자는 최신의 주가에만 관심이 있기 때문에 아직 완료되지 않은 이전의 요청은 다음과 같이 중단되어야 합니다.
var ctrlURL = "ajaxCtrl.jsp";
var feedRequest = null;

function sendInfoRequest(symbols, callback) {
    if (feedRequest)
        abortRequest(feedRequest);
    var params = new Array();
    for (var i = 0; i < symbols.length; i++)
        params[i] = {
            name: "symbol",
            value: symbols[i]
        };
    feedRequest = sendHttpRequest(
        "GET", ctrlURL, params, callback);
}

request 객체의 abort() 메소드를 호출하기 전에 abortRequest() 함수(ajaxUtil.js 파일에 포함되어 있습니다)는 onreadystatechange 속성을 아무것도 하지 않는 callback으로 설정합니다. 또한 메모리 누수를 막기 위해 다음과 같이 request 객체를 삭제하는 것이 매우 중요합니다.
function abortRequest(request) {
    function doNothing() {
    }
    request.onreadystatechange = doNothing;
    request.abort();
    delete feedRequest;
}

Let’s consider another case. When transmitting the whole user data to be saved in a database, you should send the request synchronously because you probably don’t want to let the user modify the data while it is being saved. In this case, the XML format is preferred because it is usually easier to encode an object model in a document instead of using lots of string parameters. In addition, the requests for saving the data aren’t very frequent and the server can handle the load without any problems. The XML document could be encoded as a parameter so that you can access it in a JSP page, using the EL syntax ( ${param.xml}). Here is the function that sends the model’s data encoded in an XML document:
function sendSaveRequest(xml) {
    var params = [ { name: "xml", value: xml } ];
    var saveRequest = sendHttpRequest("POST", ctrlURL, params);
    if (saveRequest)
        delete saveRequest;
}

다른 경우를 생각해 보겠습니다. 데이타베이스에 저장될 전체 사용자 데이타를 전송하는 경우에, 저장중인 데이타를 사용자가 수정하도록 허용하고 싶지 않을 것이므로 요청을 동기식으로 보내야 합니다. 이 경우에는 XML 형식을 선호하는데, XML을 사용하면 많은 문자열 매개변수를 사용하는 것과 비교해 문서에서 객체 모델을 인코딩하는 작업이 일반적으로 더 쉬워지기 때문입니다. 또한 데이타 저장 요청은 자주 발생하지 않기 때문에 서버는 아무 문제 없이 부하를 처리할 수 있습니다. XML 문서를 매개변수로 인코딩할 수 있기 때문에 EL 구문을 사용하여 JSP 페이지에서 액세스할 수 있습니다(${param.xml}). 아래 함수는 XML 문서로 인코딩된 모델의 데이타를 전송합니다.
function sendLoadRequest() {
    var model = null;
    var loadRequest = sendHttpRequest("GET", ctrlURL);
    if (loadRequest) {
        model = eval(loadRequest.responseText);
        delete loadRequest;
    }
    return model;
}

다음 두 섹션에서는 사용자가 XML 문서를 사용하여 서버에서 일반적으로 수행하는 작업과 Ajax 요청에 응답할 수 있는 방법에 대해 설명하겠습니다.
서버 측에서 요청 처리하기
Servlet/JSP 컨테이너는 각각의 HTTP 요청 구문을 분석하여 ServletRequest 인스턴스를 만들기 때문에, 사용자는 getParameter()/getParameterValues() 함수를 사용하여 요청의 매개변수를 얻거나 getInputStream() 함수를 사용하여 요청의 본문을 구할 수 있습니다. JSP 페이지에서도 EL 구문(${param...} 및 ${paramValues...})을 사용하여 매개변수를 구할 수 있습니다. Ajax 클라이언트에서 application/x-www-form-urlencoded 형식을 사용하고 buildQueryString() 같은 유틸리티 함수를 사용하여 데이타를 인코딩할 경우에만 getParameter() 또는 ${param...}를 사용하여 요청의 매개변수를 구할 수 있다는 것을 명심해야 합니다. 이 같은 내용은 앞 절에서 다루었습니다. 클라이언트 측에서 XML 문서 또는 DOM 트리를 XMLHttpRequest의 send() 메소드로 전달한다면 서버 측에서는 ServletRequest의 getInputStream() 메소드를 사용해야 할 것입니다.
데이타 검증. 일반적인 웹 응용프로그램에서는 많은 데이타 검증을 합니다. 대부분의 오류는 요청 매개변수가 없거나, 숫자 형식에 문제가 있는 것처럼 매우 단순합니다. 사용자는 폼 요소의 값을 입력하는 것을 잊어버리거나 유효하지 않은 값을 입력하는 식으로 항상 이러한 오류를 일으킵니다. JSP 및 Oracle ADF Faces와 같은 웹 프레임워크는 사용자 오류를 매우 훌륭하게 처리합니다. Ajax 응용프로그램의 경우에는 JavaScript를 사용하여 클라이언트 측에서 오류를 잡고 처리해야 합니다. 예를 들어 isNaN(new Number(value)) 함수를 사용하여 숫자 값이 유효하지 않은지 여부를 검증할 수 있습니다.
보안 및 신뢰성의 이유로 데이타는 서버 측에서 다시 검증해야 하며, XML 요청의 형식이 올바르게 되어 있다고 가정해서는 안됩니다. XML Schema(스키마)는 서버 측에서 복잡한 요청을 검증하는 데 사용할 수 있는 훌륭한 도구입니다. 샘플 코드 다운로드에는 스키마 문서를 로드하고 사용하기 위한 메소드를 제공하는 XMLUtil이라는 이름의 클래스가 포함되어 있습니다. 다음 코드는 SchemaFactory를 초기화하는 방법을 보여줍니다.
import javax.xml.*;
import javax.xml.validation.*;
...
protected static SchemaFactory schemaFactory;
static {
    schemaFactory = SchemaFactory.newInstance(
        XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schemaFactory.setErrorHandler(newErrorHandler());
}

newErrorHandler() 메소드는 다음과 같이 SAX 오류 처리기를 반환합니다.

import org.xml.sax.*;
...
public static ErrorHandler newErrorHandler() {
    return new ErrorHandler() {
        public void warning(SAXParseException e)
                throws SAXException {
            Logger.global.warning(e.getMessage());
        }

        public void error(SAXParseException e)
                throws SAXException {
            throw e;
        }

        public void fatalError(SAXParseException e)
                throws SAXException {
            throw e;
        }
    };
}

다음과 같이 getResourceAsStream()을 사용하여 CLASSPATH에 지정된 디렉터리 또는 JAR에서 XSD 파일을 찾아 로드할 수 있습니다.
public static InputStream getResourceAsStream(String name)
        throws IOException {
    InputStream in = XMLUtil.class.getResourceAsStream(name);
    if (in == null)
        throw new FileNotFoundException(name);
    return in;
}

그리고 다음과 같이 SchemaFactory 인스턴스를 사용하여 newSchema() 메소드로 Schema 객체를 얻습니다.
import javax.xml.validation.*;
...
public static Schema newSchema(String name)
        throws IOException, SAXException {
    Schema schema;
    InputStream in = getResourceAsStream(name);
    try {
        schema = schemaFactory.newSchema(new StreamSource(in));
    } finally {
        in.close();
    }
    return schema;
}

또한 다음 메소드를 사용하여 Oracle XMLSchema 객체를 만들 수도 있습니다.
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.schema.XSDBuilder;
...
public static XMLSchema newOracleSchema(String name)
        throws IOException, SAXException {
    XMLSchema schema;
    InputStream in = getResourceAsStream(name);
    try {
        XSDBuilder builder = new XSDBuilder();
        schema = builder.build(new InputSource(in));
    } catch (Exception e) {
        throw new SAXException(e);
    } finally {
        in.close();
    }
    return schema;
}

다음으로 만들어야 할 것은 DocumentBuilderFactory입니다. JAXP 1.2로 정의한 setSchema() 메소드는 CLASSPATH에서 JAXP 1.1로 구현된 것이 발견되면 Java SE 5.0의 JAXP 1.2로 구현된 것으로 대체하면서 UnsupportedOperationException을 발생시킬 수도 있습니다. 이 경우에도 다음과 같이 newOracleSchema()를 사용하여 스키마 객체를 만들고 setAttribute() 메소드를 사용하여 객체의 속성을 설정할 수 있습니다.
import javax.xml.parsers.*;
import oracle.xml.jaxp.JXDocumentBuilderFactory;
...
public static DocumentBuilderFactory newParserFactory(
        String schemaName) throws IOException, SAXException {
    DocumentBuilderFactory parserFactory
        = DocumentBuilderFactory.newInstance();
    try {
        parserFactory.setSchema(newSchema(schemaName));
    } catch (UnsupportedOperationException e) {
        if (parserFactory instanceof JXDocumentBuilderFactory) {
            parserFactory.setAttribute(
                JXDocumentBuilderFactory.SCHEMA_OBJECT,
                newOracleSchema(schemaName));
        }
    }
    return parserFactory;
}

그 다음, 다음과 같이 XML 문서를 검증하고 구문을 분석하는 데 사용할 수 있는 DocumentBuilder 객체를 만들 수 있습니다.
import javax.xml.parsers.*;
...
public static DocumentBuilder newParser(
        DocumentBuilderFactory parserFactory)
        throws ParserConfigurationException {
    DocumentBuilder parser = parserFactory.newDocumentBuilder();
    parser.setErrorHandler(newErrorHandler());
    return parser;
};

사용자가 portfolio.xsd 스키마 예제를 대상으로 XML 문서를 검증하고 싶어한다고 가정하겠습니다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <xsd:element name="portfolio" type="portfolioType"/>

    <xsd:complexType name="portfolioType">
        <xsd:sequence>
            <xsd:element name="stock"
                    minOccurs="0" maxOccurs="unbounded">
                <xsd:complexType>
                    <xsd:attribute name="symbol"
                        type="xsd:string" use="required"/>
                    <xsd:attribute name="shares"
                        type="xsd:positiveInteger" use="required"/>
                    <xsd:attribute name="paidPrice"
                        type="xsd:decimal" use="required"/>
                </xsd:complexType>
            </xsd:element>
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>

데이타 모델(DataModel) 클래스의 parsePortfolioDoc() 메소드는 다음과 같이 XMLUtil을 사용하여 xml 매개변수를 검증하고 구문 분서한 후에 DOM Document를 반환합니다.
private static final String SCHEMA_NAME
    = "/ajaxapp/model/portfolio.xsd";
private static DocumentBuilderFactory parserFactory;
   
private static Document parsePortfolioDoc(String xml)
        throws IOException, SAXException,
        ParserConfigurationException {
    synchronized (DataModel.class) {
        if (parserFactory == null)
            parserFactory = XMLUtil.newParserFactory(SCHEMA_NAME);
    }
    DocumentBuilder parser = XMLUtil.newParser(parserFactory);
    InputSource in = new InputSource(new StringReader(xml));
    return parser.parse(in);
}

이제 DOM 트리를 얻었기 때문에 다음에 할 일은 DOM 노드에서 필요한 데이타를 구하는 것입니다.
필요한 정보 추출하기. DOM API를 사용하여 DOM 트리를 탐색하거나 XQuery 또는 XPath 같은 쿼리 언어를 사용할 수 있습니다. Java는 XPath에 사용할 수 있는 표준 API를 제공하는데, 다음에 이것을 사용할 것입니다. XMLUtil 클래스는 다음과 같이 newXPath() 메소드가 있는 XPathFactory를 만듭니다.
import javax.xml.xpath.*;
...
protected static XPathFactory xpathFactory;
static {
    xpathFactory = XPathFactory.newInstance();
}
   
public static XPath newXPath() {
    return xpathFactory.newXPath();
}

다음 메소드는 주어진 맥락에서 XPath 표현식을 평가한 후에 결과값을 반환합니다.
import javax.xml.xpath.*;
import org.w3c.dom.*;
...
public static String evalToString(String expression,
        Object context) throws XPathExpressionException {
    return (String) newXPath().evaluate(expression, context,
        XPathConstants.STRING);
}
   
public static boolean evalToBoolean(String expression,
        Object context) throws XPathExpressionException {
    return ((Boolean) newXPath().evaluate(expression, context,
        XPathConstants.BOOLEAN)).booleanValue();
}
   
public static double evalToNumber(String expression,
        Object context) throws XPathExpressionException {
    return ((Double) newXPath().evaluate(expression, context,
        XPathConstants.NUMBER)).doubleValue();
}
   
public static Node evalToNode(String expression,
        Object context) throws XPathExpressionException {
    return (Node) newXPath().evaluate(expression, context,
        XPathConstants.NODE);
}
   
public static NodeList evalToNodeList(String expression,
        Object context) throws XPathExpressionException {
    return (NodeList) newXPath().evaluate(expression, context,
        XPathConstants.NODESET);
}

DataModel의 setData() 메소드는 다음과 같이 XPath 평가 메소드를 사용하여 포트폴리오 XML 문서에서 정보를 추출합니다.
public synchronized void setData(String xml)
        throws IOException, SAXException,
        ParserConfigurationException,
        XPathExpressionException {
    try {
        ArrayList stockList
            = new ArrayList();
        Document doc = parsePortfolioDoc(xml);
        NodeList nodeList = XMLUtil.evalToNodeList(
            "/portfolio/stock", doc);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            StockBean stock = new StockBean();
            stock.setSymbol(
                XMLUtil.evalToString("@symbol", node));
            stock.setShares(
                (int) XMLUtil.evalToNumber("@shares", node));
            stock.setPaidPrice(
                XMLUtil.evalToNumber("@paidPrice", node));
            stockList.add(stock);
        }
        this.stockList = stockList;
    } catch (Exception e) {
        Logger.global.logp(Level.SEVERE, "DataModel", "setData",
            e.getMessage(), e);
    }
}

일단 서버 측의 객체 모델에 데이타를 포함시킨 후에는 사용자 응용프로그램의 요구 사항에 따라 처리할 수 있습니다. 그런 다음 사용자는 반드시 Ajax 요청에 응답해야 합니다.
서버 측에서 응답 만들기
사용자가 JSP 구문을 사용하여 마크업을 만들 수 있고 Ajax 클라이언트는 <div> 또는 <span> 요소의 innerHTML 속성을 사용하여 페이지 안의 어딘가에 HTML을 삽입하기만 하면 되기 때문에 Ajax 요청에 대한 응답으로 HTML을 반환하는 것이 가장 쉬운 해결 방법입니다. 하지만 표현 마크업 없이 데이타만을 Ajax 클라이언트로 반환하는 것이 훨씬 더 효율적입니다. XML 형식 또는 JSON을 사용할 수 있습니다.
XML 응답 만들기. Java EE는 XML 문서를 만들 때 사용할 수 있는 많은 옵션을 제공하기 때문에 JSP로 XML 문서를 만들 수도 있고, JAXB를 사용하여 오브젝트 트리로부터 XML 문서를 만들 수도 있으며, DOM 트리를 직렬화하는 다음 예제에서처럼 javax.xml.transform.Transformer를 사용하여 XML 문서를 만들 수도 있습니다.
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
...
public static TransformerFactory serializerFctory;
static {
    serializerFctory = TransformerFactory.newInstance();
}
   
public static void serialize(Node node, OutputStream out)
        throws TransformerException {
    Transformer serializer = serializerFctory.newTransformer();
    Properties serializerProps = new Properties();
    serializerProps.put(OutputKeys.METHOD, "xml");
    serializer.setOutputProperties(serializerProps);
    Source source = new DOMSource(node);
    Result result = new StreamResult(out);
    serializer.transform(source, result);
}

서버 측에서 XML을 만들 때 사용할 수 있는 표준 옵션 및 오픈 소스 프레임워크가 너무 많기 때문에 사용자의 유일한 문제는 자신에게 적합한 것을 선택하는 일입니다. 하지만 클라이언트 측에서는 DOM이 XML 구문 분석을 위한 유일한 옵션이기 때문에 상황이 전혀 다릅니다. 일부 브라우저에서도 XPath 및 XSLT를 지원합니다.
이전의 Ajax 기사에서는 JSP를 사용하여 XML을 만든 다음, JavaScript 및 DOM을 사용하여 클라이언트 측에서 이를 처리하는 방법을 배웠습니다. 또 하나의 해결 방법은 Ajax 요청에 대한 응답에 사용할 데이타 형식으로 XML 대신 JSON을 사용하는 것입니다. 앞서 말한 대로 eval() 함수를 사용하여 JSON 문자열을 JavaScript 객체 트리로 변환할 수 있습니다. 이것은 JavaScript를 사용하여 DOM 트리에서 정보를 추출하는 것보다 훨씬 쉽습니다. 사용자에게 필요한 것은 서버 측에서 JSON을 만들기 위한 좋은 유틸리티 클래스뿐입니다.
JSON 인코딩. JSONEncoder 클래스는 리터럴, 객체 및 배열을 인코딩하기 위한 메소드를 제공합니다. 결과는 java.lang.StringBuilder에 저장됩니다.
package ajaxapp.util;

public class JSONEncoder {
    private StringBuilder buf;
   
    public JSONEncoder() {
        buf = new StringBuilder();
    }

    ...
}

character() 메소드는 단일 문자를 인코딩합니다.
public void character(char ch) {
    switch (ch) {
        case ''':
        case '"':
        case '\':
            buf.append('\');
            buf.append(ch);
            break;
        case 't':
            buf.append('\');
            buf.append('t');
            break;
        case 'r':
            buf.append('\');
            buf.append('r');
            break;
        case 'n':
            buf.append('\');
            buf.append('n');
            break;
        default:
            if (ch >= 32 && ch < 128)
                buf.append(ch);
            else {
                buf.append('\');
                buf.append('u');
                for (int j = 12; j >= 0; j-=4) {
                    int k = (((int) ch) >> j) & 0x0f;
                    int c = k < 10 ? '0' + k : 'a' + k - 10;
                    buf.append((char) c);
                }
            }
    }
}

string() 메소드는 전체 문자열을 인코딩합니다.
public void string(String str) {
    int length = str.length();
    for (int i = 0; i < length; i++)
        character(str.charAt(i));
}

literal() 메소드는 JavaScript 리터럴을 인코딩합니다.
public void literal(Object value) {
    if (value instanceof String) {
        buf.append('"');
        string((String) value);
        buf.append('"');
    } else if (value instanceof Character) {
        buf.append(''');
        character(((Character) value).charValue());
        buf.append(''');
    } else
        buf.append(value.toString());
}

comma() 메소드는 쉼표 문자를 추가합니다.
private void comma() {
    buf.append(',');
}

deleteLastComma() 메소드는 버퍼의 끝에서 쉼표가 발견될 경우에 마지막 쉼표 문자를 제거합니다.
private void deleteLastComma() {
    if (buf.length() > 0)
        if (buf.charAt(buf.length()-1) == ',')
            buf.deleteCharAt(buf.length()-1);
}

startObject() 메소드는 JavaScript 객체 앞에 { 문자를 추가합니다.
public void startObject() {
    buf.append('{');
}

property() 메소드는 JavaScript 속성을 인코딩합니다.
public void property(String name, Object value) {
    buf.append(name);
    buf.append(':');
    literal(value);
    comma();
}

endObject() 메소드는 JavaScript 객체의 끝에 } 문자를 추가합니다.
public void endObject() {
    deleteLastComma();
    buf.append('}');
    comma();
}

startArray() 메소드는 JavaScript 배열의 앞에 [ 문자를 추가합니다.
public void startArray() {
    buf.append('[');
}

element() 메소드는 JavaScript 배열의 요소(엘리먼트)를 인코딩합니다.
public void element(Object value) {
    literal(value);
    comma();
}

endArray() 메소드는 JavaScript 배열의 끝에 ] 문자를 추가합니다.
public void endArray() {
    deleteLastComma();
    buf.append(']');
    comma();
}

toString() 메소드는 JSON 문자열을 반환합니다.
public String toString() {
    deleteLastComma();
    return buf.toString();
}

clear() 메소드는 버퍼를 비웁니다.
public void clear() {
    buf.setLength(0);
}

JSONEncoder 클래스는 DataModel이 자신이 보유한 데이타를 인코딩하기 위해 사용합니다.
public synchronized String getData() {
    JSONEncoder json = new JSONEncoder();
    json.startArray();
    for (int i = 0; i < stockList.size(); i++) {
        StockBean stock = stockList.get(i);
        json.startObject();
        json.property("symbol", stock.getSymbol());
        json.property("shares", stock.getShares());
        json.property("paidPrice", stock.getPaidPrice());
        json.endObject();
    }
    json.endArray();
    return json.toString();
}

ajaxCtrl.jsp 페이지는 xml 요청 매개변수가 존재하는 경우에 모델 데이타를 설정합니다. 그렇지 않은 경우에는 다음과 같이 ${dataModel.data} EL 표현식을 사용하여 getData()가 반환한 JSON 문자열을 출력합니다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
<jsp:useBean id="dataModel" scope="session"
    class="ajaxapp.model.DataModel" />

<c:choose>
    ...
    <c:when test="${!empty param.xml}">
        <c:set target="${dataModel}"
            property="data"
            value="${param.xml}" />
    </c:when>
    <c:otherwise>
        ${dataModel.data}
    </c:otherwise>
</c:choose>

Ajax 클라이언트에서 JSON 데이타를 처리해야 하기 때문에 이 작업은 완료되지 않았습니다.
클라이언트 측에서 응답 처리하기
일반적인 웹 응용프로그램에서는, 사용자가 JSP, 웹 프레임워크 및 태그 라이브러리를 사용하여 서버 측에서 콘텐츠를 만듭니다. JavaServer Faces 및 Oracle ADF Faces와 같은 웹 프레임워크를 사용하면 Ajax 응용프로그램을 만들 때 많은 도움이 될 수 있기 때문에 Ajax 응용프로그램은 이러한 프로파일에 매우 적합합니다. 하지만 Ajax 응용프로그램과 비 Ajax 응용프로그램 사이에는 큰 차이점이 있습니다. Ajax를 사용할 경우에는 데이타를 사용자에게 제공하기 위하여 클라이언트 측에서 데이타를 처리하고 콘텐츠를 만들어야 합니다.
데이타 전송에 JSON 형식을 사용하는 경우에는 JavaScript에서 제공하는 eval() 함수를 사용하여 매우 쉽게 텍스트를 객체 트리로 변환할 수 있습니다. 사용자가 XML을 선호하여 사용하는 경우에는 처리해야 할 작업이 더 많겠지만, 이 형식은 자신만의 이점이 있습니다. 예를 들어 여러 가지 유형의 클라이언트가 XML을 사용할 수 있는데 반해, JSON은 오직 JavaScript 환경에서만 쉽게 구문을 분석할 수 있습니다. 그 외에도 XML에서는 오류를 찾고 수정하는 작업을 훨씬 더 빨리 할 수 있기 때문에 디버깅(오류 제거) 시간을 줄일 수 있습니다.
JavaScript로 DOM 트리에 액세스하기. JavaScript용 DOM API는 Java의 org.w3c.dom 패키지와 매우 비슷합니다. 주요 차이점은 JavaScript에서는 속성에 직접 액세스할 수 있는데 비해 Java에서는 속성을 private으로 유지하기 때문에 get과 set 메소드를 사용하여 속성에 액세스해야 한다는 것입니다. 예를 들어 dom.documentElement를 사용하여 문서의 루트 요소를 구할 수 있습니다.
DOM은 어떻든 낮은 수준의 API이기 때문에, 사용자는 DOM을 사용하여 구문 분석된 문서의 구조에 액세스할 수 있습니다. 예를 들어 대부분의 경우 사용자는 주석을 무시하고 싶어하며, 아마도 인접 텍스트 노드를 갖게 되는 것을 기대하지 않을 것입니다. 다음과 같은 간단한 예제를 들어 보겠습니다.
var xml = "<element>da<!--comment-->ta&amp;"
    + "<![CDATA[cdata</element>";

앞에서 제시한 유틸리티 함수를 사용하여 위의 XML 문자열을 구문 분석할 수 있습니다.
var dom = parse(xml);
parse() 함수의 코드가 ajaxUtil.js 파일에 있습니다. 이 예제에서 parse() 함수는 루트 요소에 텍스트 노드, 주석, 또 하나의 텍스트 노드, 그리고 문자 데이타 노드가 포함된 DOM 트리를 반환합니다. 주석 없이 포함된 텍스트만을 원하는 경우에는 해당 요소의 하위 노드를 반복해서 검색하여 그 유형이 각각 3과 4인 텍스트 및 문자 데이타 노드의 값을 연결해야 합니다.
var element = dom.documentElement;
var childNodes = element.childNodes;
var text = "";
for (var i = 0; i < childNodes.length; i++)
    if (childNodes[i].nodeValue) {
        var type = childNodes[i].nodeType;
        if (type == 3 || type == 4)
            text += childNodes[i].nodeValue;
    }

DOM을 사용할 때는 작은 규모의 유틸리티 함수 모음을 만들어서 위에서 설명한 것과 같은 낮은 수준의 세부 사항을 처리할 필요가 없도록 해야 합니다.
JavaScript를 사용하여 동적 콘텐츠 만들기. 웹 브라우저는 document 객체를 통하여 웹 페이지의 DOM 구조에 액세스할 수 있는 기능을 사용자에게 제공합니다. 예를 들어 document.getElementById(...)를 사용하여 매우 쉽게 요소를 찾을 수 있습니다. 또한 새 요소 및 텍스트 노드를 만들어서 기존의 문서에 삽입할 수도 있습니다. 하지만 아래 예제에서처럼 문자열을 결합하여 HTML을 만드는 것이 훨씬 더 쉽습니다.
function updateInfo(request) {
    var shares = eval(request.responseText);
    var table = "<table border=1 cellpadding=5>";
    table += "<tr>";
    table += "<th>Symbol</th>";
    table += "<th>Trend</th>";
    table += "<th>Last Price</th>";
    table += "</tr>";
    for (var i = 0; i < shares.length; i++) {
        var share = shares[i];
        var symbol = escapeXML(share.symbol)
        var trend = share.trend > 0 ? "+" : "-";
        var lastPrice = new Number(share.lastPrice).toFixed(2);
        table += "<tr>";
        table += "<td>" + symbol + "</td>";
        table += "<td>" + trend + "</td>";
        table += "<td>" + lastPrice + "</td>";
        table += "</tr>";
    }
    table += "</table>";
    document.getElementById("table").innerHTML = table;
}

getElementById()가 반환한 객체의 innerHTML 속성을 설정하여 생성된 HTML을 다음과 같이 빈 <div> 요소에 삽입할 수 있습니다.
<div id="table">
</div>

이 기사의 샘플에서는 sendInfoRequest를 사용하여 ajaxLogic.js 파일에서 서버로 전송된 Ajax 요청에 대한 응답을 처리하기 위한 콜백으로서 updateInfo() 함수를 사용합니다. 정보를 5초 마다 갱신하려는 경우 다음과 같이 JavaScript의 setInterval() 함수를 사용할 수 있습니다.
var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);

DataFeed라는 이름의 클래스가 서버 측의 피드(공급)를 시뮬레이션합니다. ajaxCtrl.jsp 페이지는 피드의 getData() 메소드를 호출하여 응답을 JSON 문자열로 반환합니다. 클라이언트 측에서는 앞에서 제시한 코드 샘플에서 보았듯이 updateInfo() 함수에서 eval(request.responseText)을 사용하여 JSON 문자열의 구문을 분석합니다.
결론
이 기사에서는 URL 인코딩과 XML 형식을 사용하여 Ajax 클라이언트에서 서버로 데이타를 전송하는 방법을 배웠습니다. 그 다음에는 서버 측에서 XML 스키마를 사용하여 데이타를 검증하고, XPath로 필요한 정보를 추출하고, XML 또는 JSON 응답을 생성하는 방법을 알아 보았습니다. 그리고 마지막으로 JavaScript를 사용하여 XML 및 JSON 응답을 처리하는 방법과 브라우저에서 웹 페이지를 갱신하는 데 사용되는 동적 콘텐츠를 생성하는 방법도 알게 되었습니다.

반응형

'IT 세상 > 자바세상' 카테고리의 다른 글

JAVA 에러대처  (0) 2007.12.13
JAVA : 상대경로로 절대경로 얻기  (0) 2007.12.13
자바스크립트로 간단히 만드는 트리  (0) 2007.12.12
Java Applet vs Java Servlet  (0) 2007.12.11
자바 디컴파일러 리스트  (0) 2007.12.11

관련글 더보기