Whiteship's Note


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