Whiteship's Note


[Spring Web Flow] booking 예제 분석 3 - webmvc-config.xml 설정

Spring Web Flow/etc : 2009.10.16 20:28


<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <!-- Scans for application @Components to deploy -->
    <context:component-scan base-package="org.springframework.webflow.samples.booking" />

    <!-- Imports the configurations of the different infrastructure systems of the application -->
    <import resource="webmvc-config.xml" />
    <import resource="webflow-config.xml" />
    <import resource="data-access-config.xml" />
    <import resource="security-config.xml" />

</beans>

web.xml에 유일하게 등록되어 있는 빈 설정파일이었는데, 열어보니 네 개의 다른 빈 설정 파일들을 import 하고 있습니다. 이 중에서 webmvc-config.xml 파일을 보겠습니다.

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

    <!-- Maps request paths to flows in the flowRegistry; e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->
    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
        <property name="order" value="0" />
    </bean>

    <!-- Maps request paths to @Controller classes; e.g. a path of /hotels looks for a controller named HotelsController -->
    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
        <property name="order" value="1" />
        <property name="defaultHandler">
            <!-- If no @Controller match, map path to a view to render; e.g. the "/intro" path would map to the view named "intro" -->   
            <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
        </property>
    </bean>

    <!-- Resolves logical view names returned by Controllers to Tiles; a view name to resolve is treated as the name of a tiles definition -->
    <bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
    </bean>

    <!-- Configures the Tiles layout system -->
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/layouts/layouts.xml</value>
                <value>/WEB-INF/views.xml</value>
                <value>/WEB-INF/hotels/views.xml</value>
                <value>/WEB-INF/hotels/booking/views.xml</value>
            </list>
        </property>
    </bean>
   
    <!-- Dispatches requests mapped to POJO @Controllers implementations -->
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

    <!-- Dispatches requests mapped to org.springframework.web.servlet.mvc.Controller implementations -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

    <!-- Dispatches requests mapped to flows to FlowHandler implementations -->
    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
        <property name="flowExecutor" ref="flowExecutor"/>
    </bean>

    <!-- Custom FlowHandler for the hotel booking flow -->
    <bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />   
       
</beans>

1. org.springframework.webflow.mvc.servlet.FlowHandlerMapping

이 클래스는 (FlowUrlHandler를 사용하여) URL을 분석하여 해당 URL을 처리할 FlowHandler를 찾아서 반환해주는 역할을 합니다.

먼저, ApplicationContext에서 찾고, 다음은 FlowRegistry에서 찾아보고, 그래도 없으면 null을 반환합니다. 그럼 이제 이 다음 핸들러 맵핑 체인을 타게 되겠죠.

따라서 이 클래스에 설정할 수 있는 주요 속성은 FlowUrlHandler와 FlowRegistry입니다. 하지만 이중에서 FlowUrlHandler는 스프링이 기본으로 제공하는 DefaultFlowHandler를 사용하는게 일반적일 것 같군요.

2. org.springframework.webflow.mvc.servlet.FlowHandlerAdapter

이 녀석은 FlowHandler가 담당하기로 한 요청을 FlowExecutor를 이용해서 처리하는 일련의 워크 플로우에 따라 실행시켜주는 곳입니다. 실질적인 작업들은 상당 부분을 위임하고 있으면서 그 골격을 만들어둔 곳입니다.

플로우 id에 해당하는 플로우 정의를 가져오고, 플로우 정의로 플로우 실행 객체(FlowExecution)을 만들고, FlowUrlHandler로 필요한 execution 매개변수도 만들는 등.. 한 번의 코드리뷰로 파악하기에는 분량이 많더군요. 특히 FlowExecutor 쪽으로 들어가면.. 크헉.. 클래스가 팍팍 늘어납니다.

3. org.springframework.webflow.mvc.servlet.FlowHandler

특정 플로우 정의에 접근하는 것을 커스터마이징 할 때 이 녀석을 구현한다고 합니다.
- Launch executions of that flow with data in the execution input map
- 플로우 결과는 맘대로 처리하고 싶을 때.
- 플로우에서 다루지 않은 예외를 맘대로 다루고 싶을 때.

위 예제에서 BookingFlowHandler 이녀석이 구현한 건 두 개.
- String handleExecutionOutcome: 실행이 끝났을 때 갈 위치
- String handleException: 에러가 발생했을 때 갈 위치



top


[Spring Web Flow] booking 예제 분석 2 - Resources Servlet

Spring Web Flow/etc : 2009.10.15 16:05


<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <!-- The master configuration file for this Spring web application -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/config/web-application-config.xml
        </param-value>
    </context-param>
   
    <!-- Enables Spring Security -->
     <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <!-- Loads the Spring web application context -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

      <!-- Serves static resource content from .jar files such as spring-faces.jar -->
    <servlet>
        <servlet-name>Resources 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>Resources Servlet</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>
   
    <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
    <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></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
       
    <!-- Map all *.spring requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

web.xml에서 눈여볼만한 부분은 먼저, 스프링 JS가 제공하는 ResourceServlet 입니다.
JAR 파일에 들어있는 정적인 자원들(이미지, javascript, css)을 효율적으로 가져오기 위한 서블릿입니다. (JAR 파일에 없으면 로컬에 있는 파일을 참조합니다.

standard.jsp 파일에 있는 헤더를 보죠.

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Spring Travel: Spring MVC and Web Flow Reference Application</title>
    <link type="text/css" rel="stylesheet" href="<c:url value="/resources/dijit/themes/tundra/tundra.css" />" />
    <style type="text/css" media="screen">
        @import url("<c:url value="/resources/css-framework/css/tools.css" />");
        @import url("<c:url value="/resources/css-framework/css/typo.css" />");
        @import url("<c:url value="/resources/css-framework/css/forms.css" />");
        @import url("<c:url value="/resources/css-framework/css/layout-navtop-localleft.css" />");
        @import url("<c:url value="/resources/css-framework/css/layout.css" />");
        @import url("<c:url value="/resources/styles/booking.css" />");
    </style>
    <script type="text/javascript" src="<c:url value="/resources/dojo/dojo.js" />"></script>
    <script type="text/javascript" src="<c:url value="/resources/spring/Spring.js" />"></script>
    <script type="text/javascript" src="<c:url value="/resources/spring/Spring-Dojo.js" />"></script>
</head>

저기서 보면 /resources로 시작하는 url들이 보입니다. css와 js를 찹조하고 있군요. spring js JAR 파일을 볼까요.


위에서 참조하는 리소스들이 이 JAR 파일에 들어있네요. 그럼 JAR 파일에 들어있지 않은 자원들은 어찌할까요.

...
        <div id="branding" class="spring">
            <a href="<c:url value="/" />"><img src="<c:url value="/resources/images/header.jpg"/>" alt="Spring Travel" /></a>
        </div>
    </div>
    <div id="content" class="clearfix spring">
        <div id="local" class="spring">
            <a href="http://www.thespringexperience.com">
                <img src="<c:url value="/resources/images/diplomat.jpg"/>" alt="generic hotel" />
            </a>
            <a href="http://www.thespringexperience.com">
                <img src="<c:url value="/resources/images/tse.gif"/>" alt="The Spring Experience" />
            </a>
...

이것도 standard.jsp 파일의 일부입니다. 위에 보시면 /resources 로 시작하기 때문에 ResourceServlet이 요청을 처리할겁니다. 그러나 JAR 파일에는 저런 이미지들이 들어있지 않습니다. 그때는 프로젝트의 웹 폴더를 기준으로 /resources를 잘라낸 나머지 부분을 가지고 경로 탐색을 해서 찾아줍니다.


이렇게 보시다시피, /resoruces/images/header.jsp 라는 리소스 URL을 ResourceServelt이 받아서 프로젝트에 위치한 web/images/header.jsp를 찾아서 돌려줍니다.

마지막으로 ResourceServlet의 속성을 살펴보죠.

public void setGzipEnabled(boolean gzipEnabled)
public void setAllowedResourcePaths(java.lang.String allowedResourcePaths)
public void setCompressedMimeTypes(java.lang.String compressedMimeTypes)
public void setJarPathPrefix(java.lang.String jarPathPrefix)
public void setCacheTimeout(int cacheTimeout)

흠.. 뭐 이런 것들이 있군요.

다음..DispatcherServlet 설정이 뭔가 좀 낯설게 느껴집니다. 봄싹같은 경우는.

    <servlet>
        <servlet-name>springsprout</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>springsprout</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

이렇게 설정했습니다. 저렇게 해두면, springsprout-servlet.xml 파일을 찾아서 WebApplicationContext의 설정파일로 사용합니다.

그런데 지금 저 예제에서는

    <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></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>

이렇게 설정했지요. 아무래도 설정을 간단하게 하고자.. 보통 ApplicationContext의 설정파일로 쓰는 것에 웹과 관련 된 설정까지 다 해놓고, 저기서는 별다른 설정 파일을 읽지 않도록, 기본 설정 파일을 찾지 않게 그냥 빈 문자열로 설정한 듯 합니다.

봄싹의 경우 기본 네임스페이스가 springsprout기 때문에 init-param으로 contextConfigLocation 속성을 설정하지 않아도 springsprout-servlet.xml 파일을 찾도록 되어 있습니다. 몾찾으면 에러가 납니다.

그런데, <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> 이녀석은 지금 기본 네임스페이스가 Spring MVC Dispatcher Servlet 이게 되고 따라서 그냥 두면 Spring MVC Dispatcher Servlet-servlet.xml 이 파일을 찾다가 몾찾아서 에러가 날 겁니다. 그래서 contextConfigLocation을 null로 설정해서 설정 파일이 필요없도록 해둔거네요.

굳이 Web 설정이랑 App 설정을 구분할 필요가 없다면, 저렇게 해도 괜찮긴 하겠네요. 흠..

web.xml 분석은 이만~







top


[Spring Web Flow] booking 예제 분석 1 - 예제 실행하기

Spring Web Flow/etc : 2009.10.15 14:59



위 코드를 받아서 메이븐 플젝 import를 해서 Run on server로 돌리거나, Spring 사이트에서 Spring Web Fow를 다운 받은 다음 projects 폴더에 가셔서 booking-mvc 폴젝을 메이븐을 플젝 import 하셔도 됩니다.

막상 실행해 보고나니 너무 간단한 예제라서 조금 아쉽네요. 적어도 멀티 폼 정도의 예제는 만들어줘어야 하는가 아닌가 싶기도 합니다. 그렇치만, 스프링 MVC 연동, 스프링 시큐리티 연동, 스프링 EL, ConversionService 등록, 스프링 JS 등을 눈여겨 볼 수 있는 좋은 예제입니다.

실행은 별거 없으니 여기서 마무리?

혹시 Run On Server로 실행할 때 ClassNotFound 예외가 발생한다면 mvn war:inplace를 한 번 해주고 다시 하면 될 겁니다.



top


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.

'Spring Web Flow > Chapter 12' 카테고리의 다른 글

SWF 12장 JSF 통합  (0) 2009.03.10
top


SWF 11장 스프링 자바스크립트 퀵 레퍼런스



11.1 도입

스프링 자바스크립트(spring-js)는 Dojo 같이 자주 사용하는 자바스크립트 툴킷에 대한 경량 추상화다. 목표는 공통의 클라인트-쪽 프로그래밍 모델을 제공하여 웹 페이지를 리치 위젯과 애이작스 리모팅으로 급격히 개선하는 것이다.

11.2. 자바스크립트 리소스 제공하기

스프링 JS는 웹 애플리케이션 루트 디렉토리와 jar 파일에서 자바스크립트와 CSS 파일 같은 웹 리소스를 서빙하는 ResourceServlet를 제공한다. 이 서블릿은 Spring.js 파일을 여러분 페이지에 공급하는 편리한 방법을 제공한다. 이 서블릿을 배포하려면 web.xml에 다음과 같이 설정하라.

<!-- Serves static resource content from .jar files such as spring-js.jar -->
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
</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>
       
11.3. 스프링 자바스크립트를 페이지에 포함시키기

스프링 JS는 자신의 API 구현체를 유명한 자바스크립트 툴킷으로 빌드 할 수 있게 설계됐다. Spring.js 기본 구현체는 Dojo 툴킷을 기반으로 한다.

스 프링 자바스크립트를 페이지에서 사용하려면 보통 기반으로 하는 툴킷, Spring.js 기반 인터페이스 파일, 스프링-(라이브러리 구현체).js 파일을 포함시킬 필요가 있다. 예를 들어 다음은 ResourceServlet를 사용하여 Dojo 구현체를 가져온다.

<script type="text/javascript" src="<c:url value="/resources/dojo/dojo.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring-Dojo.js" />"> </script>
       
기 반 라이브러리의 위젯 시스템을 사용하면 보통 여러분은 반드시 원하는 룩앤필(look and feel)을 얻기 위해 CSS 리소스를 포함시킬것이다. booking-mvc 레퍼런스 애플리케이션은 Dojo의 tundra.css를 포함시킨다.

<link type="text/css" rel="stylesheet" href="<c:url value="/resources/dijit/themes/tundra/tundra.css" />" />
       
11.4. 스프링 자바스크립트 데코레이션

스 프링 자바스크립트의 주요 개념은 기존 DOM 노드에 데코레이션을 적용하는 개념이다. 이 기술은 기능이 좋치 않은 브라우저에서도 여전히 동작할 페이지 같은 웹 페이지를 혁신적으로 개선할 때 사용한다. addDecoration 매서드는 데코레이션을 적용할 때 사용한다.

다음 예제는 스프링 MVC의 <form:input> 태그에 풍부한 제안 기능을 가능하게 하는 것을 보여준다.

<form:input id="searchString" path="searchString"/>
<script type="text/javascript">
    Spring.addDecoration(new Spring.ElementDecoration({
        elementId: "searchString",
        widgetType: "dijit.form.ValidationTextBox",
        widgetAttrs: { promptMessage : "Search hotels by name, address, city, or zip." }}));
</script>
       
ElementDecoration 는 리치 위젯 기능을 기존의 DOM 노드에 추가할 때 사용한다. 이 데코레이션 타입은 기반으로 하는 툴킷을 완전히 감추는 것을 목표로 하지 않는다. 따라서 툴킷의 네이티브 위젯 타입과 속성을 직접 사용한다. 이 방법은 공통의 데코레이션 모델을 사용하여 기반하는 툴킷의 모든 위젯을 일관적인 방법으로 통합하기 위한 것이다. booking-mvc 레퍼런스 애플리케이션에서 제안에서 부터 클라이언트쪽 검증까지 데코레이션을 적용하는 많은 예제를 참조하라.

ElementDecoration를 사용하여 리치 검증 기능을 갖춘 위젯을 적용할 때 일반적인 요구 사항은 폼이 검증을 통과하기 전까지는 서브밋하지 않는 것이다. ValidateAllDecoration으로 그것을 할 수 있다.

<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" />
<script type="text/javascript">
    Spring.addDecoration(new Spring.ValidateAllDecoration({ elementId:'proceed', event:'onclick' }));
</script>
       

이것은 "Proceed" 버튼을 클라이언트 쪽 검증을 하고 성공적으로 패스 할 때 까지 폼 서브밋을 허용하지 않는 특별한 onclick 이벤트 핸들러로 데코레이트한다.

AjaxEventDecoration는 원격 애이작스 요청을 서버로 보내는 클라이언트쪽 이벤트 리스너를 적용한다. 또한 응답할 때 자동으로 콜백 함수를 링크에 등록한다.

<a id="prevLink" href="search?searchString=${criteria.searchString}&page=${criteria.page - 1}">Previous</a>
<script type="text/javascript">
    Spring.addDecoration(new Spring.AjaxEventDecoration({
        elementId: "prevLink",
        event: "onclick",
        params: { fragments: "body" }
    }));
</script>
       
이 것은 "Previous Results" 링크의 onclick 이벤트를 응답시에 특정 조각을 다시 랜더링하도록 특별한 매개변수와 함께 애이작스 호출로 꾸며준다. 이 링크는 클라이언트에서 자바스크립트를 사용할 수 없는 경우에도 여전히 동작한다는 것에 주목하라. (애이작스 요청 다루기에서 서버에 요청을 어떻게 다루는지 자세히 살펴보라.)

또한 하나의 엘리먼트에 여러 개의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 애이작스와 validate-all 서브밋 금지를 같이 적용한 버튼에 대한 예제다.

<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" /> 
<script type="text/javascript">
    Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'}));
    Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'proceed', event:'onclick',formId:'booking', params:{fragments:'messages'}}));
</script>
       
Dojo의 쿼리 API를 사용하여 한 줄로 여러 엘리먼트에 하나의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 체크박스 집합을 Dojo Checkbox 위젯으로 꾸민다.

<div id="amenities">
<form:checkbox path="amenities" value="OCEAN_VIEW" label="Ocean View" /></li>
<form:checkbox path="amenities" value="LATE_CHECKOUT" label="Late Checkout" /></li>
<form:checkbox path="amenities" value="MINIBAR" label="Minibar" /></li>
<script type="text/javascript">
    dojo.query("#amenities input[type='checkbox']").forEach(function(element) {
        Spring.addDecoration(new Spring.ElementDecoration({
            elementId: element.id,
            widgetType : "dijit.form.CheckBox",
            widgetAttrs : { checked : element.checked }
        }));
    });
</script>
</div>

11.5. 애이작스 요청 다루기

스프링 자바스크립트의 클라이언트 쪽 애이작스 응답 다루기는 서버에서 "조각" 받기 개념을 기반으로 했다. 이들 조각은 단순한 표준 HTML로 기존 페이지의 일부를 교체할 의도를 가지고 있다. 서버에서 필요한 핵심 조각은 전체 응답 중에서 어떤 조각을 일부만 랜더링 해야 하는지 판단할 때 사용한다.

전체 응답 중에 일부 조각만 랜더링 하려면 전체 응답은 반드시 응답 구성할 때 컴포지션을 사용할 수 있고 그 컴포지션의 구성 요소들은 개별적으로 참조하거나 랜더링 할 수 있는 템플릿 기술을 사용해서 만들어야 한다. 스프링 자바스크립트는 타일즈(Tiles)를 사용하는 간단한 스프링 MVC 확장을 통해 이것을 달성한다. 컴포지션을 지원하는 어떤 템플릿 시스템이든지 이론적으로는 같은 기술로 사용할 수 있다.

스프링 자바스크립트의 애이작스 리모팅 기능은 애이작스 요청을 다루는 코드가 일반 브라우저 요청과 다르지 않아야 한다는 개념을 기반으로 한다. 따라서 애이작스 요청에 대한 별도의 지식은 코드에서 직접적으로 필요하지 않고 동일한 핸들러를 두 가지 스타일의 요청에 모두 사용할 수 있다.

11.5.1. 스프링 MVC 컨트롤러로 애이작스 요청 다루기

애이작스 요청을 스프링 MVC 컨트롤러로 다루려면 스프링 MVC 확장이 제공하는 설정을 여러분의 스프링 애플리케이션 컨텍스트에 일부 응답을 랜더링 하도록 추가하면 된다.

<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
</bean>
           
이것은 AjaxUrlBasedViewResolver를 설정하여 애이작스 요청을 분석하고 FlowAjaxTilesView 객체를 만들어서 적절한 부분을 랜더링하는 것을 처리하게 한다. FlowAjaxTilesView는 웹 플로우와 순수 스프링 MVC 요청을 모두 랜더링하는 작업을 할 수 있다. 조각은 타일즈 뷰 정의의 개별 속성에 대응한다. 예를 들어, 다음의 타일즈 뷰 정의를 보자.

<definition name="hotels/index" extends="standardLayout">
    <put-attribute name="body" value="index.body" />
</definition>

<definition name="index.body" template="/WEB-INF/hotels/index.jsp">
    <put-attribute name="hotelSearchForm" value="/WEB-INF/hotels/hotelSearchForm.jsp" />
    <put-attribute name="bookingsTable" value="/WEB-INF/hotels/bookingsTable.jsp" />
</definition>
           
애이작스 요청은 "body", "hotelSearchForm", "bookingsTable"를 기술하여 요청에서 일부를 랜더링할 수 있다.

11.5.2. 스프링 MVC + 스프링 웹 플로우에서 애이작스 요청 다루기

스프링 웹 플로우는 조각의 부가적인 랜더리을 플로우 정의 언어에서 render 엘리먼트로 직접 다룬다. 이 방법의 장점은 조각의 선택이 클리이언트 쪽 코드와 완전히 분리 된다는 것이다. 스프링 MVC 컨트롤러 접근 방법에서 사용하듯이 요청에 넘겨주는 별도의 매개변수가 필요 없다. 예를 들어, 만약 앞선 예제 타일즈 뷰를 리치 자바스크립트 팝업으로 "hotelSearchForm" 조각을 랜더링 하고 싶다면 다음과 같이 할 수 있다.

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

'Spring Web Flow > Chapter 11' 카테고리의 다른 글

SWF 11장 스프링 자바스크립트 퀵 레퍼런스  (0) 2009.03.05
top


SWF 10장 스프링 MVC 통합



10.1. 도입

이번 장에서는 웹 플롱를 스프링 MVC 웹 애플리케이션으로 어떻게 통합하는지 살펴보겠다. booking-mvc 예제 애플리케이션은 웹 플로우와 스프링 MVC에 대한 좋은 참고자료다. 이 애플리케이션은 간단하게 만든 여행 사이트로 사용자가 호텔 방을 검색하고 예약할 수 있다.

10.2 web.xml 설정하기

스프링 MVC를 사용하는 첫 번쨰 단계는 DispatcherServlet을 web.xml에 설정하는 것이다. 여러분은 일반적으로 웹 애플리케이션 마다 한 번씩 이 작업을 할 것이다.

아 래 예제는 /spring/ 으로 시작하는 모든 요청을 DispatcherServlet으로 맵핑한다. init-param을 사용하여 contextConfigurationLocation을 제공한다. 이 설정 파일은 웹 애플리케이션에 관한 것이다.

<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>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>

10.3. 플로우로 디스패칭하기

DispatcherServlet은 애플리케이션 리소스에 대한 요청을 핸들러에게 전달한다. 플로우는 핸들러 하나의 핸들러 타입니다.

10.3.1. FlowHandlerAdapter 등록하기

요청을 플로우로 디스패칭하는 첫 번째 단계는 스프링 MVC에서 플로우 처리가 가능하게 하는 것이다. 그렇게 하려면 FlowHandlerAdapter를 설치하라.

<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <property name="flowExecutor" ref="flowExecutor" />
</bean>
           

10.3.2. 플로우 맵핑 정의하기

플로우 처리를 가능하게 했다면, 다음 단계는 특정 애플리케이션 리소스를 여러분 플로우로 맵핑하는 것이다. 그렇게 하는 가장 간단한 방법은 FlowHandlerMapping를 정의하는 것이다.

<!-- Maps request paths to flows in the flowRegistry;
     e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->       
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    <property name="flowRegistry" ref="flowRegistry"/>
    <property name="order" value="0"/>
</bean>
           
이 맵핑을 설정함으로써 Dispatcher는 애플리케이션 리소스 경로를 플로우 레지스트리에 있는 플로우로 맵핑한다. 예를 들어, /hotels/booking 경로의 리소스에 접근한다면 레지스트리는 hotels/booking id를 가지고 있는 플로우를 찾는다. 만약 해당 id를 가진 플로우를 발경하면 그 플로우가 요청을 처리한다. 만약 플로우를 찾지 못하면 다음 Dispatcher에 있는 (ordered chain)순서에 따라 다음 핸들러 맵핑이 찾아보거나 "noHandlerFound" 응답을 반환한다.

10.3.3. 플로우 처리 워크플로우

유효한 플로우 맵핑을 발견하면, FlowHandlerAdapter는 플로우의 새로운 실행 시작 지점이 어딘지 알아내거나 현재 HTTP 요청 정보를 기반으로 기존의 실행을 다시 시작한다. 어탭터가 적용할 플로우 실행을 새로 시작하거나 다시 시작하는데 관련된 여러 기본 행위가 존재한다.

  • HTTP 요청 매개변수들은 모든 시작 플로우 실행에서 이용할 수 있다.
  • 플로우 실행이 최종 응답 없이 종료되면 기본 핸들러가 동일한 요청을 새로운 실행 시작을 시도한다.
  • 예 외가 NoSuchFlowExecutionException이 아니라면 처리하지 않는 예외를 DispatcherSerlvet에 위임한다. 기본 핸들러는 NoSuchFlowExecutionException를 만나면 새로운 실행을 시작하여 예외를 극복하려고 한다.
보다 자세한 내용은 FlowHandlerAdapter API 문서를 참조하라. 여러분은 이런 기본 행위를 상속 또는 여러분 만의 FlowHandler를 구현하여 재정의 할 수 있다. 다음 절에서 자세히 다루겠다.

10.4. 커스텀 FlowHandler 구현하기

FlowHandler는 HTTP 서블릿 환경에서 플로우를 어떻게 실행할지 커스터마이징 할 때 사용할 수 있는 확장 지점이다. FlowHandler는 FlowHandlerAdapter가 사용하며 다음을 책임진다.
  • 실행할 플로우 정의의 id를 반환한다.
  • 시작할 때 플로우의 새로운 실행에 넘겨줄 입력값을 만든다.
  • 종료할 때 플로우의 실행에서 반환하는 결과를 처리한다.
  • 플로우 실행 중에 발생하는 예외를 처리한다.
이런 책임들은 org.springframework.mvc.servlet.FlowHandler 인터페이스에 정의에 나타나있다.

public interface FlowHandler {

    public String getFlowId();

    public MutableAttributeMap createExecutionInputMap(HttpServletRequest request);

    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
        HttpServletRequest request, HttpServletResponse response);

    public String handleException(FlowException e,
        HttpServletRequest request, HttpServletResponse response);
}               
       
To implement a FlowHandler, subclass AbstractFlowHandler. All these operations are optional, and if not implemented the defaults will apply. You only need to override the methods that you need. Specifically:

FlowHandler를 구현하려면 AbstractFlowHandler를 상속하라. 모든 기능은 부가적인 것이기 떄문에 구현하지 않으면 기본 설정이 적용된다. 여러분은 필요한 매서드만 재정의하면 된다. 특히...
  • 여러분 플로우의 id를 Http 요청에서 직접 가져올 수 없을 때 getFlowId(HttpServletRequest)를 재정의하라. 기본으로 실행할 플로우의 id는 요청 URI의 pathinfo 부분에서 가져온다. 예를 들어 http://localhost/app/hotels/booking?hotelId=1는 기본으로 플로우 id로 hotels/booking를 가져온다.
  • HttpServletRequest에서 플로우 입력 매개변수 추출을 상세하게 제어하고 싶다면 createExecutionInputMap(HttpServletRequest)를 재정의하라. 기본으로 모든 요청 매개변수를 플로우 입력 매개변수로 사용한다.
  • 특정 플로우 실행 결과를 별도 방식으로 처리하고 싶다면 handleExecutionOutcome을 재정의하라 기본 행위는 종료한 플로우의 URL로 리다이렉트하여 플로우의 새로운 실행을 시작한다.
  • unhandled 플로우 예외를 상세하게 제어하고 싶다면 handleException을 재정의하라. 기본 행동은 클라이언트가 종료됐거나 만료된 플로우 실행에 접근하면 플로우 재시작을 시도한다. 그밖의 예외는 스프링 MVC ExceptionResolver 기반 시설에 던져준다.
10.4.1. FlowHandler 예제

스프링 MVC와 웹 플로우 사이에 흔한 협력은 종료했을 때 @Controller로 리다이렉트 하는 것이다. FlowHandler는 특정 컨트롤러 URL로 플로우 정의에 결합하지 않고도 할 수 있게 해준다. 스프링 MVC로 리다이렉트하는 FlowHandler 예제는 아래와 같다.

public class BookingFlowHandler extends AbstractFlowHandler {
    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
                                         HttpServletRequest request, HttpServletResponse response) {
        if (outcome.getId().equals("bookingConfirmed")) {
            return "/booking/show?bookingId=" + outcome.getOutput().get("bookingId");
        } else {
            return "/hotels/index";
        }
    }
}
           
이 핸들러는 오직 플로우 실행 결과를 특정 방법으로 다뤄야 하기 때문에 다른 것은 재정의할 필요가 없다. bookingConfirmed 결과만 새로운 예약을 보여주도록 리다이렉트할 것이다. 그밖에 다른 결과는 호텔 인덱스 페이지로 다시 리다이렉트 한다.

10.4.2. 커스텀 FlowHandler 배포하기

To install a custom FlowHandler, simply deploy it as a bean. The bean name must match the id of the flow the handler should apply to.

커스텀 FlowHandler를 설치하려면 간단하게 빈으로 배포하면 된다. 빈 이름은 반드시 핸들러를 적용할 플로우의 id와 일치해야 한다.

<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
           
이 설정에서 /hotels/booking에 접근하는 것은 커스텀 BookingFlowHandler를 사용하는 /hotels/booking 플로우를 실행하는 것이다. 예약 플로우가 종료되면 FlowHandler는 플로우 실행 결과를 처리하고 적절한 컨트롤러로 리다이렉트 한다.

10.4.3. FlowHandler 리다이렉트

FlowExecutionOutcome 또는 FlowException를 처리하는 FlowHandler는 처리 한 다음 리다이렉트 할 리소스를 가리키는 문자열을 반환한다. 앞선 예제에서 BookingFlowHandler는 bookingConfirmed 결과는 booking/show 리소스 URI로 리다이렉트 하고 그밖에 모든 결과는 hotels/index 리소스로 리다이렉트 했다.

기본으로, 반환된 리소스 위치는 현재 서블릿 맵핑을 기준으로 상대적이다. 이로인해 플로우 핸들러가 상대 경로를 사용하여 애플리케이션의 다른 컨트롤러로 리다이렉트 할 수 있다. 또한 명시적인 리다이렉트 접두어는 좀 더 제어가 필요한 경우에 사용할 수 있다.

지원하는 명시적인 리다이렉트 접두어는 다음과 같다.
  • servletRelative: - 현재 서블릿에서 상대적인 위치의 리소스로 리다이렉트 하라.
  • contextRelative: - 현재 웹 애플리케이션 컨텍스트 경로에 상대적인 위치의 리소스로 리다이렉트 하라.
  • servetRelative: - 서버 루트에 상대적인 위치의 리소스로 리다이렉트 하라.
  • http:// 또는 https://: - 전체를 명시한 리소스 URI로 리다이렉트 하라.
이것과 동일한 접미어를 exteralRedirect를 사용하여 플로우 정의에서 사용할 수 있다. view-state 또는 end-state에서 지시자로 쓸 수 있다. 예를 들어 view="externalRedirect:http://springframework.org" 이렇게 쓸 수 있다.

10.5. View Resolution

웹 플로우 2는 다른 것을 기술하지 않으면 선택한 뷰 식별자를 플로우 작업 디렉토리에 위치한 파일로 맵핑한다. 기존의 스프링 MVC + 웹 플로우 애플리케이션에서는 외부 viewResolver가 이미 이런 맵핑을 다루고 있을 것이다. 따라서 그 리졸버를 계속 하용하고 플로우 뷰를 패키징 하는 방법을 바꾸고 싶지 않다면 웹 플로우를 다음과 같이 설정하라.

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
   <webflow:location path="/WEB-INF/hotels/booking/booking.xml" />
</webflow:flow-registry>

<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/>

<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
   <property name="viewResolvers" ref="myExistingViewResolverToUseForFlows"/>
</bean>
      
10.6. 뷰에서 발생한 이벤트 신호보내기

When a flow enters a view-state it pauses, redirects the user to its execution URL, and waits for a user event to resume. Events are generally signaled by activating buttons, links, or other user interface commands. How events are decoded server-side is specific to the view technology in use. This section shows how to trigger events from HTML-based views generated by templating engines such as JSP, Velocity, or Freemarker.

플로우가 멈춘 view-state로 들어가면, 사용자를 실행 URL로 리다이렉트 하고 사용자가 다시 시작 이벤트를 발생할 때까지 기다린다. 이벤트는 보통 활성화 버튼, 링크 또는 다른 사용자 인터페이스 커맨드에 의해 신호가 보내진다. 이벤트가 서버쪽에 어떻게 디코드 되느냐는 사용하는 뷰 기술에 따라 다르다. 이번 절에서 (JSP, Velocity, 또는 Freemarker 같은 템플릿 엔진에 의해 만들어진 뷰를 기반으로) HTML에서 이벤트를 어떻게 발생시키는지 살펴보겠다.

10.6.1. 이름이 있는 HTML 버튼 사용하여 이벤트 신호 보내기

아래 예제는 클릭했을 때 각각 proceed와 cancel 이벤트 신호를 보내는 두 개의 버튼을 보여준다.

<input type="submit" name="_eventId_proceed" value="Proceed" />
<input type="submit" name="_eventId_cancel" value="Cancel" />

버튼이 눌리면 웹 플로우는  _eventId_ 로 시작하는 요청 매개 변수 찾고 나머지 문자열을 이벤트 id로 간주한다. 이 예제에서 _eventId_proceed를 보내면 proceed가 id가 된다. 같은 폼에서 발생할 수 있는 여러 이벤트가 있을 때 이런 스타일을 사용할 수 있겠다.

10.6.2. 감춰진 HTML 폼 매개변수를 사용하여 이벤트 신호 보내기

아래 예제는 서브밋 할 때 proceed 이벤트를 보내는 폼이다.

<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />   
          
여기서 웹 플로우는 간단하게 특수한 _eventId 매개변수를 찾고 그 값을 이벤트 id로 사용한다. 이런 스타일은 오직 폼에서 발생하는 이벤트가 한 개 일 때에만 사용하라.

10.6.3. HTML 링크를 사용하여 이벤트 신호 보내기

아래 예제는 활성화 했을 때 cancle 이벤트를 보내는 링크다.

<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>       
          
이벤트를 발생시키면 HTTP 요청이 서버로 보내진다. 서버 쪽에서는 플로우가 현재 view-state에서 이벤트를 디코딩한다. 어떻게 이 디코딩 프로세스가 동작하는지는 뷰 구현체에 따라 다르다. 스프링 MVC 뷰 구현체가 간단하게 _eventId 이름을 가지고 있는 요청 매개변수를 찾는 것을 기억하라. 만약 _eventId 매개변수가 없다면 뷰는 _eventId_로 시작하는 매개변수를 찾고 해당 문자열의 남은 부분을 이벤트 id로 사용한다. 만약 둘 다 없는 경우에는 어떤 플로우도 실행하지 않는다.

'Spring Web Flow > Chapter 10' 카테고리의 다른 글

SWF 10장 스프링 MVC 통합  (0) 2009.03.04
top


SWF 9장 시스템 설정



9.1. 도입

이번 장에서는 웹 플로우 시스템을 웹 환경에서 사용하기 적당하도록 설정하는 방법을 살펴본다.

9.2. webflow-config.xsd

웹 플로우는 스프링 스키마를 제공하여 여러분이 시스템을 설정할 수 있도록 한다. 이 스키마를 사용하려면 그것을 기반시설 계층의 빈 파일 중 하나에 추가하라.

<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"
       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">       

    <!-- Setup Web Flow here -->
   
</beans>

9.3. 기본 시스템 설정

다음 절에서 웹 플로우 시스템을 여러분 애플리케이션에 설정할 때 필요한 최소한의 설정을 살펴보겠다

9.3.1. FlowRegistry

플로우를 FlowRegistry에 등록하라.

<webflow:flow-registry id="flowRegistry">
    <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>

9.3.2. FlowExecutor

플로우를 실행하는 핵심 서비스 FlowExecutor를 배포하라.

<webflow:flow-executor id="flowExecutor" />

스프링 MVC와 스프링 Faces 부분에서 웹 플로우 시스템을 어떻게 MVC와 JSF에 통합하는지 살펴보라.

9.4. flow-registry 옵션

이번 절은 flow-registry 설정 옵션을 살펴본다

9.4.1. 플로우 위치 명시하기

location 엘리먼트를 사용하여 등록할 플로우 정의 경로를 명시한다. 기본으로 base-path가 정의되어 있지 않다면 플로우는 자신의 파일 이름에서 확장자를 뺀 식별자를 부여받는다.

<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
        
9.4.2. 커스텀 플로우 식별자 부여하기

id를 명시하여 커스텀 등록 식별자를 플로우에 부여한다.

<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" id="bookHotel" />
           
9.4.3. 플로우 meta-attributes 부여하기

flow-definition-attributes 엘리먼틀르 사용하여 등록한 플로우에 커스텀 meta-attributes를 부여한다.

<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml">
    <flow-definition-attributes>
        <attribute name="caption" value="Books a hotel" />
    </flow-definition-attributes>
</webflow:flow-location>
           

9.4.4. 위치 패턴으로 플로우 등록하기

flow-location-pattern 엘리먼트를 사용하여 명시한 리소스 위치 패턴에 대응하는 플로우를 등록한다.

<webflow:flow-location-pattern value="/WEB-INF/flows/**/*-flow.xml" />
           
9.4.5. 플로우 위치 기반 경로

base-path 엘리먼트를 사용하여 애플리케이션의 모든 플로우에 대한 기본 위치를 정의한다. 모든 플로우 위치는 기본 경로를 기준으로 상대 경로가 된다. 기본 경로는 '/WEB-INF'같은 리소스 경로 또는 'classpath:org/springframework/webflow/samples' 같은 클래스패스 위치가 될 수 있다.

<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
    <webflow:flow-location path="/hotels/booking/booking.xml" />
</webflow:flow-registry>
           
With a base path defined, the algorithm that assigns flow identifiers changes slightly. Flows will now be assigned registry identifiers equal to the the path segment between their base path and file name. For example, if a flow definition is located at '/WEB-INF/hotels/booking/booking-flow.xml' and the base path is '/WEB-INF' the remaining path to this flow is 'hotels/booking' which becomes the flow id.

기본 경로를 정의함으로서 플로우 식별자를 부여하는 알고리즘이 약간 바뀐다. 플로우는 이제 기본 경로와 파일이름 사아의 값을 등록 식별자로 부여받는다. 예를 들어, 플로우가 정의되어 있는 위치가 '/WEB-INF/hotels/booking/booking-flow.xml' 이렇고 기본 경로가 '/WEB-INF' 라면 남는 부분인  'hotels/booking'가 플로우의 id가 된다.

[팁]    디렉토리 당 플로우 정의
각각의 플로우 정의를 별도의 디렉토리로 묶는 것이 좋은 습관이라는 것을 기억해두자. 이렇게 하면 모듈화를 증진시키고 독립적인 리소스를 프로우 정의와 함께 묶을 수 있다. 또한 규약을 이용할 때 두 개의 플로우가 같은 식별자를 갖는 것을 방지할 수 있다.

만약 기본 경로가 명시되어 있지 않거나 플로우 정의가 기본 경로에 있다면 파일이름(에서 확장자를 뺸)것을 사용하여 id를 대입한다. 예를 들어, 만약 플로우 정의 파일이 'booking.xml'라면 플로우 식별자는 간단하게 'booking'이 된다.

위치 패턴은 등록 기본 경로와 함께 사용하면 매우 막강하다. 플로우 식별자가 '*-flow'가 되지 않고 디렉토리 경로 기반이 된다. 예를 들어..

<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
    <webflow:flow-location-pattern value="/**/*-flow.xml" />
</webflow:flow-registry>
           
위 예제에서 WEB-INF 밑에 /user/login, /user/registration, /hotels/booking, /flights/booking에 위치한 플로우들이 있다고 했을 때 플로우 id는 각각 user/login, user/registration, hotels/booking,  flights/booking가 된다.

9.4.6. FlowRegistry 계층구조 설정하기

Use the parent attribute to link two flow registries together in a hierarchy. When the child registry is queried, if it cannot find the requested flow it will delegate to its parent.

parent 속성을 사용하여 두 플로우 등록을 하나의 계층구조로 묶을 수 있다. 요청받은 플로우를 하위 레지스트리에서 찾지 못하면 parent로 위임한다.

<!-- my-system-config.xml -->
<webflow:flow-registry id="flowRegistry" parent="sharedFlowRegistry">
    <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>

<!-- shared-config.xml -->
<webflow:flow-registry id="sharedFlowRegistry">
    <!-- Global flows shared by several applications -->
</webflow:flow-registry>
           
9.4.7. 커스텀 FlowBuilder 서비스 설정하기

flow-builder-services 속성을 사용하여 flow-registry에서 플로우를 만들 때 사용할 서비스와 설정을 커스터마이징할 수 있다. 만약 flow-builder-services 태그가 명시되어 있지 않다면 기본 서비스 구현체를 사용한다. 태그가 정의되어 있다면 커스터마이징 하고 싶은 서비스를 참조하면 된다.

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
    <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>

<webflow:flow-builder-services id="flowBuilderServices" />
           

설정 가능한 서비스는 conversion-service, expression-parser, view-factory-creator다. 이 서비스들은 여러분이 정의한 커스텀 빈을 첨조하도록 설정되었다. 예를 들어..

<webflow:flow-builder-services id="flowBuilderServices"
    conversion-service="conversionService"
    expression-parser="expressionParser"
    view-factory-creator="viewFactoryCreator" />

<bean id="conversionService" class="..." />
<bean id="expressionParser" class="..." />
<bean id="viewFactoryCreator" class="..." />
           

9.4.7.1. conversion-service

conversion-service 속성을 사용하여 웹 플로우 시스템이 사용할 ConversionService를 커스터마이징할 수 있다. Converter는 플로우 실행도중 어떤 타입을 다른 타입으로 변경할 필요가 있을 때 사용한다. 기본 ConversionService는 숫자, 클래스, Enum같은 기본 객체 타입에 대한 컨버터를 등록한다.

9.4.7.2. expression-parser

expression-parser 속성을 사용하여 웹 플로우 시스템이 사용할 ExpressionParser를 커스터마이징할 수 있다. 기본 ExpressionParser는 클래스패스에서 사용할 수 있다면 Unified EL을 사용하고 그렇지 않다면 OGNL을 사용한다.

9.4.7.3. view-factory-creator

view-factory-creator 속성을 사용하여 웹 플로우 시스템이 사용할 ViewFactoryCreator를 커스터미이징 할 수 있다. 기본 ViewFactoryCreator는 JSP, Velocity, Freemarker를 랜더링 할 수 있는 스프링 MVC ViewFactory를 생성한다.

설정 가능한 설정은 development다. 이 설정은 플로우 생성 과정 중에 적용할 전역 설정 속성이다.

9.4.7.4. development

플로우를 개발 모드로 변경하려면 이것을 true로 설정하라. 개발 모드는 메시지 번들 같은 독립적인 플로우 리소스 변경을 포함하여 플로우 정의 변경 핫-릴로딩을 변경한다.

9.5. flow-executor 옵션

이번 장에서는 flow-executor 설정 옵션을 살펴본다.

9.5.1. 플로우 실행 리스너 부착하기

flow-execution-listeners 엘리먼틀르 사용하여 플로우 실행 생명주기 리스너를 등록한다.

<flow-execution-listeners>
    <listener ref="securityListener"/>
    <listener ref="persistenceListener"/>
</flow-execution-listeners>
           
특정 플로우만 관찰할 리스너를 설정할 수도 있다.

<listener ref="securityListener" criteria="securedFlow1,securedFlow2"/>
           

9.5.2. FlowExecution 영속성 튜닝하기

flow-execution-repository 엘리먼트를 사용하여 플로우 실행 영속성 설정을 튜닝한다.

<flow-execution-repository max-executions="5" max-execution-snapshots="30" />
           
9.5.2.1. max-executions

max-executions 속성을 설정하여 사용자 세션당 생성할 수 있는 플로우 실행 수 상한선을 설정한다.

9.5.2.2. max-execution-snapshots

max-execution-snapshots 속성을 설정하여 플로우 실행당 가질 수 있는 히스토리 스냅샷 수 상한선을 정할 수 있다. 스냅샷을 못찍게 하려면 이 값을 0으로 설정하라. 무한대로 스냅샷을 찍으려면 이 값을 -1로 설정하라.

'Spring Web Flow > Chapter 9' 카테고리의 다른 글

SWF 9장 시스템 설정  (0) 2009.02.18
top


SWF 8장 플로우 상속



8.1. 개요

플로우 상속은 한 플로우가 다른 플로우 설정을 상속할 수 있게한다. 상속은 플로우와 스테이트 수준에서 모두 발생할 수 있다. 가장 흔한 유즈케이스는 상위 플로우로 전역적인 트랜지션과 예외 핸들러를 정의하고 하위 플로우로 그 설정을 상속받는 것이다.

상위 플로우를 찾으려면 다른 플로우들처럼 flow-registry에 추가해야 된다.

8.2. 플로우 상속은 자바 상속과 비슷한가?

상위에 정의한 요소를 하위에서 접근할 수 있다는 측면에서는 자바 상속과 플로우 상속이 비슷하다. 하지만 주요 차이점이 있다.

하위 플로우는 상위 플로우의 요소를 재정의 할 수 없다. 상위와 하위 플로우에 있는 같은 요소는 병합(merge)된다. 상위 플로우에만 있는 요소는 하위 플로우에 추가된다.

하위 플로우는 여러 상위 플로우를 상속받을 수 있다. 자바 상속은 단일 클래스로 제한된다.

8.3. 플로우 상속 타입

8.3.1. 플로우 수준 상속

플로우 수준 상속은 flow 엘리먼트의 parent 속성으로 정의한다. 이 속성은 콤마로 구분한 상속받을 플로우 식별자 목록을 명시한다. 하위 프로우는 목록에 명시된 순서대로 각각의 상위 플로우를 상속 받는다. 첫 번째 상속으로 상위 플로우에 있는 요소와 내용을 추가하고 나면 그것을 다시 하위 플로우로 간주하고 그 다음 상위 프로우를 상속 받는다. 그런식으로 계속 이어진다.

<flow parent="common-transitions, common-states">

8.3.2. 스테이트 수준 상속

스테이트 수준 상속은 플로우 수준 상속과 비슷하다. 유일한 차이점은 플로우 전체가 아니라 오직 해당 스테이트 하나만 상위로 부터 상속 받는다.

플로우 상속과 달리 오직 하나의 상위만 허용한다. 또한 상속받을 플로우 스테이트의 식별자가 반드시 정의되어 있어야 한다. 플로우와 스테이트 식별자는 #로 구분한다.

상위와 하위 스테이트는 반드시 같은 타입이어야 한다. 예를 들어 view-state는 ent-state를 상속받을 수 없다. 오직 view-state만 상속받을 수 있다.

<view-state id="child-state" parent="parent-flow#parent-view-state">
           
8.4. 추상 플로우

종종 상위 플로우는 직접 호출하지 않도록 설계한다. 그런 플로우를 실행하지 못하도록 abstract로 설정할 수 있다. 만약 추상 플로우를 실행하려고 하면 FlowBuilderException가 발생한다.

<flow abstract="true">

8.5. 상속 알고리즘

하위 플로우가 상위 플로우를 상속할 때 발생하는 기본적인 일은 상위와 하위 플로우를 병합하여 새로운 플로우를 만드는 것이다. 웹 플로우 정의 언어에는 각각의 엘리먼트에 대해 어떻게 병합할 것인가에 대한 규칙이 있다.

엘리먼트에는 두 종류가 있다. 병합 가능한 것(mergeable)과 병합이 가능하지 않은 것(non-mergeable이 있다. 병합가능한 엘리먼트는 만약 엘리먼트가 같다면 병합을 시도한다. 병합이 가능하지 않은 엘리먼트는 항상 최종 플로우에 직접 포함된다. 병합 과정 중에 수정하지 않는다.

노트
상위 플로우에있는 외부 리소스 경로는 절대 경로여야 한다. 상대 경로는 두 플로우를 병합할 때 상위 플로우와 하위 플로우가 위치한 디렉토리가 다르면 깨질 수 있다. 일반 병합하면, 상위 플로우에 있던 모든 상대 경로는 하위 플로우 기준으로 바뀐다.

8.5.1. 병합 가능한 엘리먼트

만약 같은 타입의 엘리먼트고 입력한 속성이 같다면 상위 엘리먼트의 내용을 하위 엘리먼트로 병합한다. 병합 알고리즘은 계속해서 병합하는 상위와 하위의 서브 엘리먼트를 각각 병합한다. 그렇지 않으면 상위 플로우의 엘리먼트를 하위 플로우에 새로운 엘리먼트로 추가한다.

대부분의 경우 상위 프로우릐 엘리먼트가 하위 플로우 엘리먼트에 추가된다. 이 규칙에 예외로는 시작할 때 추가 될 액션 엘리먼트(evaluate, render, set)가 있다. 상위 액션의 결과를 하위 액션 결과로 사용하게 한다.

병합이 가능한 엘리먼트는 다음과 같다.
  • action-state: id
  • attribute: name
  • decision-state: id
  • end-state: id
  • flow: 항상 병합
  • if: test
  • on-end: 항상 병합
  • on-entry: 항상 병합
  • on-exit: 항상 병합
  • on-render: 항상 병합
  • on-start: 항상 병합
  • input: name
  • output: name
  • secured: attributes
  • subflow-state: id
  • transition: on and on-exception
  • view-state: id
8.5.2. 병합 할 수 없는 엘리먼트

병합할 수 없는 엘리먼트는 다음과 같다
  • bean-import
  • evaluate
  • exception-handler
  • persistence-context
  • render
  • set
  • var

'Spring Web Flow > Chapter 8' 카테고리의 다른 글

SWF 8장 플로우 상속  (0) 2009.02.16
top


SWF 7장 플로우 보안하기



7.1 개요

보안은 모든 애플리케이션에서 중요한 개념이다. 최종 사용자는 URL을 추측하여 사이트의 임의 영역에 접근해서는 안 된다. 중요한 부분은 반드시 권한을 가지고 있는 요청만 접근할 수 있어야 한다. 스프링 시큐리티는 검증된 보안 플랫폼으로 애플리케이션에 여러 수준으로 통합할 수 있다. 이번 장에서는 플로우 실행 보안을 집중적으로 살펴본다.

7.2. 플로우를 어떻게 보안할까?

플로우 실행 보안은 세 단계로 한다.
  • 스프링 시큐리티를 인증 권한 규칙으로 설정한다.
  • 플로우 정의에 보안 규칙을 정의하는 secured 엘리먼트를 추가한다.
  • SecurityFlowExecutionListener를 추가하여 보안 규칙을 처리한다.
이 모든 단계를 완료해야 하며 그렇지 않으면 보안 규칙은 적용되지 않는다.

7.3. secured 엘리먼트

secured 엘리먼트는 완전히 들어오기 전에 권한 확인을 하도록 고안되었다. 보안하고 있는 플로우 실행 스테이지 마다 두 번 이상 발생하면 안 된다.

플로우 실행에서 세 단계를 보안할 수 있다. flow, state, transition이다. 각각에서 secured 엘리먼트의 의미는 모두 동일하다. secured 엘리먼트는 자신이 보안하는 엘리먼트 안에 위치한다. 예를 들어 state를 보안하려면 해당 state 내부에 secured 엘리먼트를 추가하면 된다.

<view-state id="secured-view">
    <secured attributes="ROLE_USER" />
    ...
</view-state>
       
7.3.1. 보안 속성

attributes 속성은 콤바로 구분한 스프링 시큐리비 권한 속성 목록이다. 보통 특정 보안 롤(role)을 명시한다. 스프링 시큐리티 접근 결정 매니저(access decision manager)에 의해 이 속성에 입력한 값과 사용자가 가지고 있는 값을 비교한다.

<secured attributes="ROLE_USER" />
           
기본적으로, 롤 기반 접근 결정 관리자를 사용하여 사용자가 접근할 수 있는지 확인한다. 만약 애플리케이션이 권한 룰을 사용하지 않는다면 이 부분을 오버라이딩할 필요가 있다.

7.3.2. 매칭 타입

가용한 매칭 타입에는 두 가지가 있다. any와 all이 있다. Any는 필요한 보안 속성 중에 사용자가 하나라도 가지고 있다면 접근을 허용한다. All은 사용자가 명시되어 있는 보안 속성을 모두 가지고 있는 경우에 접근을 허용한다.

<secured attributes="ROLE_USER, ROLE_ANONYMOUS" match="any" />
   
이 속성은 필수가 아니다. 정의하지 않으면 기본 값은 any다.

The match attribute will only be respected if the default access decision manager is used.

7.4. SecurityFlowExecutionListener

보안 규칙을 폴로우 내부에 정의하는 것 만으로는 플로우 실행을 보호하지 못한다. SecurityFlowExecutionListener가 웹 플로우 설정에 정의되어 있어야 플로우 실행기(executor)에 적용된다.

<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
    <webflow:flow-execution-listeners>
        <webflow:listener ref="securityFlowExecutionListener" />
    </webflow:flow-execution-listeners>
</webflow:flow-executor>

<bean id="securityFlowExecutionListener"
      class="org.springframework.webflow.security.SecurityFlowExecutionListener" />

애플리케이션의 특정 부분에서 만약 접근이 거부되면 AccessDeniedException를 던진다. 이 예외는 나중에 스프링 시큐리티가 처리하고 사용자에게 권한을 요청하는데 사용된다. 이 예외는 예외 스택을 무제한으로 올라갈 수 있다는 것이 중요한데 이렇게 하지 않으면 최종 사용자는 권한을 요청할 수 없다.

7.4.1. 커스텀 접근 결정 관리자

만약 애플리케이션이 롤 기반이 아닌 권한을 사용중이라면 커스텀 AccessDecisionManager를 설정할 필요가 있다. 기본 결정 관리자를 오버라이드 하고 시큐리티 리스너의 accessDecisionManager 속성에 설정할 수 있다. 스프링 시큐리티 레퍼런스 문서를 참조하여 결정 관리자에 대해 자세히 살펴보기 바란다.

<bean id="securityFlowExecutionListener"
      class="org.springframework.webflow.security.SecurityFlowExecutionListener">
    <property name="accessDecisionManager" ref="myCustomAccessDecisionManager" />
</bean>

7.5. 스프링 시큐리티 설정하기

스프링 시큐리티는 일관된 설정 옵션을 제공한다. 모든 애플리케이션과 환경이 자신만의 보안 요구사항을 가지고 있기 떄문에 스프링 시큐리티 레퍼런스 문서에서 가용한 옵션을 익히는것이 좋다.

 7.5.1. 스프링 설정

스프링 설정은 (보호하는 URL과 로그인/로그아웃 방법 같은) http 관련 설정과 authentication-provider를 설정한다. 예제 애플리케이션에 로컬 인증 공급자를 설정했다.

<security:http auto-config="true">
    <security:form-login login-page="/spring/login"
                         login-processing-url="/spring/loginProcess"
                         default-target-url="/spring/main"
                         authentication-failure-url="/spring/login?login_error=1" /> 
    <security:logout logout-url="/spring/logout" logout-success-url="/spring/logout-success" />
</security:http>

<security:authentication-provider>
    <security:password-encoder hash="md5" />
    <security:user-service>
        <security:user name="keith" password="417c7382b16c395bc25b5da1398cf076"
                       authorities="ROLE_USER,ROLE_SUPERVISOR" />
        <security:user name="erwin" password="12430911a8af075c6f41c6976af22b09"
                       authorities="ROLE_USER,ROLE_SUPERVISOR" />
        <security:user name="jeremy" password="57c6cbff0d421449be820763f03139eb"
                       authorities="ROLE_USER" />
        <security:user name="scott" password="942f2339bf50796de535a384f0d1af3e"
                       authorities="ROLE_USER" />
    </security:user-service>
</security:authentication-provider>
           
7.5.2. web.xml 설정

web.xml 파일에서 filter는 모든 요청을 가로채도록 정의했다. 이 필터는 로그인/로그아웃 요청을 다루고 적절하게 처리할 것이다. 또한 AccesDeniedExceptions 예외를 잡아서 사용자를 로그인 페이지로 리다이렉트 시킨다.



 

'Spring Web Flow > Chapter 7' 카테고리의 다른 글

SWF 7장 플로우 보안하기  (0) 2009.02.12
top


SWF 6장 플로우가 관리하는 영속성



6.1. 개요

대부분의 애플리케이션은 여러 방법으로 데이터에 접근한다. 여러 사용자가 공유하는 데이터를 여럿이 수정한다. 따라서 트랜잭션 데이터 접근 속성이 필요하다. 관계형 데이터 집합을 도메인 객체로 변형하여 애플리케이션 처리를 도와준다. 웹 플로우는 "플로우가 관리하는 영속성"(flow managed persistence)을 제공하여 플로우가 객체 영속성 문맥을 만들고, 커밋하고, 닫을 수 있도록 한다. 웹 플로우는 하이버네이트와 JPA 객체 영속화 기술과 연동한다.

플로우-관리 영속성과 별도로 PesistenceContext 관리를 애플리케이션의 서비스 계층에서 완전히 캡슐화하는 패턴이 있다. 이런 경우 웹 계층은 영속화에 관여하지 않는다. 그 대신 서비스 계층으로 념주겨거나 반환받은 detached object를 가지고 동작한다. 이번 장은 플로우-관리 영속성에 초점을 맞추고 이 기능을 언제 어떻게 사용하는지 살펴보겠다.

6.2. FlowScoped PersistenceContext

이 패턴은 플로우가 시작할 때 flowScope 안에 PersistenceContext를 생성한다. 이 컨텍스트를 사용하여 플로우 실행 코드에서 데이터 접근을 하고 마지막에 영속화 요소에 대한 변경을 커밋한다. 이 패턴은 변경사항 커밋을 오직 플로우 실행 마지막에만 하기 때문에 중간 단계 편집 독립성을 제공한다. 이 패턴은 보통 낙천적인 롹킹으로 여러 사용자의 동시 수정 무결성을 보장한다. 플로우 과정을 일정 시간을 넘어서까지 저장하거나 재시작 하려면 플로우 상태에 대한 영속성 저장소를 사용해야 한다. 만약 저장과 재시작 기능이 필요 없다면 플로우 상태를 표준 HTTP 세션-기반으로 저장해도 충분하다. 그런 경우 커밋 이전에 세션 만료나 종가 발생하면 변경 사항들을 잃어버리게 된다.

 

FlowScoped PersistenceContext 패턴을 사용하려면 해당 플로우를 persistence-context로 설정하라.

 

<?xml version="1.0" encoding="UTF-8"?>

<flow xmlns="http://www.springframework.org/schema/webflow"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://www.springframework.org/schema/webflow

                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

 

    <persistence-context />

 

</flow>

 

그런 다음 적당한 FlowExecutionListener를 설정하여 이 패턴을 플로우에 적용하라. 만약 하이버네이트를 사용하고 있다면 HibernateFlowExecutionListener를 등록하고, JPA를 사용하고 있다면 JpaFlowExecutionListener를 등록하라.

 

<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">

    <webflow:flow-execution-listeners>

        <webflow:listener ref="jpaFlowExecutionListener" />

    </webflow:flow-execution-listeners>

</webflow:flow-executor>

 

<bean id="jpaFlowExecutionListener"

      class="org.springframework.webflow.persistence.JpaFlowExecutionListener">

    <constructor-arg ref="entityManagerFactory" />

    <constructor-arg ref="transactionManager" />

</bean>

 

종료 지점에서 커밋 하려면 end-state commit 속성에 명시하라.

 

<end-state id="bookingConfirmed" commit="true" />

 

이렇게. 플로우를 시작하면 리스너는 새로운 EntiryManager flowScope에 할당한다. EntityManager persistenceContext 변수를 사용하여 여러분 플로우 내에서 어디서든 참조할 수 있다. 게다가 스프링을 사용하는 모든 데이터 접근은 자동으로 이 EntityManager를 사용하게 될 것이다. 그런 데이터 접근 작업은 중간 단계 편집을 독립적으로 관리하기 위해 항상 트랜잭션이 없거나  읽기-전용 트랜잭션으로 수행한다

'Spring Web Flow > Chapter 6' 카테고리의 다른 글

SWF 6장 플로우가 관리하는 영속성  (0) 2009.02.11
top


SWF 5장 액션 실행하기



5.1. 소개

이번 장에서는 action-state 엘리먼트를 사용하여 플로우 내에서 특정 시저에 액션 실행을 제어하는 방법을 살펴본다. 또한 decision-state 엘리먼트를 사용하여 플로우 방향을 결정하는 방법도 살펴본다. 마지막으로 플로우 내에서 가능한 다양한 지점에서 액션을 호출하는 예제를 다룰 것이다.

5.2. 액션 스테이트 정의하기

액션을 호출하고 싶을 때 action-state 엘리먼트를 사용하면 액션의 결과에 기반하여 다른 상태로 전이한다.

<action-state id="moreAnswersNeeded">
    <evaluate expression="interview.moreAnswersNeeded()" />
    <transition on="yes" to="answerQuestions" />
    <transition on="no" to="finish" />
</action-state>

위의 action-state를 사용하여 인터뷰를 완성하는데 더 많은 질문이 필요한지 확인하는 인터뷰 플로우는 다음과 같다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <on-start>
        <evaluate expression="interviewFactory.createInterview()" result="flowScope.interview" />
    </on-start>

    <view-state id="answerQuestions" model="questionSet">
        <on-entry>
            <evaluate expression="interview.getNextQuestionSet()" result="viewScope.questionSet" />
        </on-entry>
        <transition on="submitAnswers" to="moreAnswersNeeded">
            <evaluate expression="interview.recordAnswers(questionSet)" />
        </transition>
    </view-state>
   
    <action-state id="moreAnswersNeeded">
        <evaluate expression="interview.moreAnswersNeeded()" />
        <transition on="yes" to="answerQuestions" />
        <transition on="no" to="finish" />
    </action-state>

    <end-state id="finish" />
   
</flow>

5.3. 의사결정 상태 정의하기

decision-state 엘리먼트를 action-state 대신 사용하여 편리한 if/else 문법을 사용하여 경로 결정을 할 수 있다. 아래 예제는 위에서 봤던 moreAnswersNeeded 상태를 action-state 대신 의사결정 상태로 구현한 것이다.

<decision-state id="moreAnswersNeeded">
    <if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
           
5.4. 액션 결과 이벤트 맵핑

액션은 보통 일반 자바 객체의 매서드를 호출한다. action-state와 decision-state에서 호출한 매서드는 뷰 상태를 전이할 때 사용할 값을 반환한다. 전이는 이벤트에 의해 발생하기 때문에 매서드의 반환값은 반드시 먼저 Event 객체로 맵핑되어야 한다. 다음 표는 흔한 반환값을 어떻게 Event 객체로 맵핑하는지 보여준다.

표 5.1. 액션 매서드 반환 값을 이벤트 id로 맵핑하기

|| 매서드 반환 값 || 맵핑한 이벤트 식별자 표현식 ||
| java.lang.String | 문자열 값 |
| java.lang.Boolean | (true 면) yes, (false 면) no |
| java.lang.Enum | Enum 이름 |
| 그밖에 다른 타입 | success |

아래 예제의 액션 스테이트에서 매서드가 boolean 값을 반환한다는 것을 알 수 있다.

<action-state id="moreAnswersNeeded">
    <evaluate expression="interview.moreAnswersNeeded()" />
    <transition on="yes" to="answerQuestions" />
    <transition on="no" to="finish" />
</action-state>
       
5.5. 액션 구현

액션 코드를 POJO 로직으로 작성하는 것이 가장 일반적이지만, 다른 액션 구현 방법도 있다. 가끔은 액션 코드에서 플로우 컨텍스트에 접속할 필요가 있을 것이다. 이 때 POJO를 호출하고 거기에 flowRequestContext를 EL 변수로 넘겨줄 수 있다. 또는, Action 인터페이스를 구현하거나 MultiAction 기본 클래스를 상속받을 수도 있다. 이러한 대안들이 보다 강한 타입 안전성을 제공하여 여러분의 액션 코드와 스프링 웹 플로우 API 사이를 자연스럽게 연결해준다. 아래에서 이들 각각에 대한 예제를 살펴보자.

5.5.1. POJO 액션 호출하기

<evaluate expression="pojoAction.method(flowRequestContext)" />   
           

public class PojoAction {
    public String method(RequestContext context) {
        ...
    }
}
           
5.5.2. 커스텀 Action 구현체 호출하기

<evaluate expression="customAction" />   
           

public class CustomAction implements Action {
    public Event execute(RequestContext context) {
        ...
    }
}
           
5.5.3. MultiAction 구현체 호출하기

<evaluate expression="multiAction.actionMethod1" />
   
           

public class CustomMultiAction extends MultiAction {
    public Event actionMethod1(RequestContext context) {
        ...
    }

    public Event actionMethod2(RequestContext context) {
        ...
    }

    ...
}
           
5.6. 액션 예외

액션은 보통 복잡한 비즈니스 로직을 캡슐화한 서비스를 호출한다. 이러한 서비스는 액션 코드가 처리해야 할 비즈니스 에외를 던질 수도 있다.

5.6.1. 비즈니스 예외를 POJO 액션에서 처리하기

다름 예제는 비즈니스 예외를 잡는 액션을 호출하고, 에러 메시지를 컨텍스트에 추가하고, 결과 이벤트 식별자를 반환한다. 결과는 호출한 플로우가 반응할 수 있는 플로우 이벤트로 간주한다.

<evaluate expression="bookingAction.makeBooking(booking, flowRequestContext)" />   
           

public class BookingAction {
   public String makeBooking(Booking booking, RequestContext context) {
       try {
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put("confirmation", confirmation);
           return "success";
       } catch (RoomNotAvailableException e) {
           context.addMessage(new MessageBuilder().error().
               .defaultText("No room is available at this hotel").build());
           return "error";
       }
   }
}
           
5.6.2. 비즈니스 예외를 MultiAction으로 처리하기

다음 예제는 기능적으로 위에 것과 같지만 POJO 액션이 아니라 MultiAction으로 구현했다. MultiAction은 ${methodName}(RequestContext) 형태의 액션 매서드를 필요로 하며 강한 타입 안전성을 제공한다. 반면 POJO 액샨은 더 많은 자유를 제공한다.

<evaluate expression="bookingAction.makeBooking" />   
           

public class BookingAction extends MultiAction {
   public Event makeBooking(RequestContext context) {
       try {
           Booking booking = (Booking) context.getFlowScope().get("booking");
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put("confirmation", confirmation);
           return success();
       } catch (RoomNotAvailableException e) {
           context.getMessageContext().addMessage(new MessageBuilder().error().
               .defaultText("No room is available at this hotel").build());
           return error();
       }
   }
}
           
5.7. 기타 액션 호출 예제

5.7.1. on-start

다음 예제는 서비스의 매서드를 호출하여 새로운 Booking 객체를 만드는 액션을 보여준다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

</flow>
   

5.7.2. on-entry

다음 예제는 상태 진입 액션으로 특별한 fragments 변수를 정의하여 view-state가 뷰의 일부만 랜더링 하도록 하고 있다.

<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
    <on-entry>
        <render fragments="hotelSearchForm" />
    </on-entry>
</view-state>
           

5.7.3. on-exit

다음 예제는 상태 종료(exit) 액션으로 편집중이던 레코드에 대한 롹을 해제하고 있다.

<view-state id="editOrder">
    <on-entry>
        <evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
                  result="viewScope.order" />
    </on-entry>
    <transition on="save" to="finish">
        <evaluate expression="orderService.update(order, currentUser)" />
    </transition>
    <on-exit>
        <evaluate expression="orderService.releaseLock(order, currentUser)" />
    </on-exit>
</view-state>

5.7.4. on-end

다음 예제는 위와 같은 객체 롹킹을 플로우 시작과 종료(end) 액션으로 하는 것을 보여준다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="orderId" />

    <on-start>
        <evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
                  result="flowScope.order" />
    </on-start>

    <view-state id="editOrder">
        <transition on="save" to="finish">
            <evaluate expression="orderService.update(order, currentUser)" />
        </transition>
    </view-state>

    <on-end>
        <evaluate expression="orderService.releaseLock(order, currentUser)" />
    </on-end>
   
</flow>

5.7.5. on-render

다음 예제는 랜더 액션으로 뷰를 랜더링 하기 전에 보여줄 호텔 목록을 로딩한다.

<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" result-type="dataModel" />
    </on-render>
    <transition on="select" to="reviewHotel">
        <set name="flowScope.hotel" value="hotels.selectedRow" />
    </transition>
</view-state>
           
5.7.6. on-transition

다음 예제는 하위플로우 결과 이벤트 속성을 컬렉션에 추가하는 트랜지션 액션을 보여준다.

<subflow-state id="addGuest" subflow="createGuest">
    <transition on="guestCreated" to="reviewBooking">
        <evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" /> 
    </transition>
</subfow-state>
           
5.7.7. Names actions

다음 예제는 하나의 action-state 내에서 액션 체인을 호출하는 방법을 보여준다. 각각의 액션 이름은 액션 결과 이벤트에 대한 식별자가 된다.

<action-state id="doTwoThings">
    <evaluate expression="service.thingOne()">
        <attribute name="name" value="thingOne" />
    </evaluate>
    <evaluate expression="service.thingTwo()">
        <attribute name="name" value="thingTwo" />
    </evaluate>
    <transition on="thingTwo.success" to="showResults" />
</action-state>
       
이 예제에서, 플로우는 thingTwo가 무사히 완료되면 showResults로 전이할 것이다.

'Spring Web Flow > Chapter 5' 카테고리의 다른 글

SWF 5장 액션 실행하기  (0) 2009.01.30
top


SWF 4장 뷰 랜더링



4. 뷰 랜더링

4.1. 소개

이번 장에서는 view-state 엘리먼트를 사용하여 플로우에서 뷰를 랜더링하는 방법을 살펴본다.

4.2. 뷰 상태 정의하기

view-state 엘리먼트를 사용하여 뷰를 랜더링하고 사용자 이벤트를 대기하는 플로우 스탭을 정의한다.

<view-state id="enterBookingDetails">
    <transition on="submit" to="reviewBooking" />
</view-state>
       
규약에 따라, view-state는 자신의 id를 flow가 위치한 디렉터리에서 뷰 템플릿으로 맵핑한다. 예를 들어, 만약에 flow 자체가 /WEB-INF/hotels/booking에 있다면 위의 state는 /WEB-INF/hotels/booking/enterBookingDetails.xhtml을 랜더링할 것이다.

아래는 예제 디렉터리 구조로 뷰와 메시지 번들 같은 기타 리소스가 그들의 플로우 정의와 같이 있는 것을 보여준다.

사용자 삽입 이미지

4.3. 뷰 식별자 작성하기

view 속성을 사용하여 랜더링 할 뷰를 명시적으로 기입한다.

4.3.1. 플로우 상대 뷰 id

뷰 id는 플로우가 있는 작업 디렉터리에 위치한 뷰 리소스에 대한 상대 경로가 될 수 있다.

<view-state id="enterBookingDetails" view="bookingDetails.xhtml">
           
4.3.2. 절대 뷰 id

뷰 id는 웹 애플리케이션 루트 디렉터리에 있는 뷰의 절대 경로가 될 수도 있다.

<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml">
           
4.3.3. 논리 뷰 id

스프링 MVC의 뷰 프레임워크 같은 몇몇 뷰 프레임워크에서 뷰 id는 프레임워크가 판단할 떄 사용할 논리적인 식별자가 될 수도 있다.

<view-state id="enterBookingDetails" view="bookingDetails">
           
스프링 MVC 연동 뿐에서 어떻게 MVC ViewResolver 기반 시설과 연동하는지 더 많은 정보를 살펴보도록 하자.

4.4. 뷰 스코프

view-state는 들어올 때마다 새로운 viewScope을 할당한다. 이 스코프는 Ajax 요청처럼 같은 뷰에서 여러 요청에 걸쳐 객체를 조작할 때 유용하다. view-state는 해당 state를 나갈 때 viewScope을 소멸한다.

4.4.1. 뷰 변수 할당하기

var 태그를 사용하여 뷰 변수를 선언한다. 플로우 변수처럼, 뷰 상태가 다시 시작하면  모든 @Autowired 레퍼런스를 자동으로 복원한다.

<var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" />
           
4.4.2. viewScope 변수 할당하기

on-render 태그를 사용하여 뷰 랜더링하기 전에 액션 결과에 변수를 할당한다.

<on-render>
    <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
           

4.4.3. 뷰 스코프에 있는 객체 조작하기

뷰 스코프에 있는 객체는 보통 동일한 뷰에서 여러 요청에 의해 조작된다. 다음 예제 페이지는 검색 결과 목록을 보여준다. 목록은 매번 랜더링 하기 전에 뷰 스코프에서 수정된다. 비동기 이벤트 핸들러가 현재 데이터 페이지를 수정하고 검색 결과 부분에 대한 요청을 다시 랜더링 한다.

<view-state id="searchResults">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" />
    </on-render>
    <transition on="next">
        <evaluate expression="searchCriteria.nextPage()" />
        <render fragments="searchResultsFragment" />           
    </transition>
    <transition on="previous">
        <evaluate expression="searchCriteria.previousPage()" />
        <render fragments="searchResultsFragment" />         
    </transition>
</view-state>
           
4.5. render 액션 실행하기

on-render 엘리먼트를 사용하여 뷰 랜더링을 하기전에 한 개 이상의 액션을 실행한다. 뷰의 일부만 다시 랜더링하는 것을 포함하여 랜더 액션은 이후의 리프래시와 마찬가지로 초기 랜더링 단계에 실행된다.

<on-render>
    <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
       
4.6. 모델로 바인딩하기

model 속성을 사용하여 뷰를 바인딩할 모델 객체를 선언한다. 이 속성은 보통 폼 같은 데이터 컨트롤을 랜더링하는 뷰랑 같이 사용한다. 폼 데이터 바인딩과 검증 작업을 모델 객체에 대한 메타데이터로 주도할 수 있다.

다음 예는 booking 모델을 다루는 enterBookingDetails State다.

<view-state id="enterBookingDetails" model="booking">

모델은 flowScope 또는 viewScope 같이 접근 가능한 모든 스코프에 있는 객체다. model을 정의하면 뷰 이벤트가 발생할 때 다음 작업이 이뤄진다.
  1. 뷰에서 모델로 바인딩. 뷰 포스트백(postback)을 할 때 사용자 입력 값을 모델 객체 속성으로 바인딩한다.
  2. 모델 검증. 바인딩을 한 다음, 만약 모델 객체가 검증을 필요로 하면, 검증 로직을 호출한다.
뷰 상태 전이를 할 수 있는 플로우 이벤트가 발생할 때, 모델 바인딩은 반드시 성공적으로 완료되어야 한다. 만약 바인딩이 실패하면, 뷰는 다시 랜더링하여 사용자가 다시 편집할 수 있게 한다.

4.7. 타입 변환 수행하기

뷰 포스트백을 할 때 모델 바인딩이 발생하면, 바인딩 시스템은 필요하다면 입력 값을 타겟 모델 속성 타입으로 변환을 시도할 것이다. 숫자들과 기본타입, 이넘, 그리고 Date에 대한 기본 컨버터(Converter)는 자동으로 적용된다. 사용자는 또한 자신들이 직접 정의한 타입에 대한 컨버터를 등록할 수 있고 기본 컨버터를 재정의 할 수도 있다.

4.7.1. 컨버터 구현하기

컨버터를 구현하려면, org.springframework.binding.convert.converters.TwoWayConverter 인터페이스를 구현하라. 입력값으로 문자열을 받아서 사용자가 정의한 객체로 변경하는 컨버터를 만들 때 이 인터페이스 구현을 편리하게 해주는 기본 클래스 StringToObject를 제공한다. 간단하게 이 클래슬르 상속하고 두 개의 매서드를 재정의하면 된다.

protected abstract Object toObject(String string, Class targetClass) throws Exception;

protected abstract String toString(Object object) throws Exception;           
          
toObject(String, Class)는 입력 문자열을 객체 타입으로 바꿔야 하고 toString(Object)는 그 반대 일을 해야 한다.

다음 예제는 문자열을 MonetaryAmount로 변환하는 컨버터다.

public class StringToMonetaryAmount extends StringToObject {

  public StringToMonetaryAmount() {
      super(MonetaryAmount.class);
  }

  @Override
  protected Object toObject(String string, Class targetClass) {
      return MonetaryAmount.valueOf(string);
  }

  @Override
  protected String toString(Object object) {
      MonetaryAmount amount = (MonetaryAmount) object;
      return amount.toString();
  }
}           
  
더 많은 컨버터 구현 예제를 보고 싶다면 org.springframework.binding.convert.converters 패키지에 미리 만들어둔 컨버터들을 참고하라.

4.7.2. 컨버터 등록하기

여러분이 작성한 컨버터를 등록하거나 기본 컨버터를 변경하고 싶다면, org.springframework.binding.convert.service.DefaultConversionService를 상속하고 addDefaultConverters() 매서드를 재정의 하라. addConverter(Converter) 매서드를 사용하여 String과 MonetaryAmount 같은 두 타입을 변환할 때 사용할 주요 컨버터(Primary Converter)를 등록하라. 부가적으로 addConverter(String, Converter)를 사용하여 같은 타입에 대한 여러 대체 컨버터(Alternate Converter)를 등록할 수 있다. 예를 들어, java.util.Date를 여러 형태의 문자열로 변환할 수 있겠다.

대체 컨버터는 유일한 converterId로 인덱싱을 하는데 모델 바인딩을 설정할 때 참조할 수 있다. converterId를 지정하지 않으면 주요 컨버터를 사용한다.

ConversionService는 웹 플로우가 런타임에 어떤 타입을 다른 타입으로 변환 해주는 변환 실행기를 룩업할 때 사용하는 객체다. 애플리케이션 당 하나의 ConversionService가 있다. System Setup  절을 참조하여 애플리케이션 전반에 걸쳐 커스텀 컨버터를 등록하는 ConversionService 구현체를 설정하는 방법을 살펴보라. 또한 자세한 내용은 Convert API를 참고하라.

4.8. 바인딩 방지하기

bind 속성을 사용하여 특정 뷰 이벤트에 대한 검증과 모델 바인딩을 방지할 수 있다. 다음 예는 cancel 이벤트가 발생했을 때 바인딩을 방지한다.

<view-state id="enterBookingDetails" model="booking">
    <transition on="proceed" to="reviewBooking">
    <transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>

4.9. 명시적으로 바인딩 설정하기

binder 엘리먼트를 사용하여 뷰에서 사용할 수 있는 모델 바인딩 집합을 설정할 수 있다. 이것은 특히 스프링 MVC 환경에서 뷰 마다 "허용하는 입력값" 집합을 제한할 때 유용하다.

<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="creditCard" />
        <binding property="creditCardName" />
        <binding property="creditCardExpiryMonth" />
        <binding property="creditCardExpiryYear" />
    </binder>
    <transition on="proceed" to="reviewBooking" />
    <transition on="cancel" to="cancel" bind="false" />
</view-state>

 만약에 binder 엘리먼트를 설정하지 않으면 모델의 모든 public 속성이 뷰에서 바인딩하는 대상이 된다. binder 엘리먼트를 사용하여 오직 명시적으로 표현한 속성만 바인딩 한다.

각각의 바인딩은 컨버터를 적용하여 모델 속성 값을 사용자에게 익숙한 형태로 변경할 수 있을 것이다. 만약 컨버터를 명시하지 않으면 모델 속성 타입에 따라 기본 컨버터를 사용하게 된다.

<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="checkinDate" converter="shortDate" />
        <binding property="checkoutDate" converter="shortDate" />   
        <binding property="creditCard" />
        <binding property="creditCardName" />
        <binding property="creditCardExpiryMonth" />
        <binding property="creditCardExpiryYear" />
    </binder>
    <transition on="proceed" to="reviewBooking" />
    <transition on="cancel" to="cancel" bind="false" />
</view-state>

위의 예제에서 shortDate 컨버터를 chekingDate와 checkoutDate 속성에 적용하고 있다. 커스텀 컨버터를 애플리케이션의 ConventionService로 등록할 수도 있다.

각각의 바인딩은 또한 필수 여부를 확인하여 사용자가 입력한 값이 비어있다면 검증 에러를 발생시키도록 할 수 있다.

<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="checkinDate" converter="shortDate" required="true" />
        <binding property="checkoutDate" converter="shortDate" required="true" />
        <binding property="creditCard" required="true" />
        <binding property="creditCardName" required="true" />
        <binding property="creditCardExpiryMonth" required="true" />
        <binding property="creditCardExpiryYear" required="true" />
    </binder>
    <transition on="proceed" to="reviewBooking">
    <transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>

위의 예제에서 모든 바인딩을 필수로 설정했다. 만약 바인딩 할 입력값이 하나라도 비었다면 에러를 만들고 뷰를 에러와 함께 다시 보여줄 것이다.

4.10. 모델 검증하기

모델 검증은 도델 객체에 기반하여 설정한 제약사항으로 주도한다. 웹 플로우는 그러한 제약 사항을 프로그래밍으로 제공한다.

4.10.1. 프로그래밍적인 검증

모델 검증을 프로그래밍적으로 수행하는 두 가지 방법이 있다. 하나는 검증 로직을 모델 객체 내부에 구현하는 것이다. 두 번째는 별도의 Validator를 구현하는 것이다. 두 가지 방법 모두 ValidationContext를 사용하여 에러 메시지를 기록하고 현재 사용자 정보에 접근할 수 있다.

4.10.1.1. 모델 검증 매서드 구현하기

모델 객체 내부에 검증 로직을 정의하는 것은 그 상태를 검증하는 가장 간단한 방법이다. 한 번 이런 로직을 웹 플로우 규약에 맞게 만들어 두면 웹 플로우가 자동으로 그 로직을 view-state 포스트백 라이프사이클 동안에 호출할 것이다. 웹 플로구 규약은 뷰에서 수정하는 모델 속성 서브셋을 쉽게 검증할 수 있게 해준다. 이렇게 하려면 간단하게 validate${state} 형태의 이름을 가진 public 매서드를 만들면 된다. ${state}는 검증을 수행할 view-state의 id다. 예를 보자.

public class Booking {
    private Date checkinDate;
    private Date checkoutDate;
    ...
       
    public void validateEnterBookingDetails(ValidationContext context) {
        MessageContext messages = context.getMessages();
        if (checkinDate.before(today())) {
            messages.addMessage(new MessageBuilder().error().source("checkinDate").
                defaultText("Check in date must be a future date").build());
        } else if (!checkinDate.before(checkoutDate)) {
            messages.addMessage(new MessageBuilder().error().source("checkoutDate").
                defaultText("Check out date must be later than check in date").build());
        }
    }
}

위 예제에서, Booking 모델을 편집하는 enterBookingDetails view-state로 전이가 발생하면 웹 플로우는 해당 전이에 검증을 방지하지 않았다면 validateEnterBookingDetails(ValidationContext) 매서드를 자동으로 호출한다. 그런 view-state 예제는 아래와 같다.

<view-state id="enterBookingDetails" model="booking">
    <transition on="proceed" to="reviewBooking">
</view-state>
       
모든 검증 메소드를 정의했다. 일반적으로 플로우는 모델을 여러 뷰에서 편집한다. 이 경우에 검증을 수행할 필요가 있는 각각의 view-state에 대한 검증 매서드를 정의했을 것이다.

4.10.1.2. Validator 구현하기

두 번째 방법은 모델 객체를 검증할 Validator라고 하는 별도의 객체를 정의하는 것이다. 그렇게 하려면 우선 ${model}Validator 패턴의 이름을 가진 클래스를 만든다. ${model}은 booking 같은 모델 표현식 형태를 띈다. 그런 다음 validate${state} 형태의 이름을 가진 매서드를 정의한다. ${state}는 enteringBookingDetails같은 view-state id에 해당한다. 클래스는 스프링 빈으로 배포될 수 있어야 한다. 여러 검증 매서드를 정의할 수 있다, 예제를 보자.

@Component
public class BookingValidator {
    public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
        MessageContext messages = context.getMessages();
        if (booking.getCheckinDate().before(today())) {
            messages.addMessage(new MessageBuilder().error().source("checkinDate").
                defaultText("Check in date must be a future date").build());
        } else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
            messages.addMessage(new MessageBuilder().error().source("checkoutDate").
                defaultText("Check out date must be later than check in date").build());
        }
    }
}
               
위 예제에서 Booking 모델을 편집하는 enterBookingDetilas view-state로 전이가 발생하면 웹 플로우는 validateEnterBookingDetails(Booking, ValidationContext) 매서드를 자동으로 호출할 것이다. 단 이때 해당 전이에 대한 검증을 방지한 상태가 아니어야 한다.

또한 Validator는 스프링 MVC의 Errors 객체로 받을 수 있다. 이 객체는 기존의 스프링 검증기를 호출할 때 필수다.

검증기는 스프링 빈으로 등록되어야 하고 ${model}Validator 라는 이름 규약을 지켜야 한다 그럼 자동으로 감지되고 호출된다. 위 예제에서 스프링 2.5 클래스패스-스캔을 하게 되면 @Component를 감지하고 자동으로 bookingValidator라는 이름의 빈으로 등록이 된다. 그러면 bokking 모델 검증이 필요할 때 이 bookingValidator 인스턴스가 호출될 것이다.

4.10.2. ValidationContext

ValidationContext는 MessageContext를 가져와서 검증을 하는 동안 메시지를 기록한다. 또한 userEvent와 현재 사용자의 Principal 같이 현재 사용자에 대한 정보를 공개한다. 이러한 정보는 검증 로직을 접속한 사용자 또는 UI의 어떤 버튼 또는 링크에 기반하여 커스터마이징 할 수 있도록 해준다. 자세한 정보는 ValidationContext API를 참고하라.

4.11. 검증 방지하기

validate 속성을 사용하여 특정 뷰 이벤트에 대한 모델 검증을 방지할 수 있다.

<view-state id="chooseAmenities" model="booking">
    <transition on="proceed" to="reviewBooking">
    <transition on="back" to="enterBookingDetails" validate="false" />
</view-state>
       
위 예제는 back 이벤트가 발생할 경우 바인딩은 하지만 검증은 하지 않는다.

4.12. 뷰 전이 실행하기

하나 또는 그 이상의 transition 엘리먼트를 정의하여 뷰에서 발생하는 사용자 이벤트를 다룰 수 있다. 트랜지션은 사용자한테 다른 뷰를 보여주거나 단순하게 액션을 실행하거나 현재 뷰를 다시 보여주는 일을 한다. 또한 트랜지션은 애이작스(Ajax)를 다룰 때 프레그먼츠("fragments")라고 하는 뷰의 일부를 다시 보여주길 요청할 수도 있다. 마지막으로 글로벌("global") 트랜지션이라고 하는 여러 뷰에 걸쳐서 공유하는 것을 정의할 수도 있다.

뷰 트랜지션을 정의하는 방법을 다음 절에서 살펴보도록 하자.

4.12.1. 트랜지션 액션

view-state 트랜지션을 수행하기 전에 하나 또는 그 이상의 액션을 실행할 수 있다. 이러한 액션은 아마 현재 view-state를 종료하지 못하게 에러 결과를 반환할 수도 있다. 만약 에러 결과가 있다면, 뷰는 다시 보여질 것이며 사용자에게 적절한 메시지를 보여줄 것이다.

만약 트랜지션 액션이 일반 자바 매서드를 호출하면, 호출된 매서드는 false를 반환하여 트랜지션 실행을 방지할 수 있다. 이런 기술은 서비스-계층 매서드에서 발생한 예외를 다룰 때 사용할 수 있겠다. 아래 예제는 서비스를 호출하는 액션을 호출하고 예외 상황을 다룬다.

<transition on="submit" to="bookingConfirmed">
    <evaluate expression="bookingAction.makeBooking(booking, messageContext)" />
</transition>
           

public class BookingAction {
   public boolean makeBooking(Booking booking, MessageContext context) {
       try {
           bookingService.make(booking);
           return true;
       } catch (RoomNotAvailableException e) {
           context.addMessage(builder.error().
               .defaultText("No room is available at this hotel").build());
           return false;
       }
   }
}
           
4.12.2. Global 트랜지션

플로우의 global-transitions 엘리먼트를 사용하여 여러 뷰에 걸쳐 적용할 트랜지션을 생성한다, Global-transition은 종종 레이아웃을 구성하는 메뉴 링크 같은 것을 다룰 때 사용한다.

<global-transitions>
    <transition on="login" to="login">
    <transition on="logout" to="logout">
</global-transitions>
           
4.12.3. 이벤트 핸들러

view-state에서 타겟이 없는 트랜지션을 정의할 수도 있다. 이러한 트랜지션을 "이벤트 핸들러"라고 한다.

<transition on="event">
    <!-- Handle event -->
</transition>
       
이런 이벤트 핸들러는 플로우의 상태를 변경하지 않는다. 단지 그들의 액션을 실행하고 현재 뷰 또는 하나 이상의 프레그먼트를 다시 보여준다.

4.12.4. 프레그먼트 다시 보여주기

트랜지션에서 render 엘리먼트를 사용하여 이벤트를 처리한 다음 현재 뷰에서 일부분만 다시 보여주도록 요청할 수 있다.

<transition on="next">
    <evaluate expression="searchCriteria.nextPage()" />
    <render fragments="searchResultsFragment" />           
</transition>
           
fragments 속성은 다시 보여주길 원하는 뷰 엘리먼트(들)의 id(여러개)가 될 것이다. 여러 엘리먼트를 다시 보여주도록 설정하려면 콤마로 구분하여 적으면 된다.

이렇게 일부를 다시 보여주는 기법은화면의 특정 부위를 업데이트하는 Ajax에 의한 이벤로 사용된다.

4.13. 메시지 다루기

스프링 웹 플로우의 MessageContext는 플로우 실행 도중에 메시지를 기록하기 위한 API다. 일반 텍스트 메시지는 물론이고 스프링 MessageSource를 사용하는 국제화 메시지도 추가할 수 있다. 메시지는 뷰에서 보여지며 자동으로 플로우 실행 리다이렉트에도 살아남을 것이다. 세 종류의 메시지를 제공한다. info, warning, error다. 또한, 편리한 MessageBuilder를 제공하여 메시지 작성을 돕고 있다.

4.13.1. 일반 텍스트 메시지 추가하기

MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate")
    .defaultText("Check in date must be a future date").build());
context.addMessage(builder.warn().source("smoking")
    .defaultText("Smoking is bad for your health").build());
context.addMessage(builder.info()
    .defaultText("We have processed your reservation - thank you and enjoy your stay").build());

4.13.2. 국체화 메시지 추가하기

MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build());
context.addMessage(builder.warn().source("smoking").code("notHealthy")
    .resolvableArg("smoking").build());           
context.addMessage(builder.info().code("reservationConfirmation").build());
           
4.13.3. 메시지 번들 사용하기

국제화 메시지는 스프링 MessageSource로 접근하는 메시지 번들에 정의한다. 플로우-관련 메시지 번들을 만들려면, 간단하게 message.properties 파일을 플로우 디렉터리에 만든다. messages.properties 파일을 만들고 지원하고자 하하는 지역 Locale에 해당하는 .properties 파일을 각각 추가한다.

#messages.properties
checkinDate=Check in date must be a future date
notHealthy={0} is bad for your health
reservationConfirmation=We have processed your reservation - thank you and enjoy your stay
           
뷰 또는 플로우에서 여러분은 resourceBundle EL 변수를 사용하여 메시지 번들에 접근할 수도 있다.

<h:outputText value="#{resourceBundle.reservationConfirmation}" />
           
4.13.4. 시스템이 생성한 메시지 이해하기

웹 플로우 자체가 사용자에게 보여줄 메시지를 생성하는 몇몇 부분이 있다. 이런 일이 발생하는 중요한 지점 중 하나는 뷰에서 모델로 데이터 바인딩을 할 때다. 변환 에러 같은 바인딩 에러가 발생하면 웹 플로우는 자동으로 에러를 여러분 리소스번들에서 가져온 메시지로 맵핑한다. 보여줄 메시지를 가져오려고 웹 플로우는 바인딩 에러 코드와 타겟 속성 이름을 담고 있는 리소스 키를 시도한다.

아래 예제는 Booking 객체의 checkinDate 속성에 바인딩을 생각해보고 있다. 사용자가 알파벳 문자열을 입력했다고 하자. 이런 경우 타입 변환 에러가 발생할 것이다. 웹 플로우는 "typeMismatch' 에러 코드를 여러분 리소스 번들 중에 있는 메시지를 찾을 때 다음 키를 사용할 것이다.

booking.checkinDate.typeMismatch

키의 앞 부분은 모델 클래스의 짧은 이름이다. 두 번째 부분은 속성의 이름이다, 세 번째 부분은 에러 코드다. 모델 속성에 바인딩이 실패하면 사용자에게 보여줄 유일한 메시지를 이런식으로 찾는다. 아마 다음과 같은 메시지일 수 있다.

booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.

만약 저 형식에 해당하는 키를 찾지 못하면, 보다 일반적인 키로 시도해본다. 이 키는 간단한 에러 코드 형태다. 속성의 플드 이름은 메시지 인자로 제공된다.

typeMismatch=The {0} field is of the wrong type.
           
4.14. 팝업 보여주기

popup 속성을 사용하여 모델 팝업 창에서 뷰를 랜더링 한다.

<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
       
스프링 자바스크립트랑 같이 웹 플로우를 사용하면, 팝업을 보여줄 때 클라이언트 쪽 코드를 사용할 필요가 없다. 웹 플로우는 팝업에서 뷰로 리다이렉트를 요청하는 사용자에게 응답을 보낼 것이고 그 결과 클라이언트는 요청은 잘 처리될 것이다.

4.15. 뷰 백트랙킹(backtracking)

기본적으로, 뷰 스테이트를 종료하고 새로운 뷰 스테이트로 전이하면, 여러분은 브라우저의 뒤로 가기 버튼을 사용하여 이전 상태로 이동할 수 있다. 이러한 뷰 스테이트 히스토리 정책은 history 속성을 사용하여 트랜지션 당 설정할 수 있다.

4.15.1. 히스토리 방지하기

history 속성을 discard로 설정하여 뷰 백트랙킹을 방지할 수 있다.

<transition on="cancel" to="bookingCancelled" history="discard">

4.15.2. 히스토리 금지하기

history 속성을 invalidate로 설정하여 뷰 백트랙킹을 방지하고 이전의 뷰로 전이하는 것도 방지한다.

<transition on="confirm" to="bookingConfirmed" history="invalidate">
           

'Spring Web Flow > Chapter 4' 카테고리의 다른 글

SWF 4장 뷰 랜더링  (0) 2009.01.30
top


SWF 3장 EL(Expression Language)



참조 및 번역: http://static.springframework.org/spring-webflow/docs/2.0.x/reference/html/index.html

3.1. 소개

웹 플로우는 EL을 사용하여 데이터 모델에 접근하며 액션을 호출한다. 이번 장에서 여러분은 EL 문법과 플로우 정의에서 참조할 수 있는 특별한 EL 변수들에 익숙해질 것이다.

3.2. 지원하는 EL 구현체

3.2.1. Unified EL

웹 플로우는 기본으로 Unified EL 사용을 시도한다. jboss-el이 현재 기본 EL 구현체이다. 여러분의 클래스패스에서 el-api을 발견하면 자동적으로 그것을 사용한다. JBoss EL jar 파일은 스프링소스 번들 저장소에서 찾을 수 있다.

노트

el-api 의존성은 보통 웹 컨테이너에서 제공하는 provided다. 예를 들어 톰캣 6는 그것을 가지고 있다.

3.2.2. OGNL

OGNL은 웹 플로우 2가 지원하는 또다른 EL이다. OGNL은 웹 플로우 버전 1.0 사용자에게 가장 익숙한 EL이다. ognl을 사용하려면, jboss-el 대신에 ognl을 클래스패스에 추가하면 된다. 문법을 익히려면 OGNL 레퍼런스 가이드를 참조하라.

3.3. EL 이식성

보통, UEL(Unified EL)과 OGNL은 문법이 비슷하다. 기본 변수 인식, 프로퍼티 접근, 그리고 메소드 호출 문법이 동일하다. 우리는 가능하면 UEL을 사용할 것을 권장하며 필요한 경우에만 별도의 EL 기능을 사용하라.

3.4. EL 사용법

EL은 플로우에서 여러 경우에 사용할 수 있다.
  1. 플로우 input 속성이나 request 매개변수 같은 클라이언트가 제공하는 데이터에 접근하기.
  2. flowScope 같은 내부 데이터 구조에 접근하기
  3. 스프링 빈의 메소드 호출하기
  4. 상태 전이 criteria, 하위 플로우 id 그리고 뷰 이름 같은 구조 인식하기
플로우에 의해 보여지는 뷰는 일반적으로 EL을 사용하여 플로우 데이터 구조에 접근한다.

3.4.1. 표현식 타입

웹 플로우에는 기본적으로 두 종류의 표현식이 있다.

3.4.1.1. 표준 평가 표현식(standard eval expressions)

처음으로 살펴볼 것은 가장 흔히 사용하는 표현식 타입으로 eval 표현식이다. 이 표현식은 EL에 의해 동적으로 평가되고 ${} 나 #{} 같은 구분자로 감싸지 않는다. 예제를 보자.

<evaluate expression="searchCriteria.nextPage()" />
       
위의 표현식은 값을 구해올 때 searchCriteria 변수의 nextPage라는 메소드를 호출하는 표준 표현식이다. 이 표현식을 ${} 나 #{} 같은 구분자로 감싸면 IllegalArgumentException 예외가 발생한다.

노트

이 상황에서 별도의 평가 구분자를 사용하는 것은 필요없다고 본다. 따라서 오직 expression 속성에 사용할 수 있는 값은 평가 표현식 문자열이다.

3.4.1.2. 템플릿 표현식

두 번째 표현식 타입은 "템플릿" 표현식이다. 이런 표현식은 문자열을 한 개 또는 여러 평가 블럭으로 혼합하는 것을 허용한다. 각각의 평가 블럭은 ${} 구분자를 사용하여 명시적으로 구분한다. 예제를 보자.

<view-state id="error" view="error-${externalContext.locale}.xhtml" />
               
위의 표현식은 템플릿 표현식이다. view 속성 값의 결과는 error- 뒤에 externalContext.locale 값을 합친 문자열이 될 것이다. 보시다시피 여기서는 템플릿 내에 평가 블럭을 구분하기 위해 명시적인 구분자가 필요하다.

웹 플로우 XML 스키마를 참조하여 표준 표현식과 템플릿 표현식에서 사용할 수 있는 XML 속성들을 전부 확인할 수 있다.

3.5. 특별한 EL 변수

플로우 안에서 참조할 수 있는 암묵적인 변수들 몇 개가 있다. 이번 절에서 이런 변수들을 논의할 것이다.

3.5.1. flowScope

flowScope를 사용하여 플로우 변수를 할당한다. 플로우 스코프는 플로우가 시작할 때 할당되고 플로우가 끝날 때 소멸된다. 기본 구현에 따라, 플로우 스코프에 저장된 모든 객체는 직렬화 할 수 있어야 한다.

<evaluate expression="searchService.findHotel(hotelId)" result="flowScope.hotel" />

3.5.2. viewScope

viewScope를 사용하여 뷰 변수를 할당한다. 뷰 스코프는 view-state에 들어갈 때 할당되고 상태가 종료되면 소멸된다. 뷰 스코프는 오직 view-state에서만 참조할 수 있다. 기본 구현체 따라, 뷰 스코프에 저장된 모든 객체는 질렬화 할 수 있어야 한다.

<on-render>
    <evaluate expression="searchService.findHotels(searchCriteria)" result="viewScope.hotels"
              result-type="dataModel" />
</on-render>

3.5.3. requestScope

requestScope를 사용하여 요청 변수를 할당한다. 요청 스코프는 플로우를 호출한 순간에 할당되고 플로우가 반환을 하면 소멸된다.

<set name="requestScope.hotelId" value="requestParameters.id" type="long" />

3.5.4. flashScope

flashScope를 사용하여 플래시 변수를 할당한다. 플래시 스코프는 플로우가 시작될 때 할당되고, 모든 뷰 랜더링을 마친뒤에 클리어하고 플로우가 끝날 때 소멸한다. 기본 구현에 따라, 플래시 스코프에 저장된 모든 객체는 직렬화 할 수 있어야 한다.

<set name="flashScope.statusMessage" value="'Booking confirmed'" />   

3.5.5. conversationScope

conversationScope를 사용하여 컨버세이션 변수를 할당한다. 컨버세이션 스코프는 최상위 플로우가 시작할 때 할당되고 최상위 플로우가 종료되면 소멸한다. 컨버세이션 스코프는 최상위 플로우와 모든 하위플로우에서 공유한다. 기본 구현에 따라, 컨버세이션 스코프에 저장하는 객체는 HTTP session에 저장하고 일반 세션 복사를 하기 위해 보통 직렬화 할 수 있어야 한다.

<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
           
3.5.6. requestParameters

requestParameters를 사용하여 클라이언트 요청 매개변수에 접근한다.

<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
           
3.5.7. currentEvent

currentEvent를 사용하여 현재 이벤트 속성에 접근한다.

<evaluate expression="booking.guests.add(currentEvent.guest)" />
           
3.5.8. currentUser

currentUser를 사용하여 로그인한 사용자 정보(authenticated Principal)에 접근한다.

<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
          result="flowScope.booking" />
           
3.5.9. messageContext

messageContext를 사용하여 플로우 에러나 성공 메시지를 포함한 플로우 실행 메시지를 생성하고 가져오는데 사용할 컨텍스트에 접근한다.MessageContext Javadoc에서 자세한 내용을 참조하라.

<evaluate expression="bookingValidator.validate(booking, messageContext)" />
           
3.5.10. resourceBundle

resourceBundle을 사용하여 메시지 리소스에 접근하라.

<set name="flashScope.successMessage" value="resourceBundle.successMessage" />
           

3.5.11. flowRequestContext

flowRequestContext를 사용하여 RequestContext API에 접근하라. 이것은 현재 플로우 요청을 나타낸다.자세한 내용은 API Javadoc을 참조하라.

3.5.12. flowExecutionContext

flowExecutionContext를 사용하여 FlowExecutionContext API에 접근하라. 이것은 현재 프로우 상태를 나타낸다. 자세한 내용은 API Javadoc을 참조하라.

3.5.13. flowExecutionUrl

flowExecutionUrl을 사용하여 현재 플로우 실행 view-state에 대한 컨텍스트 관련 URI에 접근하라.

3.5.14. externalContext

externalContext를 사용하여 사용자 세션 속성같은 클라이언트 환경에 접근하라. 자세한 내용은 ExternalContext API JavaDoc을 참조하라.

<evaluate expression="searchService.suggestHotels(externalContext.sessionMap.userProfile)"
          result="viewScope.hotels" />
           
3.6. Scope 검색 알고리즘

변수를 어떤 플로우 스코프에 할당할 때, 해당 스코프를 참조하는 것이 필요하다. 예제를 보자.

<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
       
어떤 스코프에 들어있는 변수에 단순하게 접근할 때는, 스코프를 반드시 참조하지 않아도 된다. 예제를 보자.

<evaluate expression="entityManager.persist(booking)" />
       
위에서 booking을 사용한것처럼 만약 스코프를 적지 않으면 스코프 검색 알고리즘이 적용된다. 알고리즘은 request, flash, view, flow, conversation 스코프에서 변수를 찾아본다. 만약 해당 변수를 찾지 못하면 EvaluationException 예외를 던진다.


'Spring Web Flow > Chapter 3' 카테고리의 다른 글

SWF 3장 EL(Expression Language)  (0) 2009.01.08
top


SWF 2장 플로우 정의하기



참조: http://static.springframework.org/spring-webflow/docs/2.0.x/reference/html/index.html

2.1. 개요

이번 장은 User Section으로 시작한다. 플로우 정의 언어(flow definition language)를 사용하여 플로우를 구현하는 방법을 살펴볼 것이다. 이번 장이 끝날 때 여러분은 언어 구조를 이해하고 플로우를 정의할 수 있을 것이다.

2.2 플로우란 무엇인가?

플로우는 여러 문맥에서 실행될 수 있는 재사용 가능한 스탭의 연속체를 캡슐화 한 것이다. 아래 그림은 Garrett Inforamtion Architecture 다이어그램으로 호텔 예약 프로세스의 단계를 캡슐화한 플로우를 보여주고 있다.

사용자 삽입 이미지

플로우를 나타내는 사이트 맵

2.3. 일반적인 플로우의 구성요소는 무엇인가?

스프링 웹 플로우에서, 플로우는 스테이트("states")라고 부르는 스탭의 일련으로 구성한다. 하나의 스테이트로 들어가면 보통 사용자에게 어떤 뷰 하나를 보여주게 된다. 해당 뷰에서, 스테이트가 처리할 사용자 이벤트가 발생한다. 이런 이벤트는 다른 스테이트로 이동(transition)을 시켜서 뷰 네이게이션을 하게 한다.

아래 예제는 앞선 다이어그램에서 살펴본 호텔 예약 플로우 구조를 보여주고 있다.

사용자 삽입 이미지
플로우 다이어그램

2.4. 어떻게 플로우를 작성하는가?

플로우는 웹 애플리케이션 개발자들이 간단한 XML 기반 플로우 정의 언어를 사용하여 작성한다. 이번 가이드의 다음 단계에서 이 언어의 구성 요소를 살펴보겠다.

2.5. 기본적인 언어 구성 요소

2.5.1 플로우(flow)

모든 플로우는 다음의 루트 엘리먼트로 시작한다.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

</flow>

플로우의 모든 스테이트(state)를 이 엘리먼트 안에 정의한다. 처음에 정의한 스테이트가 플로우의 시작점이 된다.

2.5.2. 뷰-스테이트(view-state)

view-state 엘리먼트를 사용하여 플로우에서 뷰를 보여주는 단계를 정의한다.

<view-state id="enterBookingDetails" />

기본적으로(by convention), 뷰-스테이트는 자신의 id를 플로우가 위치한 디렉터리의 뷰 템플릿으로 맵핑한다. 예를 들어, 위의 스테이트는 만약 플로우가 /WEB-INF/hotels/booking 디렉터리에 있었다면, /WEB-INF/hotels/booking/enterBookingDetails.xhtml을 사용자에게 보여줄 것이다.(rendering)

2.5.3. 트랜지션(transition)

transition 엘리먼트를 사용하여 스테이트에서 발생한 이벤트를 처리한다.

<view-state id="enterBookingDetails">
    <transition on="submit" to="reviewBooking" />
</view-state>
       
이런 트랜지션이 뷰 네이게이션을 끌어낸다.

2.5.4. end-state

end-state 엘리먼트를 사용하여 플로우 종료를 정의한다.

<end-state id="bookingCancelled" />

플로우 트랜지션이 종료-상태에 다다르면 플로우를 끝내고 결과를 반환한다.

2.5.5. 체크포인트: 기본 언어 구성 요소

세 개의 구성 요소 view-state, transition, end-state를 가지고 여러분은 빠르게 여러분의 뷰 네비게이션 로직을 표현할 수 있다. 팀에서 플로우 행동을 추가하기 전에 이 작업을 해봄으로써 먼저 사용자와 함께 애플리케이션 UI를 개발에 집중할 수 있다. 아래 예제는 이들 엘리먼트를 사용하여 뷰 네비게이션 로직을 구현한 것이다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>
   
    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>
   
    <end-state id="bookingConfirmed" />

    <end-state id="bookingCancelled" />
       
</flow>   

2.6. 액션(Actions)

대부분의 플로우는 단순히 뷰 네비게이션 로직 보다 더 많은 것을 표현할 필요가 있다. 일반적으로 애플리케이션의 비즈니스 서비스 또는 다른 액션을 호출할 필요가 있다.

플로우에서 여러분이 액션을 실행할 수 있는 몇몇 포인트(point)가 있다.
  • On flow start
  • On state entry
  • On view render
  • On transition execution
  • On state exit
  • On flow end
액션은 간결한 EL(expression language)로 정의한다. 스프링 웹 플로우는 기본적으로 UEL(Unified EL)을 사용한다. 다음의 몇몇 절에서 액션을 정의할 때 사용하는 기본 언어 구성 요소를 살펴보겠다.

2.6.1. evaluate

여러분이 사용할 대부분의 액션 엘리먼트는 evaluate 엘리먼트일 것이다. evaluate 엘리먼트를 사용하여 플로우의 어떤 순간(point)에 표현식의 값을 구할 수 있다. 이 태그 하나로 스프링 빈 또는 다른 플로우 변수의 메소드를 호출할 수 있다. 예를 들어 다음과 같이 사용할 수 있다.

<evaluate expression="entityManager.persist(booking)" />

2.6.1.1. evaluate 결과값 대입하기

만약 표현식이 값을 반환한다면, 그 값을 플로우의 데이터 모델 flowScope에 저장할 수 있다.

<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />

2.6.1.2. evaluate 결과값 변경하기

만약 표현식이 반환하는 값이 컨버팅이 필요하다면, 원하는 타입을 result-type 속성에 명시할 수 있다.

<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels"
          result-type="dataModel"/>
               
2.6.2. 체크포인트: 플로우 액션

이제 예제 예약 플로우에 액션을 추가해보자.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>
   
    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>
   
    <end-state id="bookingConfirmed" />

    <end-state id="bookingCancelled" />
       
</flow>   

이 플로우는 이제 플로우가 시작되면 Booking 객체를 플로우에 생성한다. 예약하려는 호텔의 id는 플로우 input 속성에서 얻어온다.

2.7. Input/Output 맵핑

각각의 플로우는 잘 정의되어 있는 input/output 제약을 가지고 있다. 플로우를 시작할 때 input 속성에 flow를 넘겨줄 수 있고, 플로우가 끝날 때 flow를 output 속성에 넘겨줄 수 있다. 플로우를 호출하는 것은 다음 시그너처를 가진 메소드를 호출하는 것과 비슷하다.

FlowOutcome flowId(Map<String, Object> inputAttributes);

FlowOutcome은 다음과 같이 생겼다.

public interface FlowOutcome {
   public String getName();               
   public Map<String, Object> getOutputAttributes();
}

2.7.1. input

input 엘리먼트를 사용하여 플로우의 input 속성을 선언한다.

<input name="hotelId" />

입력한 값은 속성의 name으로 flow 스코프에 저장된다. 예를 들어, 위의 input은 hotelId라는 이름으로 저장될 것이다.

2.7.1.1. input 타입 선언하기

type 속성을 사용하여 input 속성의 타입을 선언할 수 있다.

<input name="hotelId" type="long" />

만약 input 값이 선언한 타입과 일치하지 않으면, 타입 전환을 시도한다.

2.7.1.2. input 값 대입하기

value 속성을 사용하여 input 값에 대입할 표현식을 기술할 수 있다.

<input name="hotelId" value="flowScope.myParameterObject.hotelId" />

type 속성을 기술하지 않아을 때, 표현식 값의 타입을 판별할 수 있다면 해당 메타데이터를 타입 제약으로 사용할 것이다.

2.7.1.3. input을 필수로 만들기

required 속성을 사용하여 input이 null이거나 빈값이 아니도록 강제할 수 있다.

<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />

2.7.2. output

output 엘리먼트를 사용하여 플로우 output 속성을 선언할 수 있다. output 속성은 특정 플로우 종료를 나타내는 end-states 내부에 선언한다.

<end-state id="bookingConfirmed">
    <output name="bookingId" /> 
</end-state>
       
output 값은 name 속성 값과 일치하는 것을 flow 스코프에서 가져온다. 예를 들어, 위의 output은 bookingId 변수의 값을 대입할 것이다.

2.7.2.1. output 값 기술하기

value 속성을 사용하여 output 값 표현식을 기술할 수 있다.

<output name="confirmationNumber" value="booking.confirmationNumber" /> 

2.7.3. 체크포인트: input/output 맵핑

이제 예제 예약 플로우에서 input/output 맵핑을 다시 살펴보자.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>
   
    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>
   
    <end-state id="bookingConfirmed" >
        <output name="bookingId" value="booking.id"/>
    </end-state>

    <end-state id="bookingCancelled" />
       
</flow>   

플로우는 hotelId라는 input 속성을 받고 새로운 예약이 확정되면 bookingId라는 output 속성을 반환한다.

2.8. 변수(Variables)

플로우는 한 개 이상의 변수를 선언할 수 있다. 이들 변수는 플로우가 시작할 때 자리를 잡는다. 변수들이 가지고 있는 모든 @Autowired 임시 레퍼런스들 또한 플로우가 재시작 할 때 다시 와이어링한다.

2.8.1. var

var 엘리먼트를 사용하여 플로우 변수를 선언한다.

<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>

여러 플로우 요청(request)에 걸쳐 인스턴스 상태를 저장할 때 해당 클래스가 java.io.Serializable을 구현했는지 확인하라.

2.9. subflow 호출하기

플로우에서 다른 플로우를 하위 플로우(subflow)로 호출할 수 있다. 플로우는 서브플로우가 끝날 때까지 기다렸다가 반환값이 오면 그때 서브 플로우 결과에 응답(response)을 한다.

2.9.1. subflow-state

subflow-state 엘리먼트를 사용하여 다른 플로우를 서브플로우로 호출하기

<subflow-state id="addGuest" subflow="createGuest">
    <transition on="guestCreated" to="reviewBooking">
        <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" /> 
    </transition>
    <transition on="creationCancelled" to="reviewBooking" />
</subfow-state>

위의 예제는 createGuest를 호출하고 있다. 그런 다음 반환되기를 기다린다. 플로우가 guestCreated라는 결과를 받으면, 새로운 guest를 booking guest 리스트에 추가한다.

2.9.1.1. subflow input 전달하기

input 엘리먼트를 사용하여 input을 서브플로우에 전달한다.

<subflow-state id="addGuest" subflow="createGuest">
    <input name="booking" />
    <transition to="reviewBooking" />
</subfow-state>

2.9.1.2. 서브플로우 output 맵핑하기

간단하게 서브플로우 output 속성을 결과 트랜지션에서 그 이름으로 참조할 수 있다.

<transition on="guestCreated" to="reviewBooking">
    <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" /> 
</transition>
       
위에서 guest는 guestCreated 결과에 의해 반환되는 output 속성 이름이다.

2.9.2. 체크포인트: 서브플로우 호출하기

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>

    <view-state id="reviewBooking">
        <transition on="addGuest" to="addGuest" />
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>

    <subflow-state id="addGuest" subflow="createGuest">
        <transition on="guestCreated" to="reviewBooking">
            <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" /> 
        </transition>
        <transition on="creationCancelled" to="reviewBooking" />
    </subfow-state>
       
    <end-state id="bookingConfirmed" >
        <output name="bookingId" value="booking.id" />
    </end-state>

    <end-state id="bookingCancelled" />
       
</flow>       

플로우는 이제 createGuest 서브플로우를 호출하여 새로운 게스트를 게스트 목록에 추가한다.

'Spring Web Flow > Chapter 2' 카테고리의 다른 글

SWF 2장 플로우 정의하기  (0) 2009.01.02
top


SWF Chapter 1



참조: 스프링 웹 플로우 레퍼런스 1 장

1. 소개

1.1 이 가이드가 다루고 있는 것

이 가이드는 스프링 웹 플로우와 관련된 모든 것을 다룬다. 최종-사용자 애플리케이션의 플로우를 구현하는 방법과 기능 들을 사용하는 방법을 다룬다. 또한 프레임워크를 확장하는 방법과 전체적인 아키텍처 모델도 다룬다.

1.2. 웹 플로우를 실행할 때 필요로 하는 것

자바 1.4 이상

스프링 2.5.4 이상

1.3 지원을 받을 수 있는 곳

생략

1.4. 개발을 주도 할 수 있는 곳

생략

1.5. 웹 플로우 구성 요소를 스프링소스 번들 저장소에서 얻는 방법

각각의 웹 플로우 배포판의 jar 파일들은 스프링소스 엔터프라이즈 번들 저장소에서 얻을 수 있다. 이 jar 파일들에 메이븐이나 Ivy 의존성 관리자를 사용하여 접근할 수 있다.

1.5.1. 메이븐으로 웹 플로우에 접근하기

메이븐을 사용하여 jar에 접근 하려면 다음 저장소를 메이븐 pom에 추가하라.

<repository>
    <id>com.springsource.repository.bundles.release</id>
    <name>SpringSource Enterprise Bundle Repository - SpringSource Releases</name>
    <url>http://repository.springsource.com/maven/bundles/release</url>
</repository>

<repository>
    <id>com.springsource.repository.bundles.external</id>
    <name>SpringSource Enterprise Bundle Repository - External Releases</name>
    <url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
           

그런 다음 다음의 의존성을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
그리고 만약 JSF(JavaServerFaces)를 사용하고 있다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

          
1.5.2. Ivy로 웹 플로우에 접근하기

Ivy를 사용하여 jar 파일에 접근하려면, 다음 저장소를 Ivy 설정에 추가하라.

<url name="com.springsource.repository.bundles.release">
    <ivy pattern="http://repository.springsource.com/ivy/bundles/release/
                  [organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
    <artifact pattern="http://repository.springsource.com/ivy/bundles/release/
                       [organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>

<url name="com.springsource.repository.bundles.external">
    <ivy pattern="http://repository.springsource.com/ivy/bundles/external/
                  [organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
    <artifact pattern="http://repository.springsource.com/ivy/bundles/external/
                       [organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
           

그런 다음 다음 의존성을 선언하라.

<dependency org="org.springframework.webflow" name="org.springframework.binding"
            rev="2.0.5.RELEASE" conf="compile->runtime" />
<dependency org="org.springframework.webflow" name="org.springframework.js"
            rev="2.0.5.RELEASE" conf="compile->runtime" />
<dependency org="org.springframework.webflow" name="org.springframework.webflow"
            rev="2.0.5.RELEASE" conf="compile->runtime" />

           

그리고 만약 JSF를 사용한다면 다음을 추가하라.

<dependency org="org.springframework.webflow" name="org.springframework.faces"
            rev="2.0.5.RELEASE" conf="compile->runtime" />

           
1.6. 메이븐 중앙 저장소에서 웹 플로우 구성 요소 얻는 방법

웹 플로우 배포판의 모든 jar 파일은 메이븐 중앙 저장소에서도 접근할 수 있다.

메이븐 중앙 저장소에 있는 웹 플로우 jar에 접근하려면 다음 의존성을 pom에 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
만약 JSF를 사용한다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
1.7. 나이틀리 빌드 얻는 방법

나이틀리 스냅샷 버전의 웹 플로우 트렁크는 스프링 소스 번들 저장소에서 이용할 수 있다. 스냅샷에 접근하려면 다음 저장소를 pom에 추가하라.

<repository>
    <id>com.springsource.repository.bundles.snapshot</id>
    <name>SpringSource Enterprise Bundle Repository - Nightly Snapshots</name>
    <url>http://repository.springsource.com/maven/bundles/snapshot</url>
</repository>
       
그리고 다음 의존성을 선언하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

           
그리고 만약 JSF를 사용한다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

       
위에서 '# # #'은 3.0.0.CI-500 같은 빌드 넘버를 나타낸 것이다. 최근 빌드 넘버를 알고 싶다면 나이틀리 빌드 영역을 참조하라.

'Spring Web Flow > Chapter 1' 카테고리의 다른 글

SWF Chapter 1  (0) 2008.12.27
top


Spring Web Flow 2.0 특징

Spring Web Flow/etc : 2008.04.26 11:11


참조 : http://www.infoq.com/news/2008/04/spring-webflow-2rc
예제 : http://richweb.springframework.org/swf-booking-faces/spring/intro

Web Flow 2.0의 새로운 기능은?

- Progressive Ajax:  Ajax 이벤트 핸들링 기능을 추가했다. 다른 웹 플로우 이벤트처럼 Ajax 이벤트를 처리할 수 있다. 자바스크립트를 클라이언트 쪽에서 사용하지 않아도 제대로 동작하도록해놨다. Ajax 지원 기능은 서버쪽 기능과 클라이언트쪽 기능 둘로 나눠져있다.

- Spring MVC와 더 긴밀한 통합: Spring MVC와 상호 보완 관계이다. Stateless한 Spring MVC @Controller가 Folw를 호출할 수 있고 Flow의 결과가 컨트로러에 맵핑될 수 있다.

- 재구성하고 확장된 JSF와의 통합: JSF 내용은 패스 저도 잘 모르기 땜시..

- Spring Security아의 통합: 플로우에 보안 기능을 추가할 수 있다. 플로우, 스테이트, 트랜지션에 @Secured를 애노테이션을 붙이면 SecurityFlowExecutionListener가 사용자의 인증을 요구한 뒤에 플로우를 시작하고, 스텝을 진행하고 이벤트를 시작할 것이다.

- 보다 간단한 Spring Folw Definition Language: 200줄 가량의 코드와 6개의 파일이 필요한 애플리케이션이 동일한 기능을 이제는 93라인의 코드와 2개의 파일로 할 수 있다.

- 새로운 모듈화 기능: 플로우의 개념을 "재사용 가능하며, 독립적인 애플리케이션 컨트롤 모듈"로 확장했다. 따라서 기본적으로 플로우와 그것이 사용하는 리소스인 뷰, 메시지, 헬퍼등을 하나로 패키징된다.

이번 배포의 배경이 되는 철학은 무엇인가?

- 사용성 편의다.

1.0에서 2.0으로 이전하는데 필요한 중대 변화는?

- Web Flow Definition Language다. 그래서 1.0 정의를 2.0으로 바꿔주는 기능을 제공한다. 일명 WebFlowUpgrader xnf.

스프링 웹 플로우를 지원하는 도구는?

- 스프링 IDE

앞으로 웹 플로우의 개발 방향과, 2.1 배포의 초점은?

- 선언적인 모델 검증. 애노테이션으로 Validation을 할 수 있도록.. 선언적으로 모델에 벨리데이션 규칙을 붙여두면, 그것을 서버 사이드와 클라이언트 사이드에 반영해주는..
- Grooby로 Flow Definition 작성하는 기능.

top