2011년 6월 9일 목요일

JavaServer Faces 입문

저자: 부디 쿼니아완(Budi Kurniawan), 역 이상화

자바 웹 프로그래밍 분야에서 JavaServer Faces(이하 JSF)는 새로운 화두로 떠오르고 있다. JSF로 여러분들은 사용자에 의해 발생된 이벤트를 잡아 내거나 웹 페이지에서 웹 컴포넌트를 사용할 수 있다. 가까운 미래에 자바 개발 도구들이 이런 기술을 지원할 것이다. 웹 애플리케이션을 개발하는 것은 현재 드래깅(dragging)과 드로핑(dropping) 및 이벤트 리스너 작성이 가능한 스윙처럼 될 것 이다. 이 기사는 JSF입문 기사정도로 보면 될 것이다. 이 기사는 JSF에서 가장 중요한 것 중에 하나인 이벤트-드리븐(event-driven) 방식에 초점을 맞추고 있다. 본 기사를 무리없이 이해하기 위해서는 서블릿, JSP, 자바빈즈, 커스텀 태그에 관한 선행 지식이 필요하다.

무엇보다도 JSF 애플리케이션은 서블릿/JSP 애플리케이션이다. 웹 디스크립터 파일, JSP 페이지, 커스텀 태그 라이브러리, 정적인 파일들을 가지고있다. 기존 것들과 다른점은 JSF 애플리케이션은 이벤트-드리븐 방식이라는 것이다. 이벤트 리스너 클래스를 사용함으로써 애플리케이션의 작동 방법을 결정할 수 있다. 다음과 같은 단계를 통해 JSF 애플리케이션을 작성할 수 있다.
  1. HTML를 캡슐화하는 JSF 컴포넌트를 사용하여 JSP 페이지를 작성한다.
  2. 사용자 입력과 컴포넌트 데이터의 값을 저장할 수 있는 자바빈즈 파일을 만든다.
  3. 사용자가 버튼을 클릭하거나 폼을 제출할 때 발생하는 이벤트를 처리할 수 있는 이벤트 리스너를 작성한다. JSF는 ActionEventValueChangedEvent 2가지를 지원한다. ActionEvent는 버튼을 클릭하거나 폼을 제출할 때 발생되며, ValueChangedEvent는 JSF 컴포넌트가 변경될 때 일어난다.

이제 JSF가 어떻게 동작하는지 자세히 알아보자.


*참고 도서
Mac OS X for Java Geeks
Will Iverson
상세설명보기
목차보기
샘플챕터보기




JSF 작동방법

JSP 페이지는 JSF 애플리케이션의 유저 인터페이스 역할을 한다. 각각의 페이지는 폼, 입력박스, 버튼과 같은 웹 컨트롤을 나타내는 JSF 컴포넌트를 포함하고 있다. 폼 내부에 입력박스가 포함 되듯이 컴포넌트들은 다른 컴포넌트 내에 속할 수 있다. 각각의 JSP는 컴포넌트 트리 구조로 표현되며 자바빈즈는 사용자 요청으로부터 데이터를 저장한다.

흥미로운 점은 사용자가 버튼을 클릭하거나 폼을 제출할 때마다 매번 이벤트가 발생한다는 것이다. 모든 이벤트에 의해 발생된 메시지는 HTTP를 통해 서버로 전달된다. 서버는 Faces 서블릿이 실행되고 있는 웹 컨테이너이다. javax.faces.webapp.FacesServlet 클래스인 Faces 서블릿은 모든 JSF 애플리케이션의 실행 엔진이다. 동일한 웹 컨테이너에서 작동하는 각각의 JSF 애플리케이션은 자신만의 Faces 서블릿을 가지고 있다. 다른 중요한 객체는 현재 요청과 관계된 모든 필수 정보를 캡슐화하고 있는 javax.faces.context.FacesContext이다.

애플리케이션의 이면에서 Faces 서블릿이 처리하는 과정은 복잡하지만 세부사항에 대해 모두 알 필요는 없다. 단지 Faces 서블릿이 이벤트를 발생시키는 웹 컨트롤을 포함하고있는 JSP 페이지의 컴포넌트 트리를 생성한다는 것만 알면 된다. Faces 서블릿은 애플리케이션내 모든 페이지에 접근할 수 있기 때문에 트리를 어떻게 구성 할 것인지 알고있다. 또한 Faces 서블릿은 Event 객체를 생성하거나 적절한 리스너에게 통보한다. 요청에 연결된 FacesContext 객체를 통해서 JSP 페이지의 컴포넌트 트리를 얻을 수 있다.

클라이언트 브라우저속 웹 컨트롤에 의해 발생하는 이벤트는 브라우저 종류, 요청 URL과 같은 HTTP 요청으로 캡슐화된다. 그러므로 Faces 서블릿 처리가 필요한 모든 요청은 바로 서블릿으로 전달되어야 한다. 그렇다면 어떻게 모든 HTTP 요청을 Faces 서블릿으로 처리하게 할 것인가? 특정한 URL 패턴을 Faces 서블릿으로 매핑하기 위해 배치 디스크립터에다가 servlet-mapping 요소를 기술하면 된다. 관례상 다음과 같이 /faces/* 패턴을 사용한다.
<!-- Faces Servlet -->
<servlet>
   <servlet-name>Faces Servlet</servlet-name>
   <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>

<!-- Faces Servlet Mapping -->
<servlet-mapping>
   <servlet-name>Faces Servlet</servlet-name>
   <url-pattern>/faces/*</url-pattern>
</servlet-mapping>

요청하는 URL은 반드시 <url-pattern>에 선언된 패턴을 포함하고있어야 한다. 설정하는 것은 별로 어렵지않으며 Faces 서블릿을 포함하고 있는 <servlet> 요소가 애플리케이션이 시작될 때 서블릿을 로딩 시키는 <load-on-startup> 요소를 가지고 있다는 것을 기억해두자.

컴포넌트에 의해 발생하는 모든 이벤트를 처리하기 위해 반드시 이벤트 리스너를 작성해야 하고 모든 컴포넌트를 등록해야 한다. 이는 컴포넌트를 나타내는 커스텀 태그 속 <action_listener> 요소를 사용하면 된다. 예를 들어 커맨드 버튼에 의해 발생하는 이벤트를 처리하기 위해 jsfApp.MyActionListener라는 리스너를 만들었다면 JSP 페이지 안에 다음과 같이 작성해야 한다.
<h:command_button id="submitButton" label="Add" commandName="submit" >
 <f:action_listener type="jsfApp.MyActionListener" />
</h:command_button>

액션 리스너는 반드시 javax.faces.event.ActionListener 인터페이스를 상속 받고 값 변경에 관한 리스너는 javax.faces.event.ValueChangedListener를 상속 받아야 한다. 이제 이벤트-드리븐 방식으로 JSF가 동작 하는 방법을 보기위해 간단한 애플리케이션을 작성해 보도록 하겠다.

간단한 JSF 애플리케이션
Source Code
예제 애플리케이션을 작성하기 위한 소스 코드를 다운받는다.



여기에서는 두 개의 숫자를 더하는 간단한 JSF 애플리케이션을 작성할 것이다. 애플리케이션을 실행시키기 위해 Tomcat 5와 JWSDP에 포함된 JSF v1.0 EA4가 사용된다(이것들은 JWSDP(Java Web Services Developer Pack) 1.2에 포함되어 있다). 애플리케이션은 다음과 같은 것들로 구성되어 있다.
  • adder.jsp, JSP 페이지
  • NumberBean, 데이터 저장소 자바빈즈
  • MyActionListener, 액션 리스너
  • web.xml, 배치 디스크립터

JSF 애플리케이션을 실행시키기 위해 JSF 관련 라이브러리를 포함하고있는 .jar 파일이 필요하다. 일단 JWSDP 1.2를 설치하면 jsf/lib 디렉토리 밑에 앞서 말한 파일을 찾을 수 있을 것이다. WEB-INF/lib 디렉토리 밑에 .jar 파일 복사한다. 다음은 복사할 .jar, .tld 파일들이다.
  • jsf-api.jar 파일은 Faces 서블릿과 javax.faces 패키지 내 관련 클래스들을 포함하고 있다.
  • jfs-ri.jar JSF 실행과 관련된 파일이다.
  • jstl_el.jar는 JSTL 표현 수식 문법을 처리한다.
  • standard.jar는 JSTL 태그들을 사용하거나 JFS 실행을 위한 참조 클래스에 의해 참고 된다.

덧붙여 JSF 애플리케이션은 아파치 자카르타의 일부인 다음과 같은 라이브러리를 필요로 한다. 이 라이브러리들은 본 기사와 함께 애플리케이션에 포함되어 있다.
  • commons-beanutils.jar 파일은 JavaBeans 컴포넌트 프로퍼티에 접근해서 정의하기 위한 유틸리티를 포함한다.
  • commons-digester.jar 파일은 Apache Common Digester 클래스들을 포함하고있는 라이브러리이다.
  • commons-logging.jar 파일은 일반적인 목적의 유연한 로깅 도구이다.

아래 소 주제에서는 예제의 각 부분에 대해 논의하고 있다. 마지막 소주제인 "애플리케이션 실행과 컴파일"에서는 JSF 애플리케이션이 어떻게 동작하는지 보여준다.

디렉토리 구조 만들기

디렉토리 구조를 만드는 것부터 예제 JSF 애플리케이션을 시작해보자. 톰캣에서는 webapps 디렉토리 밑이 될 것이다. [그림 1]은 myJSFApp 애플리케이션의 디렉토리 구조를 나타낸다.
[그림 1] JSF 애플리케이션 디렉토리 구조
배치 디스크립터 작성하기

다른 servlet/JSP 애플리케이션과 마찬가지로 예제는 아래 [리스트 1]과 같이 배치 디스크립터를 작성해야 한다.

[리스트 1] 배치 디스크립터 (web.xml 파일)
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
   "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
   "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
   <!-- Faces Servlet -->
   <servlet>
       <servlet-name>Faces Servlet</servlet-name>
       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
       <load-on-startup> 1 </load-on-startup>
   </servlet>

   <!-- Faces Servlet Mapping -->
   <servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>/faces/*</url-pattern>
   </servlet-mapping>
</web-app>

배치 디스크립터에는 두 부분으로 이루어져 있다. <servlet> 요소는 Faces 서블릿을 등록하고 <servlet-mapping> 요소는 URL에 포함된 /faces/ 패턴을 가지는 모든 요청을 Faces 서블릿으로 보낸다.

JSP 페이지 만들기

[리스트 2]와 같이 adder.jsp는 사용자 인터페이스를 제공한다.

[리스트 2] adder.jsp 페이지
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Add 2 numbers</title>
</head>
<body>
<jsp:useBean id="NumberBean" class="jsfApp.NumberBean" scope="session" />
<f:use_faces><br />
   <h:form id="addForm" formName="addForm" ><br />
       First Number: <br />
       <h:input_number id="firstNumber" valueRef="NumberBean.firstNumber" /><br />
       Second Number:
       <h:input_number id="secondNumber" valueRef="NumberBean.secondNumber" /><br />
       Result:
       <h:output_number id="output" valueRef="NumberBean.result"/><br>
       <h:command_button id="submitButton" label="Add" commandName="submit" >
           <f:action_listener type="jsfApp.MyActionListener" />
       </h:command_button>
   </h:form>
</f:use_faces>
</body>
</html>

htmlcore, 두개의 JSF 태그 라이브러리를 사용하기 위해 두개의 taglib directive를 선언해야 한다. 두개 라이브러리에 대한 태그 라이브러리 디스크립터는 jsf-ri.jar 파일에 포함되어 있으므로 걱정하지 않아도 된다. 태그 라이브러리에 대한 접두어는 각각 hf다.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<jsp:useBean> 액션 요소는 세션 범위를 가진 NumberBean 자바빈즈를 정의한다.
<jsp:useBean id="NumberBean" class="jsfApp.NumberBean" scope="session" />
JSF 컨트롤에 대해 살펴보면 JSF 컨트롤은 반드시 <f:use_faces> 안에 포함되어 있어야 한다.
<f:use_faces>
...
</f:use_faces>

<f:use_faces> 사이에 폼이 들어 갈 수 있다.
<h:form id="addForm" formName="addForm">
...
</h:form>

폼 내부에 두 개의 숫자 입력 창(input_number), 하나의 숫자 출력 창(output_numbers), 명령 버튼(command_button)이 있다.
First Number:
<h:input_number id="firstNumber" valueRef="NumberBean.firstNumber" /><br />
Second Number:
<h:input_number id="secondNumber" valueRef="NumberBean.secondNumber" /><br />
Result:
<h:output_number id="output" valueRef="NumberBean.result" /><br />
<h:command_button id="submitButton" label="Add" commandName="submit">
   <f:action_listener type="jsfApp.MyActionListener" />
</h:command_button>

명령버튼을 위한 액션 리스너에 주의를 기울이자. [그림 2]는 루트 요소가 생략된 JSP 페이지 컴포넌트 트리이다.
[그림 2] adder.jsp 페이지의 컴포넌트 트리
네 개의 자식 컴포넌트를 가지고 있는 폼이 메인 컴포넌트이다.

객체 모델 작성하기

예제를 위해 2개의 숫자와 그 결과값을 기억하고 있는 자바빈즈가 필요하다. [리스트 3]은 NumberBean 자바빈즈이다.

[리스트 3] NumberBean 자바빈즈
package jsfApp;
public class NumberBean {
   int firstNumber  = 0;
   int secondNumber = 0;

   public NumberBean () {
       System.out.println("Creating model object");
   }

   public void setFirstNumber(int number) {
       firstNumber = number;
       System.out.println("Set firstNumber " + number);
   }

   public int getFirstNumber() {
       System.out.println("get firstNumber " + firstNumber);
       return firstNumber;
   }

   public void setSecondNumber(int number) {
       secondNumber = number;
       System.out.println("Set secondNumber " + number);
   }

   public int getSecondNumber() {
       System.out.println("get secondNumber " + secondNumber);
       return secondNumber;
   }

   public int getResult() {
       System.out.println("get result " + (firstNumber + secondNumber));
       return firstNumber + secondNumber;
   }
}

액션 리스너 작성하기

명령버튼을 위한 액션 리스너는 JSF 애플리케이션의 가장 흥미로운 부분 중 하나이다. 이것은 이벤트가 실행되기 위해 리스너를 어떻게 일으키는지 나타낸다. 리스너는 단순히 콘솔 창에 메시지를 출력한다. 하지만 이벤트가 발생한 JSP 페이지의 컴포넌트 트리나 이벤트를 일으킨 컴포넌트의 정보를 나타내는 중요한 역할을 한다.

[리스트 4] 명령버튼을 위한 액션 리스너 (MyActionListener.java)
package jsfApp;

import java.util.Iterator;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.PhaseId;
import javax.faces.tree.Tree;

public class MyActionListener implements ActionListener {

   public PhaseId getPhaseId() {
       System.out.println("getPhaseId called");
       return PhaseId.APPLY_REQUEST_VALUES;
   }
 
   public void processAction(ActionEvent event) {
       System.out.println("processAction called");

       // the component that triggered the action event
       UIComponent component = event.getComponent();
       System.out.println("The id of the component that fired the action event: "
           + component.getComponentId());

       // the action command
       String actionCommand = event.getActionCommand();
       System.out.println("Action command: " + actionCommand);
 
       FacesContext facesContext = FacesContext.getCurrentInstance();
       Tree tree                 = facesContext.getTree();
       UIComponent root          = tree.getRoot();

       System.out.println("----------- Component Tree -------------");
       navigateComponentTree(root, 0);
       System.out.println("----------------------------------------");
   }
 
   private void navigateComponentTree(UIComponent component, int level) {
       Iterator children = component.getChildren();

       // indent
       for (int i=0; i<level; i++)
           System.out.print("  ");

       // print component id
       System.out.println(component.getComponentId());

       // navigate children
       while (children.hasNext()) {
           UIComponent child = (UIComponent) children.next();
           navigateComponentTree(child, level + 1);
       }
   }
}

애플리케이션 컴파일과 실행하기

애플리케이션을 컴파일하기 위해 myJSFApp/WEB-INF/classes 디렉토리로 이동한다. 윈도우에서는 아래와 같이 명령어를 타이핑해 넣으면 된다.
$ javac -classpath ../lib/jsf-api.jar;../lib/jsf-ri.jar; \
   ../../../../common/lib/servlet.jar jsfApp/*.java

lib 디렉토리의 라이브러리 파일과 servlet.jar 파일을 사용해야 한다는 것에 주의하자. 톰캣에서는 홈 디렉토리의 하위 common/lib에서 servlet.jar 파일을 찾을 수 있다.

리눅스나 유닉스를 사용하고 있다면 라이브러리 파일을 구분하는 세미콜론을 변경해 줘야 한다.
$ javac -classpath ../lib/jsf-api.jar:../lib/jsf-ri.jar: \
   ../../../../common/lib/servlet.jar jsfApp/*.java

톰캣을 실행시키고 다음과 같은 URL로 브라우저에서 접근한다.
http://localhost:8080/myJSFApp/faces/adder.jsp
JSP 페이지 이름 전에 /faces/ 패턴을 사용했다는 점에 주의하자. 브라우저에서는 아래 [그림 3]과 같은 결과를 보게 될 것이다.
[그림 3] 애플리케이션 실행
콘솔 창에 다음과 같은 메시지를 보게 될 것이다.
Model Object Created
get firstNumber 0
get secondNumber 0
get result 0
getPhaseId called

이제 입력 창에 2개의 숫자를 넣고 Add 버튼을 눌러보자. [그림 4]와 같이 브라우저는 덧셈의 결과를 보여줄 것이다.
[그림 4] 덧셈의 결과
콘솔창에 나타난 메시지가 중요하다.
get firstNumber 0
get secondNumber 0
processAction called
The id of the component that fired the action event: submitButton
Action command: submit
----------- Component Tree -------------
null
   addForm
       firstNumber
       secondNumber
       output
       submitButton
----------------------------------------
Set firstNumber 10
Set secondNumber 20
get firstNumber 10
get secondNumber 20
get result 30

결론

본 기사를 통해 여러분들은 JSF의 가장 중요한 특징에 대해 충분히 살펴보았을 것이다. 이벤트-드리븐 방식처럼 다른 서블릿/JSP 애플리케이션과 JSF 애플리케이션이 어떻게 다른지 잘 알게 되었을 것이다. 또한 하나의 JSP 페이지로 이루어진 아주 간단한 JSF 애플리케이션도 구축해 보았을 것이다. 더욱 중요한 사실은 액션 이벤트에 반응하는 액션 리스너를 작성했다는 사실이다.

실제 JSF 애플리케이션은 훨씬 복잡할 뿐만 아니라 종종 여러 개의 JSP 페이지를 가지고 있는 경우도 있다. 이런 경우에는 한 JSP 페이지에서 다른 JSP 페이지로 이동할 수 있어야 한다. 하지만 이에 관련된 내용은 본 기사의 범위를 넘어서는 것이므로 이에 대한 것은 다른 기사에서 논의할 생각이다.
부디 쿼니아완(Budi Kurniawan)은 인터넷과 객체지향 프로그래밍을 전문으로 하는 IT 컨설턴트로 마이크로소프트와 자바 기술 모두를 가르치고 있다.

댓글 없음:

댓글 쓰기

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...