IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  웹 개발 | 자바  >

Ajax와 자바 개발을 간단하게, Part 2: 규약을 사용하여 설치와 설정을 최소화하자

JSTL과 JSP 태그 파일을 사용하여 커스터마이징이 가능한 웹 컴포넌트를 만들기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

토론

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Andrei Cioroianu, 선임 자바 개발자 겸 컨설턴트, Devsphere

옮긴이: 백기선 dwkorea@kr.ibm.com

2008 년 9 월 02 일

웹 프레임워크는 대부분 그것을 사용하는 각기 다른 애플리케이션의 요구와 개발 스타일에 따라 가능한 최대로 유연하고 확장 가능한 형태가 되려고 합니다. 하지만 불행히도, 보통 이러한 요구 때문에 복잡도가 높아지고, 처리가 과도해지며, 설정 파일이 많아집니다. 본 기사는 JSTL(JSP Standard Tag Library)과 JSP 태그 파일을 사용해 데이터 바인딩, 페이지 내비게이션, 스타일 규약을 구현하는 방법을 보여줄 것입니다. 이것으로 개발과 유지보수가 더 쉬워질 수 있습니다. 독자들은 애플리케이션을 빠르게 수정할 수 있는 동적인 속성들을 사용하여 커스텀 JSP 태그를 만드는 방법을 익힐 것입니다. 추가로, 이 글의 마지막 절에는 Ajax를 사용하여 웹 폼을 제출하는 예제를 담고 있습니다.

가장 먼저, 규약(convention)을 구현하여 설정(configuration)을 최소화하고 싶다면, 반드시 프레임워크에 의해 생성되는 HTML을 제어할 수 있어야 하며 애플리케이션에 적당한 웹 컴포넌트를 사용할 수 있어야 한다. JSF(JavaServer Faces)처럼 고도로 커스터마이징할 수 있는 웹 프레임워크도 있지만, 그 컴포넌트들이 항상 커스터마이징하기 쉬운 것은 아니다. 예를 들어, 만약 JSF에 의해 생성된 HTML을 바꾸고 싶다면, 해당 컴포넌트의 렌더러(renderer)를 다시 코딩해야 하고 새 커스텀 태그를 구현해야 한다. 그보다는 JSP 파일에서 HTML을 변경하는 편이 훨씬 간단할 것이다. 본 기사에서는 개발자들이 JSP 기반 컴포넌트를 만들어 프레임워크를 감당할 수 있도록 하는 것을 보여줄 것이다.

JSP 태그 파일을 사용하여 웹 컴포넌트 만들기

JSP 태그 파일은 웹 컴포넌트 개발을 간단하게 하는 핵심 솔루션이다. JSP 문법을 사용해 커스텀 태그 라이브러리들을 만들 수 있기 때문이다. 게다가, 태그 파일은 JSP 페이지처럼 배포되고 태그 라이브러리 서술자(TLD)를 필요로 하지 않는다. JSP 표준에 따르는 작명과 설치 규약을 사용하기 때문이다. JSP 표준은 JSP 태그 파일 내부에 태그 속성을 정의할 수 있는 지시자도 제공한다.

JSP 태그 파일을 변경하면, 애플리케이션을 다시 시작하지 않아도 애플리케이션 서버에 의해 다시 컴파일한 다음 로딩한다. 이로 인해, 개발과 테스트가 더 간편해진다. Servlet 클래스로 변환된 JSP 페이지들처럼 JSP 태그 파일들로 생성된 자바(Java™) 클래스들이 자동으로 백업되어 빠르다.

developerWorks Ajax 참고자료 센터
Ajax 참고자료 센터에서 Ajax 애플리케이션을 개발할 때 사용할 무료 도구, 코드, 정보를 확인하라. Ajax 전문가 Jack Herrington이 운영하는 활발한 Ajax 커뮤니티 포럼은 여러분이 궁금해 하는 것들을 바로 알려줄 수 있는 동료들과 연결해 줄 것이다.

이 글에서는 JSF 대신 JSP 태그 파일과 JSTL을 사용해 커스터마이징할 수 있는 웹 컴포넌트를 만드는 방법을 보여줄 것이다. 주요 목적은 동적으로 생성된 HTML 코드를 쉽게 바꾸고 HTTP(또는 Ajax) 요청을 처리하는 방법을 제어하며 개발을 단순하게 하는 규약을 구현하는 것이다.

샘플은 본 기사를 통해 살펴볼 것이며 이것을 스트럿츠나 JSF를 사용하여 웹 폼을 작성하는 대신 작은 프레임워크 안으로 그룹화할 수 있을 것이다. 자바로 웹 애플리케이션을 개발하는 것을 이제 막 시작하는 단계라면, 그 단순함과 새로운 것을 배우지 않아도 된다는 점에 감사할 것이다. 프레임워크의 태그가 HTML 태그와 똑같은 이름과 속성을 지니고 있기 때문이다.

경험이 많은 개발자라면, 이 프레임워크가 Ajax와 DHTML을 아주 잘 활용해야 하는 애플리케이션에 유용함을 알게 될 것이다. 특정 애플리케이션 모델에 국한될 필요는 없다. 프레임워크는 변경이 가능한 250줄로 구성된 JSP 코드로 웹 브라우저에서 제일 잘 보이는 HTML을 생성할 것이며, 여러분의 애플리케이션에 적합한 방법으로 HTTP 요청을 처리할 수 있는 자유를 가지게 될 것이다.

게다가, 관리가 필요한 프레임워크 설정 파일이나 추가 클래스가 없다. 각각의 페이지는 POJO를 데이터 모델로 사용할 수 있다. 데이터 처리가 자바 코드 대신 JSP 코드로 해도 될 만큼 간단한 경우에는 JavaBean 인스턴스 대신 Map 객체를 사용할 수도 있다.

폼 요소를 JavaBean 속성으로 바인딩하기

웹 프레임워크가 반드시 제공해야 하는 주요 기능 중 하나는 UI 컴포넌트를 데이터 모델 속성으로 바인딩하는 것이다. 즉 웹 페이지 요청이 들어오면 프레임워크가 JavaBean 객체에서 데이터를 가져와 HTML 폼에 포함시켜야 한다는 것을 뜻한다. 사용자가 폼을 제출하면, 프레임워크는 반드시 요청 매개변수를 가져와 수정된 값들을 다시 데이터 모델에 저장한다. 예를 들어, JSF 프레임워크는 value 속성을 사용해 입력 컴포넌트에 바이딩할 데이터를 기술하게 한다(Listing 1).


Listing 1. JSF 데이터 바인딩
                
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
...
<h:inputTextarea value="#{dataModel.address}" rows="3" cols="30"/>

데이터 모델은 반드시 Listing 2에 보이는 XML 파일처럼 JSF에 설정해야 한다.


Listing 2. JSF 데이터 모델 설정
                
<managed-bean>
    <managed-bean-name>dataModel</managed-bean-name>
    <managed-bean-class>formsdemo.AddressBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

이번 절에서 위와 같은 컴포넌트를 JSTL과 JSP 태그 파일을 사용하여 구현하는 방법을 살펴보겠다. 이미 JSF 프레임워크가 저런 컴포넌트를 제공하는데 왜 새로 만드는지 의문이 들 것이다. 본 기사에서 살펴볼 textarea.tag 파일과 JSF 프레임워크 내부의 적절한 컴포넌트를 구현한 자바 클래스 소스 코드를 비교해보면 JSP 태그 파일의 장점을 알 수 있을 것이다. 그런 다음, JSF 프레임워크나 기타 제3의 라이브러리가 제공하지 않는 다른 컴포넌트를 사용하려면 얼마나 많은 작업이 필요할지 생각해보길 바란다.

JSTL을 사용하여 웹 폼 처리하기

Listing 3은 <textarea> 엘리먼트를 가지고 있는 예제 웹 폼을 보여준다. 해당 엘리먼트의 값은 AddressBean 인스턴스에서 가져온다. <jsp:useBean> 태그는 JavaBean 객체를 생성하고 그것을 JSP request 스코프에 넣어둔다. address 속성의 값은 JSTL <c:out> 태그를 사용하여 웹 페이지에 포함시킨다.


Listing 3. JSTL을 사용하여 데이터를 바인딩하는 코드를 작성한 JSP 페이지
                
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<jsp:useBean id="dataModel" scope="request" class="formsdemo.AddressBean"/>

<form method="POST">
    <c:if test="${pageContext.request.method == 'POST' && !empty param.address}">
        <c:set target="${dataModel}" property="address" value="${param.address}"/>
    </c:if>
    <textarea name="address" rows="3" cols="30"
        ><c:out value="${dataModel.address}"/></textarea>
    <br><input type="submit" value="Submit"/>
</form>

사용자가 제출 버튼을 클릭하면, 웹 브라우저는 사용자의 입력 값을 같은 페이지로 다시 보낸다. <form> 엘리먼트에 action 속성이 없기 때문이다. 그러면, 애플리케이션 서버는 JSP 페이지를 실행하고, 이 페이지는 <c:set> JSTL 태그를 사용하여 address 매개변수 값을 dataModel 빈에 넣는다.

<df:textarea> 컴포넌트 만들기

Listing 1과 3을 비교해보면, JSF 페이지가 한 줄로 텍스트 영역을 정의한 반면, JSTL 기반 페이지는 다섯 줄을 사용하여 <textarea> 엘리먼트를 JavaBean 속성에 바인딩했음을 알 수 있을 것이다. 이 코드 조각들은 JSTL 코드를 <textarea> 엘리먼트를 출력하는 재사용 가능한 태그 파일 안으로 분리해 다시 한 줄로 줄일 수 있다.

textarea.tag 파일(Listing 4)은 데이터 바인딩 규약(폼 엘리먼트와 엘리먼트의 값을 가지고 있는 JavaBean 속성이 반드시 이름이 같다)에 따르는 name 속성만을 정의하고 있다. rowscols 같은 기타 속성들은 Map 객체에 담을 것이다. 이 객체의 dynAttr 식별자는 <%@tag%> 지시자의 dynamic-attributes 속성을 사용하여 기술한다. 이런 동적인 속성들은 <c:forEach> JSTL을 사용하여 반복 제어를 통해 출력할 것이다. JSTL 함수 fn:escapeXml()을 사용하여 ", &, < 그리고 > 문자들은 각각 &quot;, &amp;, &lt; 그리고 &gt;로 변환한다.


Listing 4: textarea.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<c:if test="${pageContext.request.method == 'POST' && !empty param[name]}">
    <c:set target="${dataModel}" property="${name}" value="${param[name]}"/>
</c:if>

<textarea name="${name}"
    <c:forEach var="attr" items="${dynAttr}">
        ${attr.key}="${fn:escapeXml(attr.value)}"
    </c:forEach>
><c:out value="${dataModel[name]}"/></textarea>

Listing 5에 보이는 웹 페이지는 태그 파일들을 담은 JSP 라이브러리를 가리키는 df 접미어를 선언하고 있다. 그럼 이제, 페이지에서 <df:textarea> 컴포넌트를 사용하여 사용자가 입력한 dataModel 객체의 address 속성의 값을 읽어 들이는 <textarea> 엘리먼트를 만들 수 있다.


Listing 5. 태그 파일을 사용하는 JSP 페이지
                
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="df" tagdir="/WEB-INF/tags/dynamic/forms" %>

<jsp:useBean id="dataModel" scope="request" class="formsdemo.AddressBean"/>

<form method="POST">
    <df:textarea name="address" rows="3" cols="30"/>
    <br><input type="submit" value="Submit"/>
</form>

스킨과 스타일 규약 사용하기

"JSF 페이지 외관 개선하기"라는 제목의 이전 기사(참고자료)에서, 표준 JSF 컴포넌트의 기본 속성을 설정하는 방법을 보여준 적이 있다. 하나의 커스텀 JSF 컴포넌트를 만들어 뷰 트리를 순회하면서 각각의 컴포넌트의 styleClass 속성을 설정했다. 이 방법은 단일 HTML 엘리먼트를 렌더링하는 간단한 JSF 컴포넌트에서 잘 동작했다.

트리나 테이블 같이 커다란 HTML 조각을 생성하는 표준 JSF 컴포넌트가 아닌 것들은 특정 HTML 엘리먼트들에 스타일을 설정할 수 없을 것이다. JSF 컴포넌트가 HTML을 생성하는 자바 코드를 사용한다는 가정하에, HTML 엘리먼트의 스타일을 변경할 수 있도록 JSF 컴포넌트의 렌더러를 다시 코딩하는 수밖에 없다. 하지만 JSP 태그 파일과 JSTL을 사용하고 있다면, HTML을 출력하는 JSP 코드에 완전히 접근할 수 있다. 즉 여러분의 애플리케이션에 적합한 방법으로 얼마든지 원하는 대로 변경할 수 있다는 뜻이다.

재사용 가능한 코드 조각을 별도의 JSP 태그 파일로 이동하기

앞 절에서 <df:textarea> 컴포넌트를 살펴보았다, 이 태그는 JSTL을 사용하여 JavaBean 속성에 연결되어 있는 <textarea> 엘리먼트를 생성한다. 웹 컴포넌트를 추가로 만들고 싶다면, 자주 쓰는 공통적인 코드 조각을 재사용할 수 있는 태그 파일로 옮기는 것이 좋겠다.

동적인 속성을 출력하는 <c:forEach> 반복문은 별도의 태그 파일 attrList.tag(Listing 6)로 옮길 수 있다. 이렇게 코드를 더 조밀하게 만드는 것은, HTML 태그에 기본 스타일 클래스를 적용하는 것과 같이 모든 컴포넌트에 유용한 기능을 구현할 수 있게 해준다.

attrList.tag 파일은 tag라는 속성을 선언한다. 이 속성을 skin 매개변수와 같이 사용하여 class 속성에 포함될 스타일 클래스 이름을 생성한다. <c:forEach> 루프는 주어진 map에 담겨 있는 것 중 class 속성을 제외한 모든 속성을 출력한다. 이 속성은 루프를 마친 다음에 추가된다.


Listing 6. attrList.tag 파일
                
<%@ attribute name="tag" required="true" rtexprvalue="true" %>
<%@ attribute name="map" required="true" rtexprvalue="true"
    type="java.util.Map" %>
<%@ tag body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<c:if test="${!empty initParam.skin}">
    <c:set var="classAttr" value="${initParam.skin}_${tag}"/>
</c:if>

<c:forEach var="attr" items="${map}">
    <c:if test="${attr.key != 'class'}">
        ${attr.key}="${fn:escapeXml(attr.value)}"
    </c:if>
    <c:if test="${attr.key == 'class'}">
        <c:set var="classAttr" value="${classAttr} ${attr.value}"/>
    </c:if>
</c:forEach>

<c:if test="${!empty classAttr}">
    class="${fn:escapeXml(classAttr)}"
</c:if>

skin 매개변수는 웹 애플리케이션의 web.xml 파일(Listing 7)에 정의하고 있다.


Listing 7. web.xml에 skin 매개변수 설정하기
                
<web-app ...>
    <context-param>
        <param-name>skin</param-name>
        <param-value>default</param-value>
    </context-param>
</web-app>

<df:textarea> 컴포넌트 수정하기

Listing 8은 textarea.tag 파일을 수정한 버전을 보여준다. <textarea> 엘리먼트의 동적인 속성들을 출력할 때 <dfu:attrList>를 사용하고 있다.


Listing 8. textarea.tag 수정 버전
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<c:if test="${pageContext.request.method == 'POST' && !empty param[name]}">
    <c:set target="${dataModel}" property="${name}" value="${param[name]}"/>
</c:if>

<textarea name="${name}" <dfu:attrList tag="textarea" map="${dynAttr}"/>
><c:out value="${dataModel[name]}"/></textarea>

스타일 규칙은 CSS 파일에 정의할 수 있다. 예를 들어, <df:textarea>가 생성한 <textarea> 엘리먼트의 모든 경계선을 변경하고 싶다면, Listing 9에 있는 것처럼 스타일 규칙을 코딩하면 된다.


Listing 9. <textarea> 엘리먼트 기본 속성 정의하기
                
.default_textarea
    { border-color: #A0A0A0; border-style: solid; border-width: thin; }

폼 처리와 페이지 내비게이션 규칙 코딩하기

JSF 프레임워크는 6단계로 분리되어 있는 복잡한 요청 처리 생명 주기를 가지고 있다. 극히 소수의 개발자만이 이것을 완전히 이해하고 있으며 커스터마이징할 수도 있다. 트래픽이 높은 Ajax 애플리케이션의 경우, CPU 오버로드를 피하려고 HTTP 요청 처리 메커니즘을 더 간단하게 하고 싶을 것이다. 본 절에서 JSTL과 JSP 태그 파일의 도움으로 이것을 얼마나 간단하게 구현할 수 있는지 살펴보겠다.

<df:form> 컴포넌트 만들기

form.tag 파일(Listing 10)은 <form> 엘리먼트를 생성하고 JSTL을 사용하여 이 기사 뒤에서 살펴볼 이 태그의 내부 태그들이 접근할 변수들을 설정한다. 앞서 JSP 예제처럼 dataModel 객체를 사용하는 대신, 태그 파일은 model 속성을 가지고 있다. 이 속성은 다른 JSP 태그 파일 속성들처럼 page 스코프에 넣을 수 있다.

form.tag 파일은 <c:set> JSTL 태그를 사용하여 몇몇 속성을 복사하여 request 스코프에 넣어둔다. 그렇게 하여 다른 태그들이 name, model, action 속성의 값을 formName, formModel formAction 변수를 통해 사용할 수 있게 한다. 게다가, formPost 변수는 HTTP 방식이 POST인지 알려주고 <jsp:useBean>은 처리시에 발생하는 어떤 에러든지 담아둘 HashMap 객체를 생성한다.


Listing 10. form.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="action" required="false" rtexprvalue="true" %>
<%@ attribute name="method" required="false" rtexprvalue="true" %>
<%@ attribute name="model" required="true" rtexprvalue="true"
    type="java.lang.Object"%>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<c:set var="formName" scope="request" value="${name}"/>
<c:set var="formModel" scope="request" value="${model}"/>
<c:if test="${!empty action}">
    <c:set var="formAction" scope="request" value="${action}"/>
</c:if>
<c:set var="formPost" scope="request"
    value="${fn:toUpperCase(pageContext.request.method) == 'POST'}"/>
<jsp:useBean id="formErrors" scope="request" class="java.util.HashMap"/>

<form name="${name}" method="POST" <dfu:attrList tag="form" map="${dynAttr}"/>>
    <jsp:doBody/>
</form>

<c:if test="${formPost && empty formErrors && !empty formAction}">
    <c:set var="forwardURL" value="${formAction}"/>
</c:if>

<c:remove var="formName" scope="request"/>
<c:remove var="formModel" scope="request"/>
<c:remove var="formAction" scope="request"/>
<c:remove var="formPost" scope="request"/>
<c:remove var="formErrors" scope="request"/>

<c:if test="${!empty forwardURL}">
    <jsp:forward page="${forwardURL}"/>
</c:if>

<form> 엘리먼트는 <df:form> 태그와 HTML 속성이 같은데 methodaction만 예외다. method는 항상 POST이고 action은 생략되어 데이터가 같은 페이지로 다시 포스트된다. <form> 엘리먼트는 <jsp:doBody>가 생성한 결과도 포함하고 있다. 해당 결과는 <df:form></df:form> 사이에 위치한 JSP 코드를 실행한 것이다(Listing 11). HTTP 방식이 POST이고 에러가 없다면, form.tag 파일은 요청을 action 속성에 설정한 URL로 페이지 처리를 계속 진행한다. 그전에, 태그 파일은 <c:remove> JSTL 태그를 사용해 request 스코프 변수들을 제거한다.


Listing 11. JSP 페이지에서 <df:form> 사용하기
                
<df:form name="..." model="${...}" action="...">
    ...
    <df:textarea .../>
    ...
</df:form>

사용자 에러와 애플리케이션 예외 다루기

앞서 살펴본 textarea.tag 파일은 폼 요소에 연결되어 있는 JavaBean 속성에 값을 설정할 때 발생할 수 있는 어떠한 예외도 처리하지 않았다. 이것은 setProp.tag 파일(Listing 12)로 해결한다. 이 태그 파일은 <c:catch> JSTL 태그를 사용해 JavaBean 속성 메서드가 던지는 예외 또는, <c:set> 태그에 의해 발생할 수 있는 NumberFormatException 같은 컨버전(conversion) 예외를 잡는다.


Listing 12. setProp.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="array" required="false" rtexprvalue="true"
    type="java.lang.Boolean" %>
<%@ attribute name="bool" required="false" rtexprvalue="true"
    type="java.lang.Boolean" %>
<%@ tag body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<c:set var="propValue" value="${null}"/>
<c:if test="${formPost && empty formErrors[name]}">
    <c:if test="${!array && !bool && !empty param[name]}">
        <c:set var="propValue" value="${param[name]}"/>
    </c:if>
    <c:if test="${!array && bool}">
        <c:set var="propValue" value="${!empty param[name]}"/>
    </c:if>
    <c:if test="${array && fn:length(paramValues[name]) > 0}">
        <c:set var="propValue" value="${paramValues[name]}"/>
    </c:if>
</c:if>

<c:if test="${propValue != null}">
    <c:catch var="exception">
        <c:set target="${formModel}" property="${name}" value="${propValue}"/>
    </c:catch>
    <c:if test="${exception != null}">
        <dfu:addError name="${name}" msg="${exception.message}"
            exception="${exception}"/>
    </c:if>
</c:if>

setProp.tag 파일을 사용하여 String, int, float, booleanString 배열의 인덱스가 있는 속성들 같이 <c:set>으로 받을 수 있는 타입을 가진 속성을 설정할 수 있다. 타입 변환 에러가 발생하거나 예외가 던져지면, addError.tag 파일(Listing 13)을 사용하여 에러 메시지를 formErrors 맵에 추가한다.


Listing 13. addError.tag 파일
                
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="msg" required="true" rtexprvalue="true" %>
<%@ attribute name="exception" required="false" rtexprvalue="true"
    type="java.lang.Throwable" %>

<c:if test="${!empty formErrors[name]}">
    <c:set var="msg" value="${formErrors[name]}; ${msg}"/>
</c:if>
<c:set target="${formErrors}" property="${name}" value="${msg}"/>

<c:if test="${exception != null}">
    <% ((Throwable) jspContext.getAttribute("exception")).printStackTrace(); %>
</c:if>

textarea.tag 파일 최종 버전(Listing 14)은 <dfu:setProp>을 사용하여 JavaBean 속성을 설정한다. 또한, 태그 파일은 JSP 페이지의 기본 값을 <df:textarea></df:textarea> 사이에 기술할 수 있게 한다. 빈(bean) 속성이 빈(empty) 값이면, 태그 파일은 <jsp:doBody>를 사용하여 JSP 페이지의 커스텀 태그 폼의 내용을 가져온다.


Listing 14. textarea.tag 최종 버전
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<dfu:setProp name="${name}"/>

<c:set var="textareaValue" value="${formModel[name]}"/>
<c:if test="${empty textareaValue}">
    <jsp:doBody var="textareaValue"/>
</c:if>

<textarea name="${name}" <dfu:attrList tag="textarea" map="${dynAttr}"/>
><c:out value="${textareaValue}"/></textarea>

좀 더 많은 UI 컴포넌트 개발하기

지금까지, 자신만의 텍스트 영역을 만드는 방법과 HTTP 요청을 처리하는 방법을 살펴봤다. 이 절에서는 리스트, 입력 필드, 체크박스, 라디오 버튼과 제출 버튼 같은 추가 컴포넌트를 만들어 웹 폼 전체를 구성할 수 있게 한다.

<df:select>와 <df:option> 컴포넌트 만들기

JSP 페이지에서, HTML 엘리먼트인 <option><select>처럼 <df:option> 태그는 <df:select> 내부에 사용한다. 따라서 select.tag 파일(Listing 15)은 namemultiple 속성 값을 request 스코프로 복사해 option.tag에서 접근할 수 있게 한다. 그러면, select.tag 파일은 <dfu:setProp>를 사용해 폼 엘리먼트와 이름이 같은 모델 속성에 설정한다. multiple 속성이 true면, 속성의 타입은 배열이 되어야 한다.


Listing 15. select.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="multiple" required="false" rtexprvalue="true"
    type="java.lang.Boolean" %>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<c:set var="selectName" scope="request" value="${name}"/>
<c:set var="selectMultiple" scope="request" value="${multiple}"/>

<dfu:setProp name="${name}" array="${multiple}"/>

<select name="${name}"
    <c:if test="${multiple}">
        multiple
    </c:if>
    <dfu:attrList tag="select" map="${dynAttr}"/>
>
    <jsp:doBody/>
</select>

<c:remove var="selectName" scope="request"/>
<c:remove var="selectMultiple" scope="request"/>

JSP 페이지에서, <df:select> 태그는 <df:select> idclass 같은 동적인 속성을 가질 수 있다. 이 속성들은 <dfu:attrList>의 도움으로 출력된 <select> 엘리먼트에 전해진다. <jsp:doBody> JSP 태그는 몇 개의 <df:option>으로 구성되어 있는 <df:select>의 내용을 실행한다.

<option> 엘리먼트를 생성하기 전에, option.tag 파일(Listing 16)은 optionLabeloptionValue 변수를 설정한다. option.tag 파일에서 사용하는 <dfu:isSelected> 태그는 option의 값이 <select> 엘리먼트와 동일한 이름을 가진 모델 속성의 값과 같을 때에만 selected 문자열이 담고 있는 내용을 출력한다. 따라서 데이터 모델의 속성은 <df:select><df:option>에 의해 생성된 선택 목록의 초기값을 결정할 수 있다.


Listing 16. option.tag 파일
                
<%@ attribute name="value" required="false" rtexprvalue="true" %>
<%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<jsp:doBody var="optionLabel"/>

<c:set var="optionValue" value="${value}"/>
<c:if test="${empty optionValue}">
    <c:set var="optionValue" value="${optionLabel}"/>
</c:if>

<option
    <c:if test="${!empty value}">
        value="${fn:escapeXml(value)}"
    </c:if>
    <dfu:isSelected name="${selectName}" value="${optionValue}"
        array="${selectMultiple}">
        selected
    </dfu:isSelected>
    <dfu:attrList tag="option" map="${dynAttr}"/>
><c:out value="${optionLabel}"/></option>

Listing 17은 isSelected.tag 파일을 보여준다. 이 태그는 option.tag에서 호출한다. array 속성이 false면, isSelected.tag 파일은 value 속성을 formModel[name]과 비교한다. array 속성이 true면, formModel[name]을 배열로 생각하고 isSelected.tag는 각각의 엘리먼트 값을 value 속성과 비교한다.


Listing 17. isSelected.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="value" required="true" rtexprvalue="true" %>
<%@ attribute name="array" required="false" rtexprvalue="true"
    type="java.lang.Boolean" %>
<%@ tag body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<c:set var="selected" value="false"/>

<c:if test="${!array}">
    <c:set var="selectedValue" value="${formModel[name]}"/>
    <c:if test="${!empty selectedValue}">
        <c:if test="${selectedValue == value}">
            <c:set var="selected" value="${true}"/>
        </c:if>
    </c:if>
</c:if>

<c:if test="${array}">
    <c:set var="selectedValues" value="${formModel[name]}"/>
    <c:if test="${fn:length(selectedValues) > 0}">
        <c:forEach var="selectedValue" items="${selectedValues}">
            <c:if test="${selectedValue == value}">
                <c:set var="selected" value="${true}"/>
            </c:if>
        </c:forEach>
    </c:if>
</c:if>

<c:if test="${selected}">
    <jsp:doBody/>
</c:if>

isSelected.tag가 실행하는 <jsp:doBody> 태그는 option.tag 태그에 있는 <dfu:isSelected></dfu:isSelected> 사이에 있는 JSP 코드를 실행한다. 여기서는 보통 selected 문자열을 출력한다. <dfu:isSelected> 컴포넌트는 input.tag에서도 라디오 버튼이 선택됐는지 확인할 때 사용한다.

<df:input> 컴포넌트 만들기

input.tag 파일(Listing 18)은 <input> 엘리먼트를 출력한다. type 속성의 값에 따라 태그 파일은 각기 다른 JSP 조각을 실행한다. 그런 식으로 HTML 엘리먼트와 같은 이름을 가지고 있는 모델 속성을 설정한다.

typetext, password 또는 hidden이면, <dfu:setProp> 태그는 HTTP 방식이 POST일 때,요청 매개변수를 JavaBean 객체에 저장한다. 그런 다음, bean 속성의 값을 formModel[name]에서 가져오고 HTTP 방식이 GET이든 POST든 상관없이 inputValue 변수에 저장한다.

radio 버튼의 경우, 앞서 살펴봤던 <dfu:isSelected> 태그를 사용하여 라디오 버튼과 같은 이름을 가지고 있는 모델 속성 값과 value 속성을 비교한다. 두 값이 같으면, inputChecked 변수는 true가 된다. type 속성이 checkbox면, bean 속성의 타입은 boolean이 된다.


Listing 18. input.tag 파일
                
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="type" required="true" rtexprvalue="true" %>
<%@ attribute name="value" required="false" rtexprvalue="true" %>
<%@ attribute name="action" required="false" rtexprvalue="true" %>
<%@ tag dynamic-attributes="dynAttr" body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>

<c:set var="inputType" value="${fn:toLowerCase(type)}"/>
<c:set var="inputValue" value="${value}"/>
<c:set var="inputChecked" value="${false}"/>

<c:choose>
    <c:when test="${inputType=='text' || inputType=='password' || inputType=='hidden'}">
        <dfu:setProp name="${name}"/>
        <c:set var="inputValue" value="${formModel[name]}"/>
    </c:when>
    <c:when test="${inputType == 'radio'}">
        <dfu:setProp name="${name}"/>
        <dfu:isSelected name="${name}" value="${value}">
            <c:set var="inputChecked" value="${true}"/>
        </dfu:isSelected>
    </c:when>
    <c:when test="${inputType == 'checkbox'}">
        <dfu:setProp name="${name}" bool="true"/>
        <c:if test="${formModel[name]}">
            <c:set var="inputChecked" value="${true}"/>
        </c:if>
    </c:when>
    <c:when test="${inputType=='submit'}">
        <c:if test="${formPost && !empty param[name] && !empty action}">
            <c:set var="formAction" scope="request" value="${action}"/>
        </c:if>
    </c:when>
</c:choose>

<input name="${name}" type="${type}"
    <c:if test="${!empty inputValue}">
        value="${fn:escapeXml(inputValue)}"
    </c:if>
    <c:if test="${inputChecked}">
        checked
    </c:if>
    <dfu:attrList tag="input_${type}" map="${dynAttr}"/>
>

submit 버튼이 사용하는 input.tag 파일이 받아오는 action 속성을 사용해 submit 버튼을 클릭했을 때 폼 액션을 수행할 페이지 URL을 설정할 수 있다. 폼은 여러 개의 제출 버튼을 가질 수 있으므로 각각의 버튼은 HTML 응답을 생성할 각기 다른 페이지로 요청할 수 있다. 이렇게 하는 것이 JSF 프레임워크처럼 내비게이션 룰을 설정 파일에 정의해두는 것보다 훨씬 간편하다.

Ajax를 사용하여 웹 폼 제출하기

"Use XMLHttpRequest to submit JSF forms"(참고자료)라는 기사에서, 웹 폼의 데이터를 Ajax를 사용해 보내는 방법을 설명했다. 해당 기사는 SupportForm.jsp라는 JSF 예제를 포함하고 있다. 여기서 비슷한 예제를 사용하여 JSF 기사에서 사용한 폼과 본 기사의 JSP 태그 파일을 기반으로 한 웹 폼을 비교할 것이다.

JavaBean 또는 자바 Map을 사용하는 폼 만들기

SupportBean 클래스(Listing 19)는 웹 폼 예제의 엘리먼트와 관련된 속성들을 가지고 있는 간단한 빈이다.


Listing 19. SupportBean 클래스
                
package formsdemo;

public class SupportBean implements java.io.Serializable {
    private String name;
    private String email;
    private String versions[];
    private String platform;
    private String browser;
    private boolean crash;
    private String problem;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    ...
}

Listing 20은 SupportForm.jsp 페이지를 보여준다. 이 페이지는 본 기사의 앞에서 살펴봤던 태그 파일 라이브러리를 사용한다. 웹 컴포넌트들은 그것들이 생성하는 HTML 엘리먼트와 같은 속성들을 가지고 있다. <df:form> 태그는 추가적인 model 속성을 가지고 있다. 이 속성은 폼 데이터를 저장할 JavaBean 객체를 나타낸다.


Listing 20. SupportForm.jsp 예제
                
<%@ taglib prefix="df" tagdir="/WEB-INF/tags/dynamic/forms" %>

<jsp:useBean id="supportBean" scope="request" class="formsdemo.SupportBean"/>

<html>
<head>
    <title>Support Form</title>
    <link rel="stylesheet" href="forms.css" type="text/css">
</head>
<body>

    <h1>Support Form</h1>

    <df:form name="supportForm" model="${supportBean}"
            action="SupportConfirm.jsp">

        <p>Name: <br>
        <df:input name="name" type="text" size="40"/>

        <p>Email: <br>
        <df:input name="email" type="text" size="40"/>

        <p>Versions: <br>
        <df:select name="versions" multiple="true" size="5">
            <df:option>2.0.1</df:option>
            <df:option>2.0.0</df:option>
            <df:option>1.1.0</df:option>
            <df:option>1.0.1</df:option>
            <df:option>1.0.0</df:option>
        </df:select>

        <p>Platform: <br>
        <df:input name="platform" type="radio" value="Windows"/> Windows <br>
        <df:input name="platform" type="radio" value="Linux"/> Linux <br>
        <df:input name="platform" type="radio" value="Mac"/> Mac <br>

        <p>Browser: <br>
        <df:select name="browser" size="1">
            <df:option value=""></df:option>
            <df:option value="IE">IE</df:option>
            <df:option value="Firefox">Firefox</df:option>
            <df:option value="Netscape">Netscape</df:option>
            <df:option value="Mozilla">Mozilla</df:option>
            <df:option value="Opera">Opera</df:option>
            <df:option value="Safari">Safari</df:option>
        </df:select>

        <p><df:input name="crash" type="checkbox"/> Causes browser crash

        <p>Problem: <br>
        <df:textarea name="problem" rows="10" cols="40"/>

        <p><df:input name="submit" type="submit" value="Submit"/>

    </df:form>

</body>
</html>

SupportForm.jsp에서 사용한 <df:form> 태그는 HTTP 요청을 SupportConfirm.jsp 페이지(Listing 21)로 넘긴다. 이 페이지의 URL은 action 속성에 설정한다. 이 확인 페이지는 데이터 모델의 속성을 출력한다.


Listing 21. SupportConfirm.jsp 페이지
                
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
    <title>Support Confirmation</title>
</head>
<body>

    <h1>Support Confirmation</h1>

    <p>Your information was received.

    <p>Name: <c:out value="${supportBean.name}"/><br>
    <p>Email: <c:out value="${supportBean.email}"/><br>
    <p>Versions: <c:forEach var="version" items="${supportBean.versions}">
        <c:out value="${version}"/></c:forEach><br>
    <p>Platform: <c:out value="${supportBean.platform}"/><br>
    <p>Browser: <c:out value="${supportBean.browser}"/><br>
    <p>Causes browser crash: <c:out value="${supportBean.crash}"/>
    <p>Problem: <c:out value="${supportBean.problem}"/><br>

</body>
</html>

<c:set> 같은 JSP EL과 JSTL 태그는 JavaBean 속성과 Map 객체의 요소에 접근하는 동일한 문법을 가지고 있다. 따라서 앞선 예제의 formsdemo.SupportBean 객체 대신 java.util.HashMap을 사용할 수 있다. 변경해야 할 것은 <jsp:useBean> 태그(Listing 22)에 있는 class 이름뿐이다.


Listing 22. SupportForm2.jsp 예제
                
<jsp:useBean id="supportBean" scope="request" class="java.util.HashMap"/>
...
<df:form name="supportForm" model="${supportBean}" action="SupportConfirm.jsp">
    ...
</df:form>

웹 폼에 Ajax 코드 추가하기

소스 코드 압축 파일에 있는 AutoSaveScript.js 파일은 "Use XMLHttpRequest to submit JSF forms"(참고자료)에서 전부 설명했다. 이 자바스크립트 파일은 본 기사에서 다시 사용한 몇 가지 함수ygw 가지고 있다. getFormData() 함수(Listing 23)는 form 객체를 가져간 다음, 폼의 데이터를 문자열로 인코딩하여 반환한다.


Listing 23. AutoSaveScript.js의 getFormData() 함수
                
function getFormData(form) {
    var dataString = "";

    function addParam(name, value) {
        dataString += (dataString.length > 0 ? "&" : "")
            + escape(name).replace(/\+/g, "%2B") + "="
            + escape(value ? value : "").replace(/\+/g, "%2B");
    }

    var elemArray = form.elements;
    for (var i = 0; i < elemArray.length; i++) {
        var element = elemArray[i];
        var elemType = element.type.toUpperCase();
        var elemName = element.name;
        if (elemName) {
            if (elemType == "TEXT"
                    || elemType == "TEXTAREA"
                    || elemType == "PASSWORD"
                    || elemType == "HIDDEN")
                addParam(elemName, element.value);
            else if (elemType == "CHECKBOX" && element.checked)
                addParam(elemName, element.value ? element.value : "On");
            else if (elemType == "RADIO" && element.checked)
                addParam(elemName, element.value);
            else if (elemType.indexOf("SELECT") != -1)
                for (var j = 0; j < element.options.length; j++) {
                    var option = element.options[j];
                    if (option.selected)
                        addParam(elemName,
                            option.value ? option.value : option.text);
                }
        }
    }
    return dataString;
}

AutoSaveScript.js 파일의 submitFormData() 함수(Listing 24)는 두 개의 매개변수를 가지고 있다. form은 Ajax를 사용해 서버로 보낼 데이터를 가지고 있는 객체이고 callback은 Ajax 응답을 처리할 때 호출할 함수다.


Listing 24. AutoSavaScript.js의 submitFormData() 함수
                
function submitFormData(form, callback) {
    var xhr;
    if (window.ActiveXObject)
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    else if (window.XMLHttpRequest)
        xhr = new XMLHttpRequest();
    else
        return null;
        
    var method = form.method ? form.method.toUpperCase() : "GET";
    var action = form.action ? form.action : document.URL;
    var data = getFormData(form);

    var url = action;
    if (data && method == "GET")
        url += "?" + data;
    xhr.open(method, url, true);
    
    function submitCallback() {
        if (callback && xhr.readyState == 4 && xhr.status == 200)
            callback(xhr);
        ...
    }
    xhr.onreadystatechange = submitCallback;

    xhr.setRequestHeader("Ajax-Request", "Auto-Save");
    if (method == "POST") {
        xhr.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded");
        xhr.send(data);
    } else
        xhr.send(null);
    
    return xhr;
}

Listing 25는 submitAllForms() 함수다. 이 함수는 현재 웹 페이지에 있는 모든 폼의 데이터를 보낸다. 게다가, 이 함수는 메모리에서 앞선 XMLHttpRequest 객체를 삭제하여 웹 브라우저의 메모리 부족을 방지한다.


Listing 25. AutoSaveScript.js의 submitAllForms() 함수
                
var autoSaveXHR = new Array();

function submitAllForms(callback) {
    var formArray = document.forms;
    for (var i = 0; i < formArray.length; i++) {
        if (autoSaveXHR[i]) {
            var oldXHR = autoSaveXHR[i];
            oldXHR.onreadystatechange = function() { };
            oldXHR.abort();
            delete oldXHR;
        }
        autoSaveXHR[i] = submitFormData(formArray[i], callback);
    }
}

SupportForm3.jsp 페이지(Listing 26)는 앞에서 살펴봤던 웹 폼의 수정된 버전이다. 이 JSP 페이지는 AutoSaveScript.js를 헤더에서 임포트(import)하고 별도 창에서 Ajax 응답을 보여줄 콜백(callback) 함수를 정의한다. 또한 폼은 Submit with Ajax라는 두 번째 버튼을 가지고 있다. 이 버튼은 웹 브라우저가 폼을 다시 보내지 않도록 false를 반환하는 submitSupportForm() 함수를 호출한다.


Listing 26. SupportForm3.jsp 예제
                
<%@ taglib prefix="df" tagdir="/WEB-INF/tags/dynamic/forms" %>

<jsp:useBean id="supportBean" scope="request" class="formsdemo.SupportBean"/>

<html>
<head>
    ...
    <script type="text/javascript" src="AutoSaveScript.js">
    </script>
    <script type="text/javascript">
        function ajaxCallback(xhr) {
            confirmWindow=window.open("", "_blank",
                "menubar=no, resizable=yes, scrollbars=yes, width=600, height=600");
            confirmWindow.document.write(xhr.responseText);
            confirmWindow.document.close();
        }

        function submitSupportForm() {
            submitAllForms(ajaxCallback);
            return false;
        }
    </script>
</head>
<body>
    ...
    <df:form name="supportForm" model="${supportBean}"
            action="SupportConfirm.jsp">
        ...
        <df:input name="ajaxSubmit" type="submit" value="Submit with Ajax"
            onclick="return submitSupportForm()"/>
    </df:form>
</body>
</html>

결론

본 기사에서 웹 개발을 간단하게 해주는 규약들을 사용하여 가벼운 컴포넌트들을 개발하는 방법을 살펴보았다. 기본적인 폼 요소인 리스트, 텍스트 필드, 라디오 버튼, 체크 박스, 제출 버튼을 만들어 내는 태그 파일 집합을 구현하는 JSP 코드가 10K도 안 된다. 컴포넌트들은 모든 기능을 갖추고 있으나 데이터 검증, 에러 리포트 같은 필수 기능이 빠져 있다. 본 연재의 다음 기사를 계속 참조하기 바란다. 다음에는 서버 쪽에서 검증하는 JSP 태그 파일을 만드는 방법과 클라이언트 쪽에서 검증을 하는 자바스크립트 코드를 생성하는 방법을 살펴볼 것이다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
이 글의 예제 애플리케이션wa-aj-simplejava2.zip16KBHTTP
다운로드 방식에 대한 정보


참고자료

  • 포럼에 참여하기.

  • "JSF 페이지 외관 개선하기"(Andrei Cioroianu, 한국 developerWorks, 2008년 3월) 표준 JSF 컴포넌트에 기본 스타일을 구현하는 방법을 설명한다. 본 기사는 "JSF, CSS, 자바스크립트를 사용하는 정교한 Ajax 애플리케이션" 연재의 Part 1이다.

  • "Use XMLHttpRequest to submit JSF forms"(Andrei Cioroianu, developerWorks, 2007년 8월) SupportForm.jsp 예제의 JSF 기반 예제가 포함되어 있고 AutoSaveScript.js 파일에 대해 자세히 설명하고 있다. 이 기사는 "Auto-save JSF forms with Ajax" 연재의 첫 번째 기사다.

  • 한국 developerWorks 웹 개발 존에는 웹 2.0 개발과 관련된 도구와 정보가 많다.

  • developerWorks Ajax 참고자료 센터에는 오늘 바로 Ajax 애플리케이션 개발을 시작할 수 있는 유용한 자료와 상당량의 Ajax 기사가 있다.

  • JSP 기술 홈페이지에 가서 더 많은 JSP에 관한 정보를 참조하라.


필자소개

Andrei Cioroianu는 Devsphere 창립자로, 자바 EE 개발과 Ajax/JSF 컨설팅 서비스를 제공하고 있다. Andrei Cioroianu는 자바와 웹 서비스 기술을 1997년부터 사용해 왔고, 10년 동안 복잡한 기술 문제를 해결하고 있으며 상용 제품, 고객 애플리케이션 그리고 오픈 소스 프레임워크 전반을 다루고 있다. devsphere.com에서 Andrei를 만날 수 있을 것이다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의