JSF(JavaServer Faces) 기술은 Java EE 및 J2EE 애플리케이션을 위한 사용자 인터페이스의 구축을 간소화시켜주는 프레임워크로서, 2004년 3월 24일자 테크 팁, JavaServer Faces 기술 소개(영문)에서 이 기술을 간략하게 개관한 바 있다. 아울러, JSF 프레임워크에 의해 모델링되는 GUI 컴포넌트가 포함된 JSF 애플리케이션을 제작하는 방법에 대해서도 알아보았다. 이후 2004년 12월 25일자 팁에서 JavaServer Faces 기술과 사용자 정의 컴포넌트에 대해 다루었는데, 이 팁들이 출판된 이후에 JSF 기술은 1.2 레벨로 업그레이드되었다. 이 JSF 1.2에는 단일의 JSF 애플리케이션에서 복수의 렌더 키트를 지원하는 기능이 새로 포함되었다. 또한, JSF에서는 렌더 키트를 이용하여 HTML 같은 마크업으로 된 사용자 인터페이스를 제시한다. 한편, JSF의 경우 HTML 렌더 키트가 포함되어 있기는 하지만, SVG(Scalable Vector Graphics)나 XUL(XML User Interface Language) 같은 다른 마크업 언어로 된 사용자 인터페이스 컴포넌트를 디스플레이하기 위해 렌더 키트를 직접 작성할 수도 있다.
각 마크업 언어가 제공하는 강점들을 하나의 JSF 애플리케이션에 결합하여 활용하는 것도 가능한데, 예를 들어 SVG를 이용하여 애플리케이션을 위한 정교한 그래픽과 애니메이션을 제작할 수 있다. 하지만 SVG는 버튼이나 그 밖의 컴포넌트가 포함된 위젯 세트(widget set)를 가지고 있지 않으므로 사용자는 마크업을 이용하여 위젯을 생성해야 한다. 이와 대조적으로 XUL은 사용하기 쉬운 완벽한 사용자 인터페이스 컨트롤 세트를 가지고 있으나, SVG이 제공하는 그래픽 기능은 빠져 있다. 이 두 마크업에 있어서 공통된 점은 HTML에서 제공되는 것과 같은 빌트인(built-in) 폼 제출 메커니즘이 없다는 것이다. 하지만 AJAX(Asynchronous JavaScript and XML)를 일부 JSF 1.2 기능과 함께 사용하면 비 HTML 마크업으로 폼 제출을 효과적으로 구현할 수 있다. 본 팁에서는 JSF 애플리케이션에서 AJAX와 세 가지 렌더 키트(HTML, SVG, XUL)를 사용하는 방법(데모 포함)에 대해 알아보도록 한다. (AJAX에 관한 자세한 내용은 2005년 12월 27일자 테크 팁, 자바 기술을 이용한 AJAX 활용하기 를 참조할 것.)
JSF 라이프 사이클 예제
JSF 애플리케이션과의 사용자 상호작용은 'JSF 라이프 사이클'이라고 하는 일련의 단계를 거쳐 처리되며, 일반적으로 라이프 사이클은 각 박스가 프로세스의 한 단계를 나타내는 일련의 연결된 박스로 예시된다. 화살표가 있는 선은 특정 단계에서 다음 단계로의 흐름을 나타낸다. 본 팁에는 JSF 라이프 사이클을 애니메이션으로 디스플레이하는 예제 JSF 애플리케이션이 포함되어 있지 않으며, 앞서 언급한 것처럼 애플리케이션에는 HTML, SVG, XUL 등의 렌더 키트가 사용되었다.
아래 도표는 서로 다른 마크업 페이지들 사이를 이동하는 데모의 흐름을 보여준다.
애플리케이션은 데모를 비롯하여 관련 배경과 디자인 세부사항을 보여주는 HTML 페이지의 디스플레이로 시작된다.
이 페이지는 표준 HTML 렌더 키트로 제작되었는데, 페이지 하단의 Next 버튼을 클릭하면 SVG 렌더 키트로 렌더링된 SVG 페이지가 디스플레이된다. SVG 페이지는 JSF 라이프 사이클을 그래픽으로 예시한다.
라이프 사이클 단계(가령 Retore View Phase)를 나타내는 임의의 상자를 클릭하면 AJAX에 의해 폼 제출이 생성되고, 이어서 JSF 라이프 사이클 단계에 관한 자세한 내용을 보여주는 XUL 페이지가 표시된다.
JSF 라이프 사이클 예시의 아래쪽에는 Initial Request, Validation Error 등의 라벨이 붙은 다양한 버튼이 포함된 더 큰 박스가 있다. 이 버튼을 클릭하면 라이프 사이클 단계들을 통과하는 특정 프로세스 흐름(가령, 초기 요청을 처리하는 프로세스 흐름)이 애니메이션으로 디스플레이된다.
데모를 위한 코드 몇 가지를 살펴보기로 하자.
클라이언트에서 포스팅하기
먼저 SVG 페이지를 살펴보자. 다음은 JSF 태그로 작성한 SVG 페이지의 단편이다.
<%@ page contentType="image/svg+xml"%>
<%@ taglib uri="http://java.sun.com/jsf/svg" prefix="g" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
...
<f:view renderKitId="SVG" >
<g:form id="form">
...
<g:commandButton id="restore" width="120"
height="50" x="100" y="100" type="submit"
action="xul-restore"
style="stroke:black; fill:#8470ff;" >
<g:outputText x="130" y="120"
textAnchor="middle" value="Restore" />
<g:outputText x="135" y="140"
textAnchor="middle" value="View" />
</g:commandButton>
...
</g:form>
</f:view>
페이지의 컨텐츠 타입을 나타내는 페이지 지시어, 그리고
<%@ page contentType="image/svg+xml"%>
SVG가 페이지의 렌더링 기술임을 나타내는 다음 태그에 유의할 것.
<f:view renderKitId="SVG" >
커스텀 렌더 키트 생성하고 사용하기(영문) 문서에서는 SVG 등의 렌더 키트를 사용하는 페이지에 표시되어야 할 지시어, 태그, 속성 등에 관한 자세한 설명이 포함되어 있다.
SVG 페이지의 정보에 대응하여, 서버 상의 SVG 렌더 키트는 다음과 같은 SVG 마크업을 생성한다.
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
...
<script xlink:href="/jsf-renderkits/src/script/http-svg.es">
</script>
<script xlink:href="/jsf-renderkits/src/script/lifecycle.es">
</script>
<g id="form" method="post"
action="/jsf-renderkits/render/svg.jsp">
...
<g id="form:restore" onclick="form_post(evt)">
<rect width="120" height="50" x="100" y="100"
style="stroke:black; fill:#8470ff;"></rect>
<text x="100" y="150" text-anchor="middle"></text>
<text x="130" y="120">Restore</text>
<text x="135" y="140">View</text>
</g>
...
<text name="javax.faces.ViewState"
id="javax.faces.ViewState" value="_id4:_id6"
visibility="hidden">_id4:_id6</text>
<text name="javax.faces.RenderKitId"
id="javax.faces.RenderKitId" value="SVG"
visibility="hidden">SVG</text>
</g>
<script><![CDATA[
function form_post(evt) {
var control = evt.target;
var form = getForm(control);
var postData = getPostData(form, control);
var url = "/jsf-renderkits/render/svg.jsp";
sendRequest(url, postData);
}
//]]>
</script>
</svg>
이 코드는 SVG 렌더 키트의 다양한 렌더러에 의해 생성된다. FormRenderer의 encodeBegin() 메소드는 다음과 같은 ECMAScript 파일 레퍼런스, 그리고
<script xlink:href="/jsf-renderkits/src/script/http-svg.es"> </script> <script xlink:href="/jsf-renderkits/src/script/lifecycle.es"> </script>
시작 '폼' 그룹을 생성한다.
<g id="form" method="post"
action="/jsf-renderkits/render/svg.jsp">
이 SVG <g> 엘리먼트를 '폼' 엘리먼트로 취급하기 위해서 시작 '폼' 그룹의 action 속성이 사용된다. 한편, SVG에는 <g> 엘리먼트가 널리 사용된다.
ButtonRenderer와 TextRenderer 인코딩 메소드는 버튼 컨트롤의 시작 및 종료 그룹 엘리먼트, 그리고 관련 치수 및 라벨을 생성한다.
<g id="form:restore" onclick="form_post(evt)">
<rect width="120" height="50" x="100" y="100"
style="stroke:black; fill:#8470ff;"></rect>
<text x="100" y="150" text-anchor="middle"></text>
<text x="130" y="120">Restore</text>
<text x="135" y="140">View</text>
</g>
onclick 메소드 이름 form_post가 버튼 컨트롤의 상위 폼 컴포넌트에서 결정된다는 점에 유의할 것.
public void encodeBegin(FacesContext context,
UIComponent component)
throws IOException {
...
UIComponent root = context.getViewRoot();
UIComponent myForm = component;
while (!(myForm instanceof UIForm) && root != myForm) {
myForm = myForm.getParent();
}
String formMethodName = myForm.getClientId(context) +
"_post(evt)";
writer.writeAttribute(
"onclick", formMethodName, "onclick");
FormRenderer.encodeEnd() 메소드는 페이지 상태와 폼 컨트롤의 종료 그룹 엘리먼트를 작성한다.
<text name="javax.faces.ViewState"
id="javax.faces.ViewState" value="_id4:_id6"
visibility="hidden">_id4:_id6</text>
<text name="javax.faces.RenderKitId"
id="javax.faces.RenderKitId" value="SVG"
visibility="hidden">SVG</text>
</g>
아울러, 포스트 데이터를 수집하고 요청을 전송하는 JavaScript 함수가 생성된다.
<script><![CDATA[
function form_post(evt) {
var control = evt.target;
var form = getForm(control);
var postData = getPostData(form, control);
var url = "/jsf-renderkits/render/svg.jsp";
sendRequest(url, postData);
}
//]]>
</script>
요청이 JSF 컨트롤러로 전달되도록 매핑이 이루어진다.
getForm, getPostData, sendRequest 등의 함수는 모두 http-svg.es ECMAScript 파일에서 정의된다. (http-svg.es 파일은 예제 JSF 애플리케이션의 renderkits\src\script 디렉토리에 들어 있음.) 예를 들어, 다음은 getPostData 함수에 대한 정의의 일부이다.
function getPostData(form, control) {
var formValues = new Array();
formValues[0] = new Object();
formValues[0].id = control.parentNode.id;
formValues[0].value = control.parentNode.id;
formValues[1] = new Object();
formValues[1].id = form.id;
formValues[1].value = form.id;
...
var postData = "";
for (var i=0; i<formValues.length; i++) {
if (formValues[i].id == "javax.faces.ViewState") {
var re = new RegExp("\\+", "g");
var val = formValues[i].value;
formValues[i].value = val.replace(re, "\%2B");
}
postData += formValues[i].id + "=" +
formValues[i].value;
if (i != formValues.length-1) {
postData += "&";
}
}
return postData;
}
getPostData는 숨겨진 필드 javax.faces.ViewState를 전송한다는 점에 유의할 것. JSF는 이런 식으로 요청이 포스트 백이라는 것을 판단한다.
요청 처리하기
요청이 JSF에 전송될 때는 통상적인 JSF 라이프 사이클 처리 단계를 거치게 된다. 그러나 애플리케이션 호출 단계 후에(즉, 응답 단계를 렌더링하기 직전에) 실행되도록 등록된 특별한 JSF 단계 리스너가 있다. 요청이 AJAX 요청인 경우(요청 헤더에서 XML-HTTP 문자열에 의해 지시됨), 이 단계 리스너는 다음에 렌더링되는 뷰의 뷰 식별자를 얻게 되고, 이어서 단계 리스너는 뷰 식별자를 응답 헤더에 포함시킨다.
public class ResponsePhaseListener implements PhaseListener {
private static final String XML_HTTP = "XML-HTTP";
private static final String VIEW_URI = "VIEW-URI";
...
public void afterPhase(PhaseEvent event) {
// Disregard requests that are not XMLHttpRequest(s)
Map<String, String> requestHeaderMap =
event.getFacesContext().getExternalContext().
getRequestHeaderMap();
if (requestHeaderMap.get(XML_HTTP) == null) {
return;
}
// If we're dealing with an XMLHttpRequest...
// Get the URI and stuff it in the response header.
FacesContext context = event.getFacesContext();
String viewId = context.getViewRoot().getViewId();
String actionURL =
context.getApplication().getViewHandler()
.getActionURL(context, viewId);
HttpServletResponse response = (HttpServletResponse)
context.getExternalContext().getResponse();
response.setHeader("Cache-Control", "no-cache");
response.setHeader(VIEW_URI, actionURL);
}
...
}
processResponse 메소드는 응답 헤더로부터 뷰 식별자를 얻고, 이어서 클라이언트는 해당 위치를 로드한다. processResponse 메소드는 http-svg.es ECMAScript 파일에서 정의된다.
function processResponse() {
var request = getXMLHttpRequest();
if (request.readyState == 4) {
if (request.status == 200) {
var action =
request.getResponseHeader("VIEW-URI");
window.location.href = action;
return;
}
}
}
JSF에서의 렌더 키트 사용법에 관한 자세한 내용을 보려면 커스텀 렌더 키트 생성하고 사용하기 기술문서를 참조할 것.
예제 코드 실행하기
예제 패지키에는 팁에서 다룬 기법을 예시하는 본 팁이 포함되어 있으며, 사용자는 Servlet 2.5 API, JSP(JavaServer Pages) Technology 2.1 및 JSF 1.2를 지원하는 어떠한 웹 컨테이너에라도 예제 패키지를 설치할 수 있다. 아울러 JDK 5도 필요하고, 클라이언트(브라우저)의 경우에는 Firefox 1.5처럼 빌트인 SVG를 지원하는 Mozilla 브라우저를 사용해야 한다.
예제를 설치하고 실행하려면 다음의 작업 절차를 따르도록 한다.
- GlassFish가 설치되어있지 않다면 GlassFish 커뮤니티 다운로드 페이지에서 다운로드한다. GlassFish는 Servlet 2.5와 JSP 2.1을 'out of the box' 방식으로 지원한다.
- 해당 팁의 예제 패키지를 다운로드하여 압축을 해제한다. 이 때, 새로 압축이 풀린 디렉토리는
<sample_install_dir>/renderkits로 표시되어야 하는데, 여기서<sample_install_dir>은 예제 패키지가 설치된 디렉토리이다. 예를 들어, Windows의C:\에 압축을 해제했다면 새로 생성된 디렉토리는C:\renderkits이 되어야 한다. 렌더 키트 디렉토리에는 애플리케이션을 위한 웹 아카이브renderkits.war와 소스를 뷰하기 위한 서브디렉토리src와web이 포함되어 있다.
- 다음 명령어를 입력하여 GlassFish 애플리케이션 서버를 시작한다.
<GF_install_dir>/bin/asadmin start-domain domain1
이 때,<GF_install_dir>은 GlassFish가 설치된 디렉토리이다.
<sample_install_dir>/renderkits/jsf-renderkits.war를<GF_install_dir>/domains/domain1/autodeploy에 복사하여 예제를 설치한다.
- 브라우저를 실행하고 다음의 URL을 연다.
http://localhost:8080/jsf-renderkits/. 앞에서 언급한 것처럼, Firefox 1.5와 같은 내장 빌트인 SVG를 지원하는 Mozilla 브라우저를 사용해야 한다.
저자 소개
Roger Kitain은 JavaServer Faces 공동 스펙 프로젝트를 이끌고 있으며, 1997년부터 서버 측 웹 기술 및 제품 분야에 폭넓게 참여해 왔다. Roger는 또한 2001년에 레퍼런스 구현 팀의 멤버로서 JavaServer Faces 기술 관련 업무를 맡기 시작했으며, Servlet 및 JSP 기술을 통해 풍부한 경험을 축적했다. 최근에 그는 JSF의 다양한 렌더링 기술에 참여하여 왕성한 활동을 벌이고 있다.
"Java EE" 카테고리의 다른 글
- Attach API (댓글 18개 / 트랙백 1개) 2007/09/03
- JSP 2.0 EXPRESSION LANGUAGE (댓글 1개 / 트랙백 0개) 2004/02/05
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- JAX-WS를 이용한 웹 서비스 개발 (댓글 1개 / 트랙백 0개) 2006/01/18
- EJB 2.1로 메시지 구동 빈 이용하기 (댓글 1개 / 트랙백 0개) 2005/05/18
- EclipseLink를 사용하여 JPA에서 반복 불가능한 읽기 방지 (댓글 0개 / 트랙백 1개) 2008/07/09
- 컴포넌트 시스템과 클래스 로더 경계 (댓글 1개 / 트랙백 0개) 2004/10/05
- JAX-WS Dispatch 및 Provider API를 이용한 문서 처리 (댓글 4개 / 트랙백 0개) 2006/09/15
- POJO를 Persistent Entity로 변환하기 (댓글 1개 / 트랙백 0개) 2005/12/27
- SAAJ 소개 (댓글 1개 / 트랙백 0개) 2005/06/08
댓글을 달아 주세요
요즘 제가 제일 관심 가지는 AJAX네요..
2007/09/07 20:23구글에서 AJAX로만 구현한 엑셀보고 놀라움을 금치 못했는데..
한국도 하루 빨리 ACTIVE-X따윈 버리고 JAVA AJAX의 세계로 사용자들을
이끌어주면 좋겠어요
좋은 정보 감사해요~
2007/09/19 04:42