Whiteship's Note


2장 컴포넌트 식별자와 클라이언트 식별자

JSF/JSF in Action : 2010.01.13 15:21


- UI 컴포넌트는 서버에서는 컴포넌트 트리로 살고 클라이언트쪽에서는 여러 형태의 뷰 표현체로 존재한다.
- 서버쪽에서는 컴포넌트 식별자로 컴포넌트를 참조한다.
- 클라이언트에서는 클라이언트 식별자를 사용해서 컴포넌트를 참조한다.
- 컴포넌트 식별자를 이용해서 클라이언트 식별자를 생성한다.
- 컴포넌트 식별자는 반드시 문자 또는 언더바(_)로 시작한다.
- 컴포넌트 식별자를 생략하면 JSF가 자동으로 만들어 준다.
- Naming container: 하위 컴포넌트들이 모두 유일한 식별자를 가지고 있는 컴포넌트.
- 예를 들어 HtmlDataTable, HtmlForm의 하위 컴포넌트트 식별자에는 이 컴포넌트의 식별자를 이용해서 클라이언트 식별자를 만든다.

top


2장 JSF 요청 처리 생명주기 - Phase 6. 응답 보여주기

JSF/JSF in Action : 2010.01.11 20:58


Render Response

- 최종적으로 사용자에게 응답을 보낸다.
- 뷰 상태를 저장하여 '뷰 복원하기' 단계에서 재사용할 수 있게 한다.
- 특정 뷰 기술에 종속적이지 않다.
- 컨트롤러의 인코딩 메서드 결과만을 뷰에서 사용할 수 있다.
- 컨트롤러의 인코딩 메서드 결과를 마크업을 만드는 애플리케이션에서 통합할 수 있다.
- 컨트롤러의 인코딩 메서드 결과를 정적인 템플릿에 통합할 수 있다.
- 컨트롤러의 디코딩 메서드 결과를 JSP 같은 동적 리소스에 통합할 수 있다.
- 각각의 컨트롤러 인코딩 과정 중에 변환기(Conveter)를 사용하여 컴포넌트의 값을 문자열로 변환한다.
- 이 단계가 JSF 요청 처리 생명주기의 마지막 단계다.
top


2장 JSF 요청 처리 생명주기 - Phase 5. 애플리케이션 호출하기

JSF/JSF in Action : 2010.01.08 21:49


Invoke Application

- 이 단계에서 등록되어 있는 리스너로 이벤트를 전파한다.
- '요청값 적용하기' 단계에서 만든 액션 이벤트를 여기서 전파한다.
- 컴포넌트에 등록된 액션 리스너 EL을 평가하여 백빈의 특정 메서드(액션 리스너 메서드)를 실행한다.
- 액션 리스너 메서드는 컴포넌트의 actionListener 속성으로 설정한다.
- 액션 리스너 메서드 실행이 끝나면 모든 컴포넌트에 자동으로 등록되는 기본 리스너를 실행한다.
- 기본 리스너는 컴포넌트에 등록되어 있는 액션 메서드를 호출한 뒤 해당 메서드의 논리적인 이름을 통해 랜더링할 뷰를 찾는다.
- 액션 메서드는 컴포넌트의 action 속성을 설정한다.
- 액션 메서드가 없는 경우 기본 액션 리스너가 현재뷰를 랜더링 한다.
- 액션 리스너 메서드나, 액션 메서드에서는 직접 응답을 랜더링 할 수도 있고, 애플리케이션 메시지를 추가할 수도 있고, 이벤트를 만들 수도 있고, 애플리케이션 로직을 실행할 수도 있고, 그냥 뷰 랜더링 단계로 건너뛸 수도 있다.
- 모든 리스너 실행이 끝나면 뷰를 랜더링 할 준비가 된 것이다.


top


2장 JSF 요청 처리 생명주기 - Phase 4. 모델 값 변경하기

JSF/JSF in Action : 2010.01.07 20:38


Update Model Values

- JSF EL 표현식을 통해서 모델 또는 백빈의 속성 값을 설정한다.

<h:inputText id="helloInput" value="#{helloBean.numControls}" 
             required="true"> 
... 
</h:inputText> 

1. helloBean이라는 키를 가지고 있는 빈을 찾아온다.
2. helloBean의 numControls 속성에 값을 해당 컴포넌트의 로컬 값으로 설정한다.

- 이 단계가 끝나면 관련 리스너에 이벤트를 보내고, 여태까지 그랬던것처럼 리스너에서는 응답을 직접 출력하거나  Render Response 단계로 건너뛸수도 있다.



top


2장 JSF 요청 처리 생명주기 - Phase 3. 검증하기

JSF/JSF in Action : 2010.01.07 20:26


Process Validation

- 이 단계에서 JSF는 컴포넌트 트리를 순회하면서 각각의 컴포넌트에게 자신의 값을 수용할 수 있는지 확인하도록 요청한다.
- '요청 값 적용하기' 단계에서 입력 컴포넌트들이 갱신되기 때문에 이 단게에서는 사용자가 입력한 최신값을 가지고 있다.
- 검증을 하기전에 변환기에 의해 값이 변환된다.
- 변환기와 검증기를 무사히 거치고 나면 해당 값을 서브밋하고 다음 단계로 넘어간다.
- 그렇치 않은 경우 '응답 보여주기' 단계로 넘어간다.

<h:inputText id="helloInput" value="#{helloBean.numControls}" 
             required="true"> 
<f:validateLongRange minimum="1" maximum="500"/> 
</h:inputText> 

1. required 속성이 true가 입력한 값이 있는지 검사한다.
2. 컨버터가 입력한 값을 int로 바꾼다.
3. validateLongRange 검증기가 값의 범위를 검증한다.

- submitted value가 변환과 검증이 끝나면 그 값을 기반으로 로컬 값을 세팅하는데 이때 value change event가 발생하고 그에 따른 적절한 리스너가 실행된다. 이 리스너들에 의해 직접 응답을 출력하거나 Render Repsponse 단계로 넘어갈 수 있다.
- 모든 submitted value가 유효하다면 다음 단계로 넘어간다.


top


2장 JSF 요청 처리 생명주기 - Phase 2. 요청 값 적용하기

JSF/JSF in Action : 2010.01.06 20:42


Apply Request Values

- 사용자 입력을 받는 컴포넌트들은 사용자가 입력한 원래 데이터를 나타내는 submitted value를 가지고 있다.
- 프레임워크가 요청의 매개변수를 기반으로 submitted value를 설정해준다.
- 이 과정을 decoding이라 부른다.
- 각 컴포넌트는 자신의 id와 자신을 감싸고 있는 컴포넌트의 id를 이용해서 client identifiter를 만든다.
- 이 단계에서 컴포넌트의 요청에 자신의 client id에 해당하는 매개변수가 있는지 확인하여 있다면 그 매개변수의 값으로 설정한다.
- 컴포넌트의 immediate 속성을 true로 설정하면 검증 단계에서 하지않고 그 즉시 검증을 실행한다.
- 이 단계에서 액션 이벤트를 만들 수도 있다. 만들어진 이벤트는 FacesContext에 추가되고 나중에 '애플리케이션 호출' 단계에서 사용된다.
- 이 단계가 끝난뒤 존재하는 이벤트를 관련 리스너로 전달한다.
- 모든 랜더러, 컴포넌트, 리스너는 생명주기를 단축시켜 이 단계를 건너뛰거나 바로 최종단계로 넘어갈 수도 있다.
- 또는 각각의 입력 컴포넌트가 적절하게 디코딩된 속성을 가지고 현재 요청에 기반한 최신 값을 가지고 있을 것이다.



top


2장 JSF 요청 처리 생명주기 - Phase 1. 뷰 복원하기

JSF/JSF in Action : 2010.01.05 21:37


Restore View

- 뷰는 특정 페이지를 구성하는 모든 컴포넌트를 나타낸다. 
- 브라우저 위에 히든 필드를 이용해서 클라이언트에 저장될 수도 있고, 세션을 이용해서 서버에 저장될 수도 있다. 서버에 저장되는게 기본값이다.
- 각각의 뷰는 컴포넌트 트리로 구성되며 고유의 식별자를 가지고 있다. 그 식별자를 "뷰 식별자"라고 한다. View identifier
- 뷰 식별자는 요청에서 특수한 경로에 해당한다. 서블릿 경로 이후의 값이 뷰 식별자가 된다. 예) http://spirngsprout.org/study/35.do 에서 /study/35.do
- 폼을 보여준 페이지와 동일한 페이지로 다시 포스팅 요청을 하는 것을 포스트백(postback)이라고 한다.
- 기존의 프레임워크와 차이: "이 액션을 수행하라." VS "사용자가 이 명령을 실행했다.", "사용자가 이 컨트롤의 값을 변경했다."
- 이때 중요한 것은 최종 페이지가 어떤 것이며, 그곳에서 어떤 이벤트가 발생했는냐 이다.
- 현재 뷰를 찾아서 사용자가 입력한 내용을 거기에 적용하는 것이 이 단계의 주요 작업 중 하나이다. 
- 세션에서 뷰를 찾아 본 뒤에 뷰가 있으면 그것을 가져와서 FacesContext에 저장하고, 없으면 새로 요청한 뷰 식별자를 기반으로 새로운 뷰를 만들어서 FacesContext에 저장한다.
- 뷰를 복원하는 단계에서 컴포넌트의 값은 물론이고 그와 관련된 이벤트 리스너, 검증기, 변환기도 복원됐는지 확인한다.
- 백빈의 속성에 바인딩되어 있는 컴포넌트의 경우에는 이 단계에서 백빈의 속성과 컴포넌트가 동기화된다.
- 브라우저가 보낸 HTTP 요청의 헤더를 통해 뷰에 사용할 언어를 설정한다.
- 요청이 포스트백일 경우에는 다음 단계로 넘어가고, 그외의 경우에는 처리할 사용자 입력이 없기 때문에 응답 랜더링(Render Response) 단계로 넘어간다.

 
top


2장 JSF 기본 - 요청 처리 생명주기

JSF/JSF in Action : 2010.01.04 18:11


뷰 복원하기(restore view) - 선택한 뷰에대한 컴포넌트 트리를 찾거나 만드는 단계.  HtmlCommandButton 같은 컴포넌트에서 액션 이벤트를 발생 시킨다.

요청 값 적용하기(Apply Request Values) - 컴포넌트의 값들을 요청에 담겨있는 값과 동일하게 맞춘다. 이 과정에서 변환기(Converter)가 사용될 수 있으며,  변환시 에러가 발생하면 변환 에러를 추가한다.

검증 수행(Process Validations) - 모든 컴포넌트가 각자 검증을 수행하도록 지시한다. 검증 에러 메시지가 보고될 수 있다.

모델 값 갱신(Update Model Values) - 컴포넌트에 연결된 백빈(backing bean) 또는 모델 객체의 값을 갱신한다.  변환 에러가 보고 될 수 있다.

애플리케이션 호출(Invoke Application) - 등록된 액션 리스너를 호출한다. 

응답 랜더링(Render Response) - 선택한 뷰를 보여준다.



top


2장 JSF 기본 - 주요 구성 요소

JSF/JSF in Action : 2009.12.31 17:06


UI 컴포넌트(컨트롤 또는 그냥 컴포넌트라고 부르기도 함) - 상태를 유지하는 객체다(stateful object). UI 컴포넌트는 자바빈으로 프로퍼티, 메서드, 이벤트를 가지고 있다. UI 컴포넌트는 뷰를 구성하며, 보통 컴포넌트 트리가 한 페이지를 보여준다.

랜더러(renderer) - UI 컴포넌트를 보여주며 사용자 입력값을 컴포넌트의 값으로 변환하는 책임을 가지고 있다. 랜더러는 한개 또는 여러개의 UI 컴포넌트를 다룰 수 있으며 한개의 UI 컴포넌트가 여러개의 다른 랜더러에 의해 처리될 수 있다.

검증기(validator) - 사용자가 입력한 값을 수용할 수 있는지 검사한다. 한 개의 UI 컴포넌트가 여러개의 검증기에 의해 처리될 수 있다.

백빈(backing bean) - UI 컴포넌트에서 값을 수집하며 이벤트 리스너 메소드를 구현하는 자바빈이다. UI 컴포넌트에 대한 레퍼런스도 가질 수 있다.

변환기(converter) - 컴포넌트의 값을 화변에 보여줄 문자열로 변환하고 그 반대로도 변환해준다. 한 개의 UI 컴포넌트에는 한 개의 컨버터만 적용된다.

이벤트와 리스너(events and listners) - JSF는 자바빈 이벤트/리스너 모델을 사용한다. UI 컴포넌트는 이벤트를 발생시키며 리스너를 등록할 수 있다.

메시지(message) - 사용자에게 다시 보여줄 정보. 백빈, 검증기, 컨버터 등에서 사용자에게 보여줄 메시지나 에러 메시지를 생서앟ㄹ 수 있다.

네비게이션(nvigation) - 어떤 페이지에서 다른 페이지로 이동하는 기능. JSF는 특화된 이벤트 리스너와 연동된 강력한 네비게이션 시스템을 가지고 있다.




top


1장 JSF 소개 - Introducing JavaServer Faces

JSF/JSF in Action : 2009.12.31 12:28


1.1 JSF는 무엇인가?

JSF는 RAD 툴의 4계층 중에 셋으로 컴포넌트 아키텍처, UI 위젯 표준 집합, 애플리케이션 인프라를 정의한 것이다. JSF 컴포넌트 아키텍처는 UI 위젯을 만드는 일반적인 방법을 정의한다.

1.2 기반 기술

HTTP - 텍스트 헤더 기반의 간단한 프로토콜로 클라이언트가 요청을 서버는 요청한 문서를 브라우저로 보내준다. HTTP는 "stateless" 프로토콜이다. 클라이언트의 여러 요청에 대한 정보를 기억하지 못한다.

Servlet - HTTP는 정적인 페이지를 보여줄 때 매우 좋다. 동적인 페이지를 만들려면 코딩이 필요하다. HTTP가 간단하긴 하지만 헤더를 파싱해서 뭘 원하는지 알아야 하고 다시 또 적절한 형식으로 헤더를 만들어야 하는 불편한 코딩을 객체 지향적으로 할 수 있게 서블릿 API를 제공한다. 즉 HTTP 요청과 응답을 객체로 캡슐화하고 Servlet에서 요청을 처리한다.

Portlet - 패스

JavaBean - 강력한 이벤트 모델을 가지고 있다.

1.4 컴포넌트

JSF는 UI 컴포넌트 또는 컨트롤을 제공한다.

1.5 Hello, world!


인텔리에서 해봤는데 JSF 개발하는데 아무 지장 없을 듯.
faces-config.xml 파일 작성할 때 자동완성 지원 귿.

top


JSF의 universal EL

JSF/exercise : 2008.09.20 11:21


참조 : http://www.ibm.com/developerworks/edu/j-dw-java-jsf1-i.html

JSP의 EL은 JavaBean 스펙에 따른 게터, 세터만 사용하는데 비해, JSF의 universal EL은 다른 메소드 호출도 됩니다.

<table>
<tr>
<td><h:outputLabel value="First Number" for="firstNumber" /></td>
<td><h:inputText id="firstNumber"
value="#{calculator.firstNumber}" required="true" /></td>
<td><h:message for="firstNumber" /></td>
</tr>
<tr>
<td><h:outputLabel value="Second Number" for="secondNumber" />
</td>
<td><h:inputText id="secondNumber"
value="#{calculator.secondNumber}" required="true" /></td>
<td><h:message for="secondNumber" /></td>
</tr>
</table>

굵게 표시한 부분이 universal EL인데, 뒷단 빈의 속성 값과 연결해 줍니다.

<div>
<h:commandButton action="#{calculator.add}" value="Add" />
<h:commandButton action="#{calculator.multiply}" value="Multiply" />
<h:commandButton action="#{calculator.clear}" value="Clear" immediate="true"/>
</div>

그리고 위 코드에 있는 EL은 add(), multyply() 등의 메소드를 호출해줍니다. 모든 액션 메소드를 호출하기 전에 폼 검증 단계를 거치는데, immediate="true"를 사용하면 그 과정을 생략하고 바로 메소드를 호출합니다.

꺄오.. JSF 괜찮은거 같은데~

'JSF > exercise' 카테고리의 다른 글

JSF의 universal EL  (0) 2008.09.20
초간단 JSF 예제 돌리기 성공  (0) 2008.09.16
JSF 기초 2  (0) 2008.07.26
JSF 기초 1  (0) 2008.07.24
top


Spring Faces 소개하기, 파트 1

JSF/article : 2008.09.18 10:15


참조, 요약, 번역: http://www.jsfcentral.com/articles/intro_spring_faces_1.html

JavaServer Faces와 스프링의 연동을 돕는 Spring Faces라는 모듈이 있는데, 이 모듈을 Spring Web Flow 2에 도입했다. 본 기사는 Spring Faces에 대한 첫 번째 기사로, JSF 기준과 Spring 기준으로 각각 두 프레임워크를 통합하는 방법을 살펴볼것이다.

스프링은 예전부터 기본적인 JSF 연동을 자체 웹 모듈에서 지원해왔다. 기존 JSF 웹 계층과 스프링이 관리하는 비즈니스 계층 사이를 연결해주는 형태였다. 이 방법을 'JSF-기준 연동'이라고 한다. 이 방법으로도 잘 사용해왔지만, 스프링 웹 팀은 그 둘 사이에 뭔가 잘 안 맞는 것.. 너무 많은 구성물과 너무 많은 개념적인 오버헤드가 생겨서 관리하기 쉽지 않다는 것을 느꼈다.

스프링 Faces는 시야를 바꿔서 통합에 대한 접근 방법을 전환했다. 즉 스프링 기반 웹 애플리케이션에 JSF의 장점을 끌어오는 것을 목표로 했다. 이것은 '스프링-기준 연동'이라는 접근 방법으로 JSF UI 컴포넌트 모델을 사용하여 웹 애플리케이션 개발을 하는 것이다. 본 기사에서는 이 접근 방법의 장점과 왜 이것을 JSF과 스프링 개발자에게 권하는지 살펴보자.

JSF와 스프링 - 완벽한 조화인가?

JSF는 그 자체로 rich 웹 애플리케이션을 개발할 때 매우 많은 장점을 제공한다. 특히 복잡한 UI를 가지고 상태를 유지하는(stateful) 프로그래밍 모델을 필요한 곳에서 말이다. 이 UI 컴포넌트 모델들은 견고하며 강력하다. 제대로 적용하면, 웹 브라우져서에서 복잡한 컨트롤을 만들어야 하는 복잡도를 낮출 수 있다. 풍족한 UI를 선언적으로 만들 수 있고, 통합 EL을 사용하여 개별 컨트롤을 직접 도메인 모델에 유연하게 바인딩할 수 있기 때문에, 보다 빠르게 복잡한 웹 UI를 개발할 수 있다. 이 모든 것을 간단한 POJO 프로그래밍 모델로 달성할 수 있다.

스프링을 JSF를 앞단에 둔 애플리케이션의 도메인 계층에 적용하는 것이 자연스럽고 이상적이다. POJO 프로그래밍 모델을 이용할 수 있기 때문이다. JSF의 풍부하고 잘 정의된 확장 포인트를 사용하면, 스프링이 관리하는 비즈니스 계층을 JSF 앞단에 간단하게 연결할 수 있다.

EL을 사용한 JSF-기준 연동

JSF-기준 연동은, JSF 컨트롤러 모델이 애플리케이션을 주고하고 JSF가 관리하는 빈을 스프링 비즈니스 계층과 연결한다. 스프링은 몇몇 연동 클래스를 제공하여 faces-config.xml에서 스프링 빈을 참조할 수 있게 하는 것이다. 가장 흔한 방법으로는 DelegatingVariableResolver 를 사용하면, EL을 사용하여 스프링 빈을 참조할 수 있다. EL을 사용해서 스프링 빈을 JSF가 관리하는 빈에 주입하는 것이다. 예를 들어, WebApplicationContext에 다음과 같은 빈이 있다고 치자.

<bean id="mySpringService" class="com.foo.MySpringServiceImpl">

EL을 사용해서 faces-config.xml에 정의한 <managed-bean>에서 이 빈을 JSF가 관리하는 빈에 주입할 수 있다.

<managed-bean>
  <managed-bean-name>myJsfManagedBean</managed-bean-name>
  <managed-bean-class>com.foo.MyJsfManagedBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
    <property-name>mySpringService</property-name>
    <value>#{mySpringService}</value>
  </managed-property>
</managed-bean>

즉 이렇게 하면, 스프링이 관리하는 싱글톤 서비스를 JSF가 매번 생성하는 객체에 주입할 수 있다. 스프링 2.0부터, request와 session 스콥을 사용할 수 있기 때문에, JSF 관리하는 빈을 모두 없애고, EL로 스프링 컨트롤러를 사용할 수도 있다.

JSF-기준 접근방법으로 충분한가?


스프링의 간단한 JSF 연동 클래스로, 부드럽게 스프링이 관리하는 서비스를 JSF 기반 앞 단과 통합할 수 있었다. 하지만, 이것으로 충분한가? 나는 그렇지 않다고 확신한다. 이 방법은 JSF 컨트롤러 모델을 사용하고 있는데, 여기에 몇 가지 단점들이 존재한다.
  • 순수-MVC 접근방법에 따라, 모든 것이 뷰를 렌더링하는것으로 주도된다. 랜더링 전에 모델을 초기화 하는 편리한 포인트가 없다.
  • 성가신 네비게이션 모델은 여러분이 실수를 해도 거의 피드백이 없다.(오타가 있어도 말이다.) 그리고 규칙이 추가되거나 변경될 때마다 애플리케이션을 다시 시작해야 한다.
  • 검증 모델이 충분하지 않다. 서버쪽에서의 필드 수준 검증을 중심으로 하고 있다. 컴포넌트 트리를 순회하지 않고도 클라이언트쪽 필드 검증과 서버쪽 모델 검증을 수행할 수 있는 편리한 방법이 필요하다.
  • URL 맵핑이 좀 유연하지 않고, 리다이렉트 지원이 충분치 않다.
  • request와 session 사이의 보다 세밀한 스콥프가 필요하다. 특히 페이지 내에서 여러번 이벤트를 발생시키는 Ajax 기반 뷰의 경우에 그렇다.
  • 예외 처리 기능이 매우 제한적이다. 특히 뷰 랜더링할 때 그렇다.
  • 컨트롤러 로직이 JSF 빈으로 분산되어 있다. 단위 테스트하기가 힘들고 변경할 때마다 애플리케이션을 다시 시작해야 한다.
JSF의 UI 컴포넌트 모델은 견고하다. 그 행위 들은 잘 정의되어 있는 JSF 컴포넌트 라이프사이클 경계 내에서 실행되고 캡슐화되어 있다. 하지만 컨트롤러 모델은 다소 제한적이다. 특히 스프링, 스프링 MVC, 스프링 Web Flow의 유연함과 막강함을 사용하고자 하는 사람들에게 더 제한적으로 느껴진다. 또한 이 접근 방법은 개념적인 오버헤드를 발생시킨다. faces-config.xml, JSF가 관리하는 빈, JSF 뷰 템플릿 그리고 스프링 기반 비즈니스 계층의 모든 컴포넌트를 계속해서 관리해야 한다.

처음에는, 스프링 Web Flow 1.x도 JSF-기준 방법으로 연동을 시도했었다. 하지만, 이 방법이 너무 제한적었다. 여전히 많은 JSF 구성요소를 사용했고 제대로 동작하려면 faces-config.xml에 뭔가를 추가해야 했다. agile이라고 할 수 없었다.

만약에 JSF의 UI 컴포넌트 모델을 스프링 환경에 연동할 수 있으면 어떨까 고민을 했다. 스프링 프로그래밍 모델을 사용하고 보다 스프링을 기준으로 한 접근 방법으로 말이다. 만약 스프링이 전체 요청을 관리하면, 여러분은 보다 편리하게 전체 애프리케이션 설정을 관리할 수 있고 편하게 프로그래밍을 할 수 있을 것이다. 또한 스프링 MVC의 유연한 라우팅 기반 시설과 스프링 Web Flow 2.x의 agile statefule 컨트롤러 모델을 사용할 수 있겠다. 이는 stateful JSF 뷰와 잘 어울린다. 바로 이러한 동기로 인해 스프링 Faces가 생겼고 스프링 포트폴리오에 추가됐다.

스프링 Faces의 스프링-기준 JSF


스프링 Faces는 JSF와 스프링 연동에 있어서 훨씬 규범적인(prescriptive)-미리 무언가를 예상하고 마련해 두는- 접근방법을 채택했다. JSF의 막강함은 그가 제공하는 여러 확장 포인트인데, 스프링 Faces는 이 모든 장점을 수용하여 JSF를 보다 자연스럽게 스프링에 녹아들게 했다. 환경에 대해 몇가지 가정을 세웠다. 일단 스프링 MVC DispatcherServlet을 사용하여 요청 처리를 라우팅하고, 스프링 Web Flow 2.x의 기초 컨트롤러 모델을 사용한다. 또, 스프링 JavaScript 모듈을 사용하여 경량 JSF Ajax 컴포넌트를 제공한다고 가정했다. (스프링 Faces는 어떠한 JSF 컴포넌트 라이브러리도 사용할 수 있도록 설계했기 대문에, 이들 컴포넌트는 전부 옵션이기 필수가 아니다.)

사용자 삽입 이미지
그림 1. 스프링 Faces 아키텍처에 대한 하이 레벨 뷰

본 기사 남은 부분에서, 스프링 Web Flow 2 배포에 포함되어 있는 Spring Travel에 스프링 Faces를 적용해볼 것이다.

전체 스프링 Faces 예제 소스는 /project/spring-webflow-samples/booking-faces/에 있다.

이미 동작하고 있는 예제가 필요하다면, 여기를 클릭하라.

스프링 Faces 예제의 구조

먼저 살펴볼 것은  /src/main/webapp/WEB-INF/faces-config.xml 다. 이 안에 설정한 것은 오직 FaceletViewHandler 뿐이고, 스프링 Faces가 보다 미리 마련해두는 방식을 채택했기 때문에, 모든 JSF 연동에 필요한 구성물들은 자동으로 클래스패스에 있는 spring-faces.jar에 포함되어 있다.

또한 다음 파일들을 살펴보고 라우팅에 대한 기본 설정에 익숙해져야 할 것이다.

  • /src/main/webapp/WEB-INF/web.xml
  • /src/main/webapp/WEB-INF/config/webmvc-config.xml
  • /src/main/webapp/WEB-INF/config/webflow-config.xml
이 설정에 대해선느 스프링 Web Flow 2 레퍼런스 가이드에 나와있으니 자세히 설명하진 않겠다. 다만 이 설정의 결과만 설명하겠다.

/spring 이하의 모든 요청은 스프링 DispatcherServlet이 처리하도록 되어 있다. DispatcherServlet은 나머지 URL을 보고 stateless JSF 뷰를 보여줘야 하는지, SWF를 사용해서 stateful JSF 뷰를 보여줘야 하는지 판단하다.

예를 들어,

/spring/intro -> renders the stateless Facelets template at /WEB-INF/intro.xhtml

/spring/main -> hands control over to the flow defined at /WEB-INF/flows/main/main.xml

/spring/booking?hotelId=5 -> hands control over to the flow defined at /WEB-INF/flows/booking/booking.xml, passing the hotelId parameter as input to the flow

그림 2에 있는 /flows 디렉토리 구조를 보면, 스프링 Faces 컨틀롤러와 뷰 구성물을 논리적으로 재사용 가능한 모듈로 조직화 하도록 한 것을 볼 수 있다.

사용자 삽입 이미지
그림 2. 모듈들의 논리적인 조직화

스프링 Web Flow와 Agile JSF 컨트롤러

스프링 Faces의 핵심은 스프링 Web Flow 연동에서 온다. 그리고 고수준의 flow DSL을 사용할 수 있으며, 이것으로 정확하고 엘레강트한 구조로 이벤트 처리, 모델 조화, 뷰 네비게이션을 제공할 수 있다. 플로우 정의는 동적이며 애플리케이션 재실행 없이 핫-릴로딩이 가능하다. 웹 컨테이너에 배포할 필요가 없기 때문에 완전한 단위 테스트도 가능하다. 보다 기민한 JSF 개발이 가능하다. 세밀한 스콥(flash, view, flow 스콥)을 사용하여 여러 요청을 넘나들며 도메인 모델을 직접 사용할 수 있다. 따라서 여러분은 도메인 모델에 보다 많은 가치를 부여하는데 힘을 쓸 수 있고, 모델을 매 요청에 저장해두고 가져오는 등의 기반 시설에 대해 걱정하는 시간을 줄일 수 있다. 이런 stateful 특징은 풍부한 JSF 뷰와 잘 어울리며 여러 이벤트로 뷰가 자주 변경되는 상황에 적당하다.

스프링 Faces를 사용하면, JSF postback 라이프사이클은 SWF 제어 하에 실행된다.
PhaseListener를 통해 JSF 라이프사이클에 플로우를 실행하는 SWF 1.x의 제한적인 연동에 비하면, 스프링 Faces JSF 확장 포인트는 SWF 구조를 기반으로 만들었다. 이로인한 아키텍처 적인 장점은 다음과 같다.

  • JSF 뷰는 SWF의 자동 POST + Redirect + GET 행위와 자연스럽게 동작하며, step-behind-URL을 없앴고, 일반적인 JSF 애플리케이션에 있던 브라우저 리프래시 경고 문제를 제거했다.
  • FacesMessage는 스프링의 MessageContext로 바인딩되어 스프링의 강력한 i18n 기능을 사용할 수 있다. 메시지들은 flash 스콥으로 저장되며 따라서 리다이렉트를 해도 보존된다.
  • 컴포넌트 트리 상태는 자동적으로 플로우 실행 상태와 동기화한다. pluggable storage 매키니즘을 적용하여 다양한 클러스터링 시나리오를 다룰 수 있다.
끝. 다음 기사에는 예제를 좀 더 자세히 살펴보겠다.

저자에 대하여

Jeremy Grelle는 SpringSource의 수석 소프트웨어 엔지니어이며 스프링 Faces 프로젝트 기술 리더다.


'JSF > article' 카테고리의 다른 글

Spring Faces 소개하기, 파트 1  (2) 2008.09.18
JavaServer Faces 시작하기 Part 1 Building basic applications  (0) 2008.09.15
top


초간단 JSF 예제 돌리기 성공

JSF/exercise : 2008.09.16 22:46


꺄오~~ 처음으로 돌려보는 JSF 예제 기념샷.

사용자 삽입 이미지
사용자 삽입 이미지

'JSF > exercise' 카테고리의 다른 글

JSF의 universal EL  (0) 2008.09.20
초간단 JSF 예제 돌리기 성공  (0) 2008.09.16
JSF 기초 2  (0) 2008.07.26
JSF 기초 1  (0) 2008.07.24
top

TAG JSF

JavaServer Faces 시작하기 Part 1 Building basic applications

JSF/article : 2008.09.15 17:38


참조 : http://www.ibm.com/developerworks/edu/j-dw-java-jsf1-i.html

JSF가 제공하는 장점
• Clean separation of behavior and presentation
• Component-level control over statefulness
• Events easily tied to server-side code
• Leverages familiar UI-component and Web-tier concepts
• Offers multiple, standardized vendor implementations
• Excellent IDE support

JSF 애플리케이션 구성 요소
• JavaBeans for managing application state and behavior
• Stateful GUI components
• Event-driven development (via listeners as in traditional GUI
development)
• Pages that represent Model-View-Controller (MVC)-style views; pages
reference view roots via the JSF component tree

JSF 구성요소
• An event-publishing model
• A lightweight inversion-of-control (IoC) container
• Components for just about every other common GUI feature, including
(but not limited to):
  • Pluggable rendering
  • Server-side validation
  • Data conversion
  • Page-navigation management
- 이 중에서 IoC 컨테이너를 스프링으로 대체할 수 있음.(Part 2에서 살펴볼 예정)

JSF와 JSP 기술
• JSF 애플리케이션 UI는 JSP로 구성되어 있고, JSP에 JSF 커스텀 캐그를 사용해서 UI 컴포넌트를 랜더링한다. 여기에 이벤트 핸들러 등록하고, 검증기(Validator) 사용하고, 데이타 컨버터(Converter) 사용하는 거임.
• 사실 JSF는 JSP에 전혀 기술적으로 묶여있지 않다. JSP 페이지와는 전혀 다른 라이프사이클 가지고 있다.
• JSF 컴포넌트 상태는 단순히 JSP 페이지를 바꾼다고 바뀌는게 아니라, 컨트롤러에 바꾸며, 이미 기존 컴폰넌트가 존재할 때는 그 녀석의 상태를 화면에 뿌린다.
• 자바코드보다 EL을 많이 쓸 것이다.

JSF와 MVC
• HTML -> 모델 1 -> 모델 2
• 모델2 아키텍처는 MVC를 웹 애플리케이션에 맞게 정화한(watered-down) 것.
• 모델2
  • 컨트롤러 servlet(or 액션)
 • 뷰 JSP
• OO라기 보단 절차적에 가까웠기에 스트럿츠는 심지어 자신조차 스트럿츠 이길 원치 않았다. 자기 코드 기반 버리고 웹워크 위에 스프럿츠2를 만들었어.
• 그 보다는 덜 절차적인 스프링 MVC랑 웹어크도 있지만, 스트럿츠가 대세인데다가, 이 둘도 JSF가 제공하는 Statefull 컴포넌트는 제공하지 않는다.
• 모델 2 프레임워크의 진짜 이슈는 이벤트 모델이 너무 단순하고 stateful GUI 컴포넌트가 없다는 것이다. 그래서 너무 많은 작업을 개발자가 도맡아야해.

풍족한(richer) MVC 환경
• JSF는 모델 2 보다 훨씬 풍족한 MVC 환경을 제공한다.
• 모델 2 보다 더 진짜 MVC 프로그래밍에 가깝다.
• 모델 2 프레임워크 보다 훨씬 상세한(fine-grained), 이벤트 기반의 GUI를 제공한다.
  • request 받았다 VS 버튼 눌렀다, 아이템 선택했다, 트리 노드 열었다.
  • 이로 인해 HTTP에 너무 묶이지 않고, 개발자의 수고를 덜 수 있다.
• 표현 로직과 비즈니스 로직을 컨트롤러에서 빼내고, JSP 페이지에서 비즈니스 로직을 빼낸다.(이건 JSF를 쓰더라도 개발자가 어떻게 구현하느냐에 따라 달린거 아닐라나...)
• 간단한 컨트롤러는 JSF에 전혀 묶이지도 않아서 테스트 하기도 쉽다.(스프링 2.5 컨트롤러도 테스트는 괜찮아 졌는데.. 그 보다 좋을까 어떨까?)

JSF의 MVC 구현체 자세히 살펴보기
• 맵핑하는 빈들을 통해 뷰와 모델을 연결한다.
• 저자는 JSF에 묶이는 빈(컨트롤러)과 그렇지 않은 빈(모델)을 따로 맵핑한다.
• JSP와 달리 JSF 뷰는 Stateful 컴포넌트 모델이다.
• JSF 뷰는 두 가지로 구성되어 있다.
  • View root: UI 상태를 가지고 있는 UI 컴포넌트 집합
  • JSP 페이지: UI 컴포넌트를 JSP 페이지에 바인딩하고 뒷단의 빈 속성(혹은 속성의 속성)을 컴포넌트 필드로 바인딩한다.

이후부턴 예제... 예제는 스크린캐스팅으로 10월 첫주에 올라갑니다.

'JSF > article' 카테고리의 다른 글

Spring Faces 소개하기, 파트 1  (2) 2008.09.18
JavaServer Faces 시작하기 Part 1 Building basic applications  (0) 2008.09.15
top

TAG JSF

JSF 기초 2

JSF/exercise : 2008.07.26 22:02


참조 : JSF for nonbelievers: The JSF application lifecycle

JSF 라이프사이클

사용자 삽입 이미지
1. Restore view

요청이 FacesSevlet으로 전달되면, 컨트롤러는 요청을 분석해서 뷰 ID를 뽑아낸다. (JSP 페이지 이름을 바탕으로)

JSF 프레임워크 컨트롤러는 뷰 ID를 사용해서 현재 뷰에서 사용할 컴포넌트들을 가져온다. 뷰가 없으면, 새로 만들어서 사용한다. 만약에 뷰가 이미 있으면, 그것을 사용한다. 뷰에는 모든 GUI 컴포넌트들이 있다.

이 단계에서는 세 가지 경우가 있는데 각각 new view, initial view, postback 이다.

new view: 이 경우에 JSF는 Faces 페이지 뷰를 만들고 이벤트 핸들러와 벨리데이터를 컴포넌트에 연결시킨다. 그렇게 만들어진 뷰는 FacesServlet 객체에 저장된다.

initial view: 이 경우에는 비어있는 뷰를 만든다. 비어있는 뷰는 사용자가 이벤트를 발생시켰을 때 만들어지고, 이 다음 단계는 render response로 넘어간다.

postback: 이미 만들어둔 페이지를 다시 보여줄 때가 postback이다. 이 경우에 JSF는 기존에 존재하던 뷰의 상태 정보를 유지한채 보여준다. 이 다음 단계는 apply request values가 된다.(원래 그 단계인데, 뭘 새삼 언급해주는거지;;)

2. Apply request values

이 단계의 목적은 각각이 컴포넌트들이 현재 상태를 가져오는 것이다. 컴포넌트들은 반드시 그 값들을 사용해서 FacesContext 객체에서 가져오거나 새로 만들어진다. 컴포넌트 값들은 보통 요청 파라메터에서 가져오는데, 쿠키나 헤더에서 가져올 수도 있다.

컴포넌트의 immediate event handling 속성이 true 가 아닐 경우: 값의 타입을 객체의 속성 타입으로 변환하는 작업을 한다. 이 때 만약 캐스팅 시에 에러가 나면, response render 단계에서 validation error랑 같이 보여준다.

컴포넌트의 immediate event handling 속성이 true 일 경우: 타입 변환 + 검증작업까지 한다. 그리고 변환된 값을 컴포넌트에 저장한다. 만약 값 변환이나 값 검증이 실패하면, 에러 메시지를 생성해서 FacesContext에 있는 큐에 넣는다. 그럼 다른 검증 에러들과 함께 response render 단계에서 보여준다.

3. Process Validation

사용자가 입력한 값이 검증 룰에 비교해봤을 때 적당한지를 살펴본다. 적절치 않을 때는 FacesContext에 에러 메시지를 추가하고 컴포넌트를 invalid 상태로 표시한다.

만약 컴포넌트가 invalid로 표시되어 있다면: JSF는 바로 render response 단계로 넘어간다.

만약 invalid 컴포넌트가 아니면: update model values 단계로 간다.

4. Update model values

서버쪽에 있는 모델이 가진 속성 값들을 수정한다. 이 단계는 항상 validation 단계 다음에 오기 때문에 빈 속성값의 유효함을 보장받는다.(물론 폼 필드 수준일 뿐, 비즈니스 룰 수준에서는 유효하지 않을 수도 있다.)

5. Invoke application

JSF 컨트롤러는 폼 처리를 할 애플리케이션을 호출한다. 컴포넌트 값이 그에 따라 바뀔 것이고, 검증될 것이고(여기선 비즈니스 룰 수준의 검증을 말하는 거겠죠), 모델 객체에 반영이 될 것이다. 즉 비즈니스 로직을 수행한다.

이 단계에서, 다음에 보여줄 논리적인 뷰를 명시할 수 있다. 간단하게 폼 처리의 성공 여부를 반환하면 된다. 네비게이션 룰은 faces-config.xml에 정의한다. 네비게이션이 이루어지면, 최종 단계로 이동한다.

6.  Render response

모든 컴포넌트의 현재 상태를 화면에 보여준다. 그림2는 객체 상태 다이어그램으로 JSF 라이프사이클 6 단계를 보여주고 있다.

사용자 삽입 이미지

'JSF > exercise' 카테고리의 다른 글

JSF의 universal EL  (0) 2008.09.20
초간단 JSF 예제 돌리기 성공  (0) 2008.09.16
JSF 기초 2  (0) 2008.07.26
JSF 기초 1  (0) 2008.07.24
top


JSF 기초 1

JSF/exercise : 2008.07.24 23:26


참조 : JSF for nonbelievers: Clearing the FUD about JSF

기본 아키텍처

사용자 삽입 이미지

JSF 예제

1. web.xml 과 faces-config.xml 설정하기

1-1. web.xml

<!-- 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>/calc/*</url-pattern>
</servlet-mapping>

이건 뭐 별 설명이 필요 없을 것 같네요. 스프링의 DispatcherServlet하고 비슷한 일을 할 듯 합니다. 중요 클래스니까 언제 한 번 열어봐야겠네요.

1-2. faces-config.xml

<faces-config>
    ...
  <managed-bean>
    <description>
      The "backing file" bean that backs up the calculator webapp
    </description>
    <managed-bean-name>CalcBean</managed-bean-name>
    <managed-bean-class>com.arcmind.jsfquickstart.controller.CalculatorController</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>

<navigation-rule>
  <from-view-id>/calculator.jsp</from-view-id>
  <navigation-case>
    <from-outcome>success</from-outcome>
    <to-view-id>/results.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

</faces-config>

흠.. JSF에서 사용할 컨트롤러 빈의 이름과 스코프를 등록하고, 네비게이션을 등록해두네요. 네비게이션을 등록해둔 걸 보면, 서브밋 할 때 action="result.jsp" 같은 코드를 사용하지 않아도 알아서 가주겠죠?

2. 모델 객체 만들기

package com.arcmind.jsfquickstart.model;

/**
 * Calculator
 *
 * @author Rick Hightower
 * @version 0.1
 */
public class Calculator {
    //~ Methods ----------------------------------------------------------------

    /**
     * add numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int add(int a, int b) {
        return a + b;
    }

    /**
     * multiply numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int multiply(int a, int b) {
        return a * b;
    }
   
}

3. 컨트롤러 만들기(완전 POJO)

package com.arcmind.jsfquickstart.controller;

import com.arcmind.jsfquickstart.model.Calculator;


/**
 * Calculator Controller
 *
 * @author $author$
 * @version $Revision$
 */
public class CalculatorController {
    //~ Instance fields --------------------------------------------------------

    /**
     * Represent the model object.
     */
    private Calculator calculator = new Calculator();

    /** First number used in operation. */
    private int firstNumber = 0;

    /** Result of operation on first number and second number. */
    private int result = 0;

    /** Second number used in operation. */
    private int secondNumber = 0;

    //~ Constructors -----------------------------------------------------------

    /**
     * Creates a new CalculatorController object.
     */
    public CalculatorController() {
        super();
    }

    //~ Methods ----------------------------------------------------------------

    /**
     * Calculator, this class represent the model.
     *
     * @param aCalculator The calculator to set.
     */
    public void setCalculator(Calculator aCalculator) {
        this.calculator = aCalculator;
    }

    /**
     * First Number property
     *
     * @param aFirstNumber first number
     */
    public void setFirstNumber(int aFirstNumber) {
        this.firstNumber = aFirstNumber;
    }

    /**
     * First number property
     *
     * @return First number.
     */
    public int getFirstNumber() {
        return firstNumber;
    }

    /**
     * Result of the operation on the first two numbers.
     *
     * @return Second Number.
     */
    public int getResult() {
        return result;
    }

    /**
     * Second number property
     *
     * @param aSecondNumber Second number.
     */
    public void setSecondNumber(int aSecondNumber) {
        this.secondNumber = aSecondNumber;
    }

    /**
     * Get second number.
     *
     * @return Second number.
     */
    public int getSecondNumber() {
        return secondNumber;
    }

    /**
     * Adds the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String add() {
       
        result = calculator.add(firstNumber, secondNumber);

        return "success";
    }

    /**
     * Multiplies the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String multiply() {

        result = calculator.multiply(firstNumber, secondNumber);
       
        return "success";
    }
}


흠.. 이 코드를 보고서 좀 놀랐습니다. 스프링 2.5로 컨틀롤러를 만들어도, 가능하긴 한데, 수많은 애노테이션들이 붙고, 그 애노테이션들을 정말 잘 알고 있어야 했는데.. 결국 그 애노테이션들 없이는 스프링의 컨트롤러는 의미도 없고 재사용할 수도 없기 떄문에 엄밀히 따지면 POJO라고 하긴 좀 뭐했습니다. 그런데 JSF의 저 컨트롤러는 뭐.. 틈잡을것이 하나도 없습니다.

4. 뷰 만들기

4-1. index.jsp

<jsp:forward page="/calc/calculator.jsp" />

흠.. 얘는 네비게이션에 등록하지도 않았고, JSF Servlet이 담당할 URL로 들어오지 않을테고.. 저기서 JSF  Servlet이 담당할 URL로 포워딩시키는 군요.

4-2. calculator.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<f:view>
  <h:form id="calcForm">
     <h:panelGrid columns="3">
    <h:outputLabel value="First Number" for="firstNumber" />
    <h:inputText id="firstNumber" value="#{CalcBean.firstNumber}" required="true" />
        <h:message for="firstNumber" />   

    <h:outputLabel value="Second Number" for="secondNumber" />
    <h:inputText id="secondNumber" value="#{CalcBean.secondNumber}" required="true" />
        <h:message for="secondNumber" />
    </h:panelGrid>
   
    <h:panelGroup>
    <h:commandButton id="submitAdd" action="#{CalcBean.add}"  value="Add" />
    <h:commandButton id="submitMultiply" action="#{CalcBean.multiply}" value="Multiply" />
</h:panelGroup>
  </h:form>
</f:view>

흠~ 이제 좀 재밌는 코드가 나왔습니다.

맨위에 첫 줄은, 폼을 비록한 HTML 관련 태그들이 담겨있고, 두 번째 줄은 JSF가 사용할 로직, 벨리데이션, 컨트롤러등을 위한 태그들이 담겨있다고 합니다.

<f:view> 안에 들어가야 JSF 컴포넌트 트리를 만든다고 합니다. JSF 를 사용할 땐 저 안에 모든 JSF 컴포넌트를 넣어야 겠군요.

<h:form> 은, html 폼을 나타내는 JSF 컴포넌트인가 봅니다. 속성중에  column=3은 한 줄에 세칸이라는 뜻이고, 따라서 저 안에 있는 컴포넌트들은 세줄씩 마침 코드에서도 한 줄 띄어쓰기로 구분해주고 있네요.

<h:message>는 에러 메시지 보여줄 때 사용할 것 같은데, 뭘 참조하라는게 없군요. 자동으로 메시지 생성해서 넣어줄 것 같은데... 내가 원하는 메시지를 보여주고 싶을 땐 어떻게 해야되지??

의문 1. 내가 원하는 메시지를 보여주고 싶을 땐 <h:message>를 어떻게 설정해야 하나?

JSP의 EL 처럼 JSF에도 EL 이 있는데, 그건 바로 #{}. CalcBean은 아까 faces-config.xml이였나.. 거기에 등록했던 컨트롤러이고, value 속성은 해당 컨트롤러의 필드값을 참조하고, action은 메소드를 호출하고 그 결과를 가져오나 보군요.

의문 2. 인자가 있는 메소드도 지원할까? 지원한다면, 어떻게 써야 하나?

예상했던 것처럼 역시나 저 폼을 서브밋하면 어디로 갈지를 폼에다가 적지 않았습니다. 아까 저 페이지? URL?에 대한 네비게이션을 정의해두었죠.

4-3. result.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
...
<f:view>
  First Number: <h:outputText id="firstNumber" value="#{CalcBean.firstNumber}"/>
  <br />
  Second Number: <h:outputText id="secondNumber" value="#{CalcBean.secondNumber}"/>
  <br />
  Result: <h:outputText id="result" value="#{CalcBean.result}"/>
  <br />
</f:view>


흠.. 이번에도 역시 <f:view>를 써서 JSF 컴포넌트 트리로 감싸고, <h:outputText> 라는 컴포넌트를 쓰고 있군요. value를 사용해서 필드값들만 참조합니다. 간단하군요.

돌리는 건 내일해야 겠습니다. 11시 반도 안 됐는데 졸리네요;

'JSF > exercise' 카테고리의 다른 글

JSF의 universal EL  (0) 2008.09.20
초간단 JSF 예제 돌리기 성공  (0) 2008.09.16
JSF 기초 2  (0) 2008.07.26
JSF 기초 1  (0) 2008.07.24
top

TAG JSF