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

Apply Request Values

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

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

Restore View

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

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

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

요청 값 적용하기(Apply Request Values) – 컴포넌트의 값들을 요청에 담겨있는 값과 동일하게 맞춘다. 이 과정에서 변환기(Converter)가 사용될 수 있으며,  변환시 에러가 발생하면 변환 에러를 추가한다.
검증 수행(Process Validations) – 모든 컴포넌트가 각자 검증을 수행하도록 지시한다. 검증 에러 메시지가 보고될 수 있다.
모델 값 갱신(Update Model Values) – 컴포넌트에 연결된 백빈(backing bean) 또는 모델 객체의 값을 갱신한다.  변환 에러가 보고 될 수 있다.
애플리케이션 호출(Invoke Application) – 등록된 액션 리스너를 호출한다. 
응답 랜더링(Render Response) – 선택한 뷰를 보여준다.

SWF 12장 JSF 통합

12.1. 도입

스프링 Faces는 스프링의 JSF 통합 모듈로 스프링에서 JSF 사용을 간편하게 해준다. JSF UI 컴포넌트 모델을 스프링 MVC와 스프링 웹 플로우 컨틀로러와 함께 사용할 수 있게 해준다.

스프링 Faces는 또한 Ajax와 클리이언트쪽 검증 기능을 제공하는 자그마한 Facelets도 제공한다. 이 컴포넌트 라이브러리는 스프링 자바스크립트를 기반으로 만들었다. 스프링 자바스크립트는 Dojo를 기반 UI 툴킷으로 통합한 자바스크립트 추상화 프레임워크드다.

12.2. 스프링-중심 통합 방법

스프링 Faces는 JSF의 UI 컴포넌트 모델 장정을 스프링의 컨트롤러와 설정 모델 장점과 결합해준다. 아무런 약점없이 JSF의 모든 장점을 활용할 수 있다.

스프링 Faces는 표준 JSF 기능에 다음과 같은 강력한 보완재를 제공한다.

  1. managed bean facility
  2. scope management
  3. event handling
  4. navigation rules
  5. easy modularization and packaging of views
  6. cleaner URLs
  7. model-level validation
  8. client-side validation and UI enhancement
  9. Ajax partial page updates and full navigation
  10. progressive enhancement and graceful degradation

이 기능을 사용하면 faces-config.xml에 필요한 설정 분량을 현격하게 줄여줄 것이며 뷰와 컨트롤러 계층을 보다 깔끔하게 분리해주며 애플리케이션의 기능 책임 모듈화를 보다 잘 지원한다. 이 기능들의 사용법은 다음 절에서 살펴보겠다. 이 기능들 대부분은 스프링 웹 플로우의 플로우 정의 언어를 기반으로 한다. 여러분이 플로우 정의하기에 나와있는 기본을 이해하고 있다고 가정한다.

12.3. web.xml 설정하기

스프링 Faces를 사용하는 첫 번째 단계는 요청을 web.xml 파일에 있는 DispatcherServlet으로 라우팅하는 것이다. 이번 예제에서, 우리는 /spring/으로 시작하는 모든 URL을 서블릿으로 맵핑한다. 서블릿을 설정해야 한다. init-param을 사용하여 서블릿에 contextConfigLocation을 넘겨준다. 이 것은 애플리케이션의 스프링 설정 파일 위치다.

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>
       
JSF를 적절하게 동작시키려면, FacesServlet을 web.xml에 설정해야 한다. 스프링 Faces를 사용할 때는 그것을 사용하여 요청을 라우팅할 필요가 없다.

<!– Just here so the JSF implementation can initialize, *not* used at runtime –>
<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<!– Just here so the JSF implementation can initialize –>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
</servlet-mapping>
       
스프링 Faces  컴포넌트를 사용할 때에도 스프링 자바스크립트 ResourceServlet을 설정하여 컴포넌트에서 CSS와 자바스크립트 리소스를 제대로 출력할 수 있게 할 필요가 있다. 이 서블릿은 반드시 /resources/*로 맵핑해야 컴포넌트에서 랜더링한 URL에 대해 제대로 동작한다.

<!– Serves static resource content from .jar files such as spring-faces.jar –>
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
       
<!– Map all /resources requests to the Resource Servlet for handling –>
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
       
스프링 Faces 컴포넌트는 JSP 대신에 Facelet을 사용해야 한다. 따라서 이들 컴포넌트를 사용하려면 일반적인 Facelet 설정을 반드시 추가해야 한다.

!– Use JSF view templates saved as *.xhtml, for use with Facelets –>
<context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
</context-param>
       
12.4. JSF 뷰 랜더링 하도록 웹 플로우 설정하기

The next step is to configure Web Flow to render JSF views. To do this, in your Spring Web Flow configuration include the faces namespace and link in the faces flow-builder-services :

다음 단계는 JSF 뷰를 랜더링 하도록 웹 플로우를 설정하는 것이다. 그렇게 하려면 스프링 웹 플로우 설정에 faces 네임스페이스를 추가하고

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
       xmlns:webflow=”http://www.springframework.org/schema/webflow-config”
       xmlns:faces=”http://www.springframework.org/schema/faces”
       xsi:schemaLocation=”
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
           http://www.springframework.org/schema/faces
           http://www.springframework.org/schema/faces/spring-faces-2.0.xsd”>

    <!– Executes flows: the central entry point into the Spring Web Flow system –>
    <webflow:flow-executor id=”flowExecutor” />

    <!– The registry of executable flow definitions –>
    <webflow:flow-registry id=”flowRegistry” flow-builder-services=”facesFlowBuilderServices” base-path=”/WEB-INF”>
        <webflow:flow-location-pattern value=”**/*-flow.xml” />
    </webflow:flow-registry>

    <!– Configures the Spring Web Flow JSF integration –>
    <faces:flow-builder-services id=”facesFlowBuilderServices” />

</beans>
       
faces:flow-builder-services 태그는 JSF 환경에 적절한 다른 기본 값들도 설정한다. 특히 Unified EL을 기본 EL로 설정한다.

완전히 동작하는 예제는 배포판에서 swf-booking-faces 레퍼런스 애플리케이션을 참고하라.

12.5. faces-config.xml 설정하기

faces-config.xml에 유일하게 설정할 필요가 있는 것은 Facelet 사용에 대한 것이다. 만약 스프링 Faces 컴포넌트를 사용하지 않고 JSP를 사용하고 있다면, 어떠한 스프링 Faces 관련 설정도 faces-config.xml에 추가하지 않아도 된다.

<faces-config>
    <application>
        <!– Enables Facelets –>
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> 
    </application>
</faces-config>
       
12.6. JSF가 관리하는 빈 기능 교체하기

스프링 Faces는 JSF가 관리하는 빈 기능을 플로우가 관리하는 변수와 스프링이 관리하는 빈으로 완전히 교체할 수 있게 해준다. 그렇게 하여 여러분이 관리하는 객체를 잘 정의되어 있는 초기화 후크와 도메인 객체 실행 후크로 생명 주기를 보다 잘 관리할 수 있다. 게다가, 여러분이 이미 비즈니스 계층에서 스프링을 사용해봤다는 가정한다면, 두 개의 다른 빈 관리 모델을 유지하는 것에 대한 개념적인 오버헤드를 줄일 수 있다.

순수 JSF 개발을 할 떄는 request 스코프로는 복잡한 이벤트-기반 뷰를 다루는 대화형 모델 객체를 저장하기에는 적당하지 않다는 것을 금방 알 수 있을 것이다. 오직 사용할 수 있는 옵션은 session 스코프에 모든 것들을 넣는 것이다. 애플리케이션의 다른 뷰 또는 기능적인 부분을 처리할 때 객체들을 청소해줘야 한다는 추가 작업이 생긴다. 정말로 필요한 것은 request와 session 스코프 중간 쯤 되는 어떤 스코프가 필요하다. 우누 좋게도 웹 플로우는 그러한 확장 기능을 제공한다.

12.6.1. 플로우 변수 사용하기

가장 간단하고 자연스럽게 모델을 선언하고 관리하는 것은 플로우 변수를 사용하는 것이다. 이 변수들을 플로우 시작시에 선언할 수 있다.

<var name=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria”/>
           
그런 다음 이 변수를 플로우 중 어떤 JSF 뷰 템플릿에 EL을 통해서 참조한다.

<h:inputText id=”searchString” value=”#{searchCriteria.searchString}”/>
           
(좀 더 구체적일 필요가 있다면 그렇게 할 수도 있지만)템플릿에서 변수를 참조할 때 그 스코프로 접두어를 붙이지 않아도 된다는 것에 주목하라. 표준 JSF 빈으로 대응하는 변수에 대해 모든 사용 가능한 스코프에서 찾아볼 것이다. 따라서 EL 표현식을 수정하지 않고도 플로우 정의에서 그것을 참조하는 변수의 스코프를 변경할 수 있다.

또한 뷰 인스턴스 변수를 현재 뷰로 범위를 제한하고 다른 뷰로 이동하면 자동으로 비워버리게 정의할 수 있다. 이렇게 하는 것은 보통 페이지 내에서 다른 뷰로 전이하기 전에 여러 여러 요청에 걸쳐 이벤트를 처리하는 JSF 뷰에서 매우 유용하다.

뷰 인스턴스 변수를 정의할때 var 엘리먼트를 view-state 정의 내부에서 사용할 수 있다.

<view-state id=”enterSearchCriteria”>
    <var name=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria”/>
</view-state>
           

12.6.2. 스코프를 가진 스프링 빈 사용하기

비록 자동 연결 플로우 인스터스 변수가 괜찮은 모듈화와 가독성을 제공하지만 가끔 여러분은 AOP 같은 스프링 컨테이너의 기능을 활용하고 싶을 수 있다. 그런 경우 여러분은 스프링 애플리케이션컨텍스트 내부에 있는 빈에 특정 웹 플로우 스코프를 줄 수 있다.

<bean id=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria” scope=”flow”/>
           
이런 접근 방법의 가장 큰 차이점은 EL 표현식에 의해 접근될 때 비을 초기화 한다는 것이다. EL을 통한 이런 종류의 생성 지연은 JSF가 관리하는 빈이 할당되는 것과 매우 비슷하다.

12.6.3. 모델 조작하기

뷰 랜터링 하기 전에 (데이터베이스에서 영속 엔티티를 로딩하는 것 같은)모델 초기화가 필요한 것은 매우 흔한 일이지만 JSF 자체로는 이러한 초기화에 사용할 편의성 후크(hook)를 제공하지 않는다. 플로우 정의 언어는 액션(Action)을 통해서 이와 관련된 자연스러운 기능을 제공한다. 스프링 Faces는 액션의 결과를 JSF-관련 데이터 구조로 변경해주는 추가적인 편의성을 제공한다. 예제를 보자.

<on-render>
    <evaluate expression=”bookingService.findBookings(currentUser.name)”
              result=”viewScope.bookings” result-type=”dataModel” />
</on-render>
         
이렇게 하면 bookingService.findBookings 매서드 결과를 취하고 그것을 표준 JSF DataTable 컴포넌트에서 해당 목록을 사용할 수 있도록 커스텀 JSF DataModel로 감싼다.

<h:dataTable id=”bookings” styleClass=”summary” value=”#{bookings}” var=”booking”
             rendered=”#{bookings.rowCount > 0}”>
    <h:column>
        <f:facet name=”header”>Name</f:facet>
        #{booking.hotel.name}
    </h:column>                  
    <h:column>
    <f:facet name=”header”>Confirmation number</f:facet>
        #{booking.id}
        </h:column>
    <h:column>
        <f:facet name=”header”>Action</f:facet>
        <h:commandLink id=”cancel” value=”Cancel” action=”cancelBooking” />
    </h:column>
</h:dataTable>
           
커스텀 DataModel은 request 스코프 외의 저장을 위한 직렬화와 EL에서 현재 선택한 줄에 대한 접근 등 몇몇 추가적인 편의성을 제공한다. 예를 들어, DataTable 내의 컴포넌트에 의해 액션 이벤트가 발생한 뷰에서 포스트백 할 때, 여러분은 선택한 줄의 모델 인스턴스를 가질 수 있다.

<transition on=”cancelBooking”>
    <evaluate expression=”bookingService.cancelBooking(bookings.selectedRow)” />           
</transition>
           
12.7. 스프링 웹 플로우로 JSF 이벤트 처리하기

스프링 웹 플로우는 낮은 결합도를 유지하면서 JSF 액션 이벤트를 처리할 수 있게 해준다. 자바 코드에서 JSF API에 의존하지 않아도 된다. 이벤트를 커스텀 자바 액션 코드를 전혀 사용하지 않고 플로우 정의 언어를 사용하여 완전하게 처리할 수 있다. 이렇게 하면 (JSF 뷰 템플릿과 SWF 플로우 정의) 이벤트를 연결할 때 만들어지는 구성물들을 전체 애플리케이션을 빌드하고 다시 배포할 필요 없이 즉시 리프래시 되기 때문에 보다 기민한 개발 프로세스가 가능해진다.

12.7.1. JSF In-page 액션 이벤트 처리하기

간단하지만 JSF에서 가장 흔한 경우가 모델을 조작하는 이벤트를 발생시키고 동일한 뷰로 모델의 변경된 상태를 보여주는 것이다. 플로우 정의 언어는 transition 엘리먼트에서 이것을 지원한다.

이 것에 대한 좋은 예제는 페이징 처리를 하는 목록 표다. 거대한 결과 목록 중의 일부만 읽어오고 보여주고 싶다고 가정해보자. 그리고 사용자는 그 결과를 페이징할 수 있다. 목록을 읽어오고 보여주는 초기 view-state 정의는 다음과 같다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
</view-state>
           

JSF DataTable로 현재 호텔 목록을 보여주도록 할 수 있다. 그런 다음 표 하단에 “More Results” 링크를 제공한다.
 
<h:commandLink id=”nextPageLink” value=”More Results” action=”next”/>
           
이 커맨드링크는 action 속성에서 “next” 이벤트를 보낸다. 그럼 여러분은 이벤트를 view-state 정의에 추가하려 처리할 수 있다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
            result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
    </transition>
</view-state>
           
여기서 searchCriteria 인스턴스에서 page 카운트를 증가하여 “next” 이벤트를 처리한다. 그럼 다음 on-render 액션을 변경된 criteria로 호출한다. 그러면 다음 페이지 결과를 DataModel로 로딩해준다. transition  엘리먼트에 to 속성이 없기 때문에 동일한 뷰를 다시 보여준다. 그리고 모델에 변경된 사항을 뷰에 반영해준다.

12.7.2. JSF 액션 이벤트 처리하기

in-page 이벤트 다음 단계는 조작한 모델을 가지고 다른 뷰로 이동하는 이벤트다. 순수 JSF로 이것을 하려면 faces-config.xml에 네비게이션 로직을 추가하고 자바 코드를 JSF가 관리하는 빈에 추가해야 한다.(두 작업 모두 다시 배포해야 한다.) 플로우 정의 언어를 사용하면, in-page 이벤트를 다루던 방식과 매우 비슷하게 한 곳에서 그런 것을 다룰 수 있다.

계속해서 페이징 처리하는 목록을 살펴보자. 보여지는 DataTable의 각각의 row에 row 인스턴스에 대한 자세한 내용 페이지 링크를 가지고 있도록 하고자 한다. 여러분은 테이블에 다음의 commandLink 컴포넌트를 가지고 있는 컬럼을 추가할 수 있다.

<h:commandLink id=”viewHotelLink” value=”View Hotel” action=”select”/>
           
이것은 “select” 이벤트를 발생시킨다. 그다음 기존의 view-state에 또 다른 transition 엘리먼트를 추가하여 이것을 처리할 수 있다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
            result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
    </transition>
    <transition on=”select” to=”reviewHotel”>
            <set name=”flowScope.hotel” value=”hotels.selectedRow” />
    </transition>
</view-state>
           
여기서 “select” 이벤트는 DataTable에서 현재 선택한 hotel 인스턴스를 플로우 스코프에 넣어서 처리하고 있다. 그렇게 하면 “reviewHotel” view-state에서 참조할 것이다.

12.7.3. 모델 검증 수행하기

JSF는 변경 사항을 모델에 반영하기 전에 필드-수준 입력 검증 관련 유용한 기능을 제공한다. 하지만 변경 사항을 적용한 뒤에 모델-수준의 좀 더 복잡한 검증을 수행할 필요가 있다면 여러분은 관리하는 빈의 JSF 액션 매서드에 커스텀 코드를 추가해야 한다. 이런 종류의 검증은 모메인 모델 자체 책임이지만 도메인 모델 계층에 원하지 않던 JSF API 의존성을 추사하지 않고서는 에러 메시지를 뷰에 전달하기가 어렵다.

스프링 Faces를 사용하면 일반적이고 낮은-수준의 MessageContext를 여러분의 비즈니스 코드에서 유용하게 사용할 수 있고 그곳에 추가한 모든 메시지는 랜더링 시에 FacesContext에서 사용할 수 있다.

예를 들어, 사용자가 호텔 예약을 완료하기 위해 필요한 상세 정보를 입력하는 뷰가 있다고 가정하자. 여러분은 거기서 입력받은 체크인 체크아웃 날짜가 비즈니스 규칙에 맞는지 확인해야 한다. transition 엘리먼트에서 그러한 모델-수준 검증을 호출할 수 있다.
 
<view-state id=”enterBookingDetails”>
    <transition on=”proceed” to=”reviewBooking”>
        <evaluate expression=”booking.validateEnterBookingDetails(messageContext)” />
    </transition>
</view-state>
          
여기서 “proceed” 이벤트는 booking 인스턴스의 모델-수준 검증 매서드를 호출하여 처리한다. 매서드를 호출할 때 MessageContext 인스턴스를 넘겨줘서 메시지를 기록할 수 있게 한다. 그런다음 h:messages 컴포넌트를 사용하여 JSF 메시지를 보여줄 수 있다.

12.7.4. Ajax 이벤트 처리하기

스프링 Faces는 표준 JSF 컴포넌트에 Ajax-기반 일부 뷰 수정 기능을 추가한 몇몇 특별한 UICommand 컴포넌트를 제공한다. 이들 컴포넌트는 사용자가 사용하는 브라우져의 기능이 떨어진다면 전체 페이지를 리프래시 하여 모두 잘 동작할 것이다.

[노트] 노드
스프링 Faces의 코어 JSF 지원이 JSF 1.1 호환가능 하지만, 스프링 Faces Ajax 컴포넌트는 JSF 1.2를 필요로 한다.

Revisiting the earlier example with the paged table, you can change the “More Results” link to use an Ajax request by replacing the standard commandButton with the Spring Faces version (note that the Spring Faces command components use Ajax by default, but they can alternately be forced to use a normal form submit by setting ajaxEnabled=”false” on the component):

           
<sf:commandLink id=”nextPageLink” value=”More Results” action=”next” />
           

This event is handled just as in the non-Ajax case with the transition element, but now you will add a special render action that specifies which portions of the component tree need to be re-rendered:

<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
        <render fragments=”hotels:searchResultsFragment” />
    </transition>
</view-state>
           

The fragments=”hotels:searchResultsFragment” is an instruction that will be interpreted at render time, such that only the component with the JSF clientId “hotels:searchResultsFragment” will be rendered and returned to the client. This fragment will then be automatically replaced in the page. The fragments attribute can be a comma-delimited list of ids, with each id representing the root node of a subtree (meaning the root node and all of its children) to be rendered. If the “next” event is fired in a non-Ajax request (i.e., if JavaScript is disabled on the client), the render action will be ignored and the full page will be rendered as normal.

In addition to the Spring Faces commandLink component, there is a corresponding commandButton component with the same functionality. There is also a special ajaxEvent component that will raise a JSF action even in response to any client-side DOM event. See the Spring Faces tag library docs for full details.

An additional built-in feature when using the Spring Faces Ajax components is the ability to have the response rendered inside a rich modal popup widget by setting popup=”true” on a view-state .

<view-state id=”changeSearchCriteria” view=”enterSearchCriteria.xhtml” popup=”true”>
    <on-entry>
        <render fragments=”hotelSearchFragment” />
    </on-entry>
    <transition on=”search” to=”reviewHotels”>
        <evaluate expression=”searchCriteria.resetPage()”/>
    </transition>
</view-state>
           

If the “changeSearchCriteria” view-state is reached as the result of an Ajax-request, the result will be rendered into a rich popup. If JavaScript is unavailable, the request will be processed with a full browser refresh, and the “changeSearchCriteria” view will be rendered as normal.

Validator에도 여러 가지가 있네

JSF 스캐(스크린캐스팅)를 찍다가 알게 된건데, 기본 검증기, Application-level 검증, 커스텀 검증기, 표준 검증기가 있었습니다.

기본 검증기는 프레임워크에서 제공하는 것으로 JSF에는 길이 검사라던가, 숫자가 최소치와 최대치 사이의 값인지 범위를 검사할 수 있는 기본 Validator들을 제공하고 있었습니다. 스프링 MVC는 어떨까요? 글쎄요. 없는 것 같네요. ValidationUtils는 있지만 JSF 처럼 간단하게 뷰에서 바로 써먹을 수 있는 건 없는 것 같습니다.

다음은 폼에서 이미 검증을 마쳐서 모델에 사용자가 입력한 데이터를 담은 상태로 비즈니스 로직을 검사하는 검증입니다. 이 검증은 프레임워크에 독립적이며 도메인 클래스가 해당 로직을 가지고 있기도 합니다. 예를 들어.. member.validateBy주민번호() 같은 메소드(주민등록 번호로 성별과 생년 월일이 일치하는지 검사하는 로직)를 만들고 이를 컨트롤러에서 호출한 다음에 예외를 잡아서 화면에 어떤 에러 메시지를 출력하는 방법이 있겠습니다. 비즈니스 로직에 적합한지 검사하려면 역시 화면에 붙어있는 검증기와는 별개로 이런게 필요한 것 같습니다. 스프링MVC에서도 검증 로직이 순전히 도메인 레벨에서 가능하다면 이런 식으로 검증할 수도 있겠습니다.

다음은 검증기는 검증기인데, 특정 인터페이스를 따르지도 않고 완전한 POJO 검증기 입니다. 위에서 애플리케이션 검증 로직을 별도의 클래스로 빼내고 그 로직을 뷰에서 값을 입력 받을 때 호출하게 하는 건데, JSF의 EL은 메소드 호출도 정말 간단하게 할 수 있더군요. 그냥 이런 검증기를 managed-bean으로 등록하고 그 로직을 호출하면 됐습니다. 검증 로직을 별도의 클래스로 분리하고 한 곳에서 관리할 때 좋겠습니다.

마지막은 스프링이 제공하는 Validator인터페이스 처럼 프레임워크의 틀에 맞는 검증기를 구현하고 그것을 설정하여 사용하는 겁니다. JSF에도 그런 구조가 갖춰져 있죠. 이 걸 사용하는 장점은 어떤 틀이 있기 때문에 위처럼 중구 난방으로 작성할 수 있는 코드에 규약을 정하고 그걸 따르는 검증기를 공유할 수 있다는 겁니다. JSF는 스프링 @MVC와 달리 이 검증 로직 실행을 프레임워크의 Phase 중에서 Validation Phase에 하기 때문에 이 검증 로직을 만족하지 못하면 모델에 값이 들어갈 수가 없습니다. 스프링은 아니죠. 일단 모델에 값을 받은 상태에서 검증을 하는거죠.

흠~~ 그래서 결론적으로 생각해볼 때, 여러 검증 단계와 방법을 제공하는 건 JSF가 더 좋아보이지만, 모델에 값을 설정한 상태에서 Validator라는 규격을 가지고 그 안에서 비즈니스 로직 검사도 수행(application-level 검증)도 할 수 있는.. 스프링 MVC의 단순한 구조가 오히려 개발을 더 깔끔하게 할 수 있게 해주지 않나 생각합니다. 모든 개발자가 검증 로직을 사방한대서 한다고 생각하면.. 어휴.. 끔찍하죠.ㅋㅋ