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


오바마 포스터 스타일~ 오바미콘



http://obamiconme.pastemagazine.com/

위에 가셔서 만드시면 됩니다. 사용법은.. 간단해서 설명이 필요 없어 보입니다.


확대

신고
top


EJ2E Item 19. 인터페이스는 오직 타입을 정의할 때만 사용하라

Java : 2009.01.28 12:38


참조: Effective Java 2nd Edition

어떤 클래스가 인터페이스를 구현할 때 인터페이스는 해당 클래스의 인스턴스를 참조할 수 있는 타입을 제공한다. 즉 해당 클래스 인스턴스를 가지고 고객이 무엇을 할 수 있는지 알려주는 것이다. 이 경우 이외에 다른 의도로 인터페이스를 사용하는 것은 부적절하다.

상수 인터페이스(constant interface) 안티 패턴
- 매서드 없이 상수를 표현한 static final 필드만 있다.
- 그 상수를 사용하는 클래스는 해당 인터페이스를 구현하여 상수 이름을 클래스 이름으로 구분해야 할 필요를 없앤다.(A.C B.C 대신에 A와 B가 구현하는 인터페이스 Z로 C를 올려서 Z.C 형태로 사용하게 하는 건가보네요.)
- 클래스가 내부에서 어떤 상수를 사용하는지는 구체적인 내용에 해당하는데 클래스가 상수 인터페이스를 구현하면 구체적인 내용을 밖으로 노출하게 된다.
- 차후에 해당 상수가 필요없어지더라도 바이너리 호환성을 위해 해당 인터페이스를 그대로 구현하고 있어야 한다.
- 자바 라이브러리 중에 java.io.ObjectStreamConstants는 예외다.
- 상수를 다룰 땐 해당 상수가 속하는 클래스나 인터페이스를 잘 고려해야 하며 열거형이 아닌지 보고 열거형일 경우에는 enum type 사용을 고려하라.

상수를 자주 사용할 때는 static import를 사용할 수도 있겠다.

인터페이스로는 타입을 정의해야지 상수를 표현하지 않아야 한다.
신고
top

Java : 2009.01.28 12:38 Trackback. : Comment.

난 몇 살?

모하니?/Thinking : 2009.01.28 11:28



나이가 세 종류나 되며 각각의 계산 식도 있네요. 검색해본 글에 따르면 제 나이는..

기본정보
- 태어난 날: 1982년 7월 15일
- 오늘: 2009년 1월 26일

1. 만 나이(western age) = 2009 - 1982 - 1 = 26
2. 연 나이(year age) = 2009 -1982 = 27
3. 한국 나이(korean age) = 2009 - 1982 + 1 = 28

26~28 중에 아무 숫자나 부르면 되는군요. 만으로 26인데 한국 나이로는 28살이라~ 캬.. 느낌이 전혀 다르네요.
신고
top

TAG 나이

TCP/IP Layering

TCP/IP/The Protocols : 2009.01.28 11:10


TCP/IP 프로토콜 스위트에서는 여러 프로토콜이 있다.

사용자 삽입 이미지
TCP/IP 프로토콜 스위트에서 각 계층에서 사용하는 다양한 프로토콜들..

TCP와 UDP는 가장 널리 사용되고 있는 전송 계층 프로토콜로 둘 다 네트워크 계층으로 IP를 사용한다.

TCP는 자신이 사용하는 서비스(IP)가 비록 신뢰할 수 없음에도 불구하고 신뢰할 수 있는 전송 계층을 제공한다. 17~22장에서 자세히 살펴봄. 텔넷(26장), FTP(27장), SMTP(28장)

UDP는 애플리케이션데 대한 데이터그램을 주고 받는다. 데이터그램은 정보 단위로(보내는 쪽에서 정의한 정보를 몇몇 바이트로 나타낸것) 보내는 쪽에서 받는 쪽으로 전송된다. TCP와는 달리 데이터그램이 최종 목적지로 갈 것이라는 보장을 할 수 없다. 11장에서 자세히 살펴봄. DNS(14장), TFTP(15장), Bootstrap Protocol(16장)에서 UDP를 사용하는 애플리케이션 몇 개를 살펴본다.

IP는 네트워크 계층의 핵심 프로토콜로 TCP와 UDP가 이것을 사용한다. 모든 TCP와 UDP 데이터는 IP 계층을 통해서 종단 시스템과 모든 중계 라우터를 거쳐간다. 그림을 보면 애플리케이션에서 IP 계층에 직접 접근하는 모습도 보인다. 이는 드물지만 가능하다. 3장에서 IP를 살펴보고 9, 10장에서 IP 라우팅을 살펴본다.

ICMP는 IP의 부속물로. IP 계층에서 에러 메시지와 다른 호스트 또는 라우터에 있는 중요한 정보 변환에 사용된다. 6장에서 자세히 살펴본다. IP가 주로 사용하며 애플리케이션에서 직접 접근하는 것도 가능하다. ICMP를 사용하는 두 가지 주요 진단 도구인 Ping과 Tracerout를 7, 8장에서 살펴본다.

IGMP는 인터넷 그룹 관리 프로토콜로 UDP 데이터그램을 여러 호스트에 보내는 멀티캐스팅(multicasting)이 사용한다. 브로드캐스팅과 멀티캐스팅은 12장에서 다루고 IGMP에 대해서는 13장에서 다룬다.

ARP(Address Resolution Protocol)와 RARP(Reverse Address Resolution Protocol)은 (이더넷과 토큰링 같은) 특정 타입의 네트워크 인터페이스에서 사용한다. 이 프로토콜들을 사용하여 IP 계층에서 사용하는 주소와 네트워크 인터페이스에서 사용하는 주소를 변환한다. 4, 5장에서 이를 다룬다.
신고

'TCP/IP > The Protocols' 카테고리의 다른 글

TCP/IP Layering  (0) 2009.01.28
Layering  (0) 2009.01.23
top


Layering

TCP/IP/The Protocols : 2009.01.23 17:19



사용자 삽입 이미지

1. Link: 하드웨어적인 것들을 다루는 계층
2. Network: 네트워크 간에 패킷 이동을 다루는 계층
3. Transport: 호스트 간에 데이터 흐름을 제공하는 계층.
- TCP: 신뢰할 만한 데이터 전송. 하위의 네트워크 계층에 보낼 크기로 데이터를 나누는 작업, 받은 패킷 알려주기, 타임 아웃 설정 등을 다루기 때문에 애플리케이션 계층에선 이런걸 신경쓰지 않아도 됨.
- UDP: 데이터그램이라는 데이터 패킷으로 전송만 하고 목적지로 데이터 그램이 보내질지는 보장 못함. 신뢰성이 필요하면 애플리케이션 계층에서 추가작업 해야 함.
4. 애플리케이션 계층: 텔넷, FTP, SMTP, SNMP등 애플리케이션 관련 내용을 다루는 곳

사용자 삽입 이미지

1,2,3 VS 4 로 구분할 수 있다.
- kernel VS user processes
- 커뮤니키에션 담당 VS 애플리케이션 담당

네트워크 계층하고 전공 계층을 왜 나눠 뒀을까? 이걸 이해하려면 단일 네트워크 말고 여러 네트워크로 시각을 넓혀야 한다.

네트워크가 커져가면서 여러 네트워크를 라우터(router)에 연결하여 인터넷을 구성하는 것이 가장 간단한 방법이 된다. 라우터는 여러 하트웨어 박스를 네트워크에 연결할 때 사용한다. 여러 종류의 물리적 네트워크(이더넷, 토큰 링, p2p, FDDI 등)를 연결할 수 있다. 처음에는 이걸 게이트웨이라고 불렀는데 요즘에는 애플리케이션 게이트웨이(서로다른 두 프로토콜을 연결하는 것)를 나타낼 때 사용한다.

다음은 두 네트워크를 연결한 모습이다. 이더넷과 토큰 링을 라우터로 연결 했다. 두 개의 네트워크를 라우터로 연결했을 뿐인데 두 네트워크의 모든 호스트가 의사소통을 할 수 있게 있게 되었다.

사용자 삽입 이미지

end system(양 쪽의 호스트)과 intermediate system(중간의 라우터)을 구분할 수 있다. 애플리케이션 계층과 전공 계층은 end-to-end 프로토콜이다. 이 두 계층은 오직 끝에 있는 시스템에서만 필요하다. 하지만 네트워크 계층은 hop-by-hop 프로토콜이고 모든 중간 지점 시스템에서 이것을 사용한다.

TCP/IP 프로토콜은 네트워크 계층에 IP 프로토콜로 신뢰성이 떨어지는 서비스를 제공한다. 그냥 옮기기만 한다. 반면 TCP는 신뢰할 수 있는 전송 계층을 제공한다. 즉..provides a reliable transport layer using the unreliable service of IP 그래서 타임아웃이라던가 수신 확인 등을 수행한다. 이 두 계층 간에는 확실하게 책임을 구분한다.

라우터는 정의에 따르면 두 개 이상의 네트워크 인터페이스 계층을 가지고 있다. 여러 인터페이스를 지닌 시스템을 multihomed라고 한다. 호스트는 multihomed가 될 수 있지만 블라브라.. 생략.

네트워크를 연결하는 또 다른 방법으로 브릿지(bridge)가 있다. 네트워크를 링크 계층에서 연결한다. 여러 개의 LAN을 마치 상위 계층의 단일 LAN으로 보이게 한다.

보통 브릿지보다 라우커를 많이 사용한다.


신고

'TCP/IP > The Protocols' 카테고리의 다른 글

TCP/IP Layering  (0) 2009.01.28
Layering  (0) 2009.01.23
top


요즘 내 우선순위

모하니?/Planning : 2009.01.22 12:21


1순위 연애
- 주말 전체, 주중 저녁

2순위 번역
- 2월까지 재재벌 마무리

3순위 스터디
- 격주로 한 번 오프모임. 한 주 동안 퀴즈 풀기, 한 주 동안 리뷰.

4순위 회사일
- 600/1200/300 TCP/IP 관련 책 세 권 읽고 정리
- 리눅스 서버 관리 공부
- SWF 공부
- Spring Batch 공부
- 레거시 분석

5순위는 블로깅
- 연애 블로깅 할 거 없음
- 번역 블로깅 할 거 없음
- 스터디 블로깅 할 거 없음
- 회사일 블로깅 할 거 많음

이러니까 블로깅을 못 할 수밖에... @_@
신고

'모하니? > Planning' 카테고리의 다른 글

[ToDo] 20091016  (0) 2009.10.16
[ToDo] 오늘 할 일 - 할일(예상 소요 시간)(실제 소요 시간)  (4) 2009.10.15
2009년 마무리로 할 일  (4) 2009.10.08
9월에 할 일 정리  (10) 2009.08.31
공부할 것 정리  (6) 2009.03.16
요즘 내 우선순위  (2) 2009.01.22
봄싹 3기 TDD 스터디 장소 시간 확정 됐습니다.  (2) 2008.12.30
봄싹 3기 TDD 스터디 계획  (4) 2008.12.29
이번 주 할 일  (0) 2008.12.23
잠깐 정리 좀;;  (2) 2008.12.23
오늘 내일 할 일  (0) 2008.12.15
top


DI(Dependency Injection)의 장점

Spring/etc : 2009.01.21 14:27


참조: 프로 스프링 2.5 1장

접착 코드(glue code) 감소
- JNDI를 사용할 경우 복잡해질 수 있느 코드를 DI로 대체하면 깔끔해진다.

의존성 외부화
- 설정 옵션 분리, 의존성 변경 용이함

한 곳에서 의존성 관리
- 각각의 클래스 내부에서 자신의 의존성을 관리하는 것 보단 한 곳에서 관리하는 것이 편리함

테스트 편의성 향상
- 테스트용 더비(Dummy) 구현체로 쉽게 교체할 수 있기 때문에 귿.

좋은 애플리케이션 설계 도출
- DI를 활용하다보면 컴포넌트를 인터페이스를 사용하여 연결하게 된다. 이러한 설계야 스프링 전에도 가능했지만 스프링이 알아서 다 해주는걸 무료로 사용할 수 있으니까 비즈니스 로직에만 집중할 수 있을 것이다.


ps1: 프로 스프링 1장 베타리딩 시작합니다.
ps2: 베타리딩은 봄싹에서 전담합니다.
신고
top


새로운 인연

모하니?/Thinking : 2009.01.19 10:14



피아노를 같이 칠 수 있고
스노우보드를 같이 즐길 수 있고
영어 회화를 같이 할 수 있고
불어를 배울 수 있고
자바, 스프링을 가르쳐 줄 수 있고
요리를 같이 할 수 있고
번역을 같이 할 수 있고
...

이밖에도 엄청나게 많은 것들을 함께 할 수 있는(이 중에서 대부분은 제가 배우는 입장이지만ㅋㅋ) 사람을 만났습니다. 처음에는.. 세상에 이런 사람도 있구나 하는 호기심에 접근 했는데 어느새 제 애인이 되어 있네요.

이 친구는 정말 특이하게도 저와 가치관이 비슷합니다. 연애 경험도 비슷하고 서로 자기 일을 소중히 여기며 일에 집중할 땐 연락을 못 하는 것도 똑같더군요. 아마 이런 사람이 흔하진 않겠죠. 오랜만에 염장글이었습니다.ㅋㅋㅋ

언젠가 해뜰날 온다더니.. 이제 좀 구름이 걷히려나 봅니다.

ps: 지나간 연인아.. 그대도 좋은 사람 만나서 행복 하시길..
신고
top

TAG astre

EJ2E Item 18. 추상 클래스 보다는 인터페이스를 선호하라

Java : 2009.01.19 01:43


참조: Effective Java 2nd Edition. Item 18: Prefer interfaces to abstract classes

기존 클래스를 쉽게 수정하여 새로운 인터페이스를 구현할 수 있다.
- 인터페이스는 implements에 추가해주고 필요한 매서드를 구현하면 끝
- 하지만 새로운 추상 클래스를 만들어서 공통 로직을 상위로 올린다면, 하면 하위 클래스에는 자신에게 적당할지 안 할지도 모를 로직들을 상속받게 된다.

믹스인을 정의할 때 인터페이스가 제격이다.
- 믹스인(Mixin)이란 "원래 타입"에 어떤 부가적인 행위를 추가로 구현했다는 것을 나타내는 타입. ex) Comparable 인터페이스
- 추상 클래스는 믹스인으로 쓰기 어렵다.(단일 상속이니까)

인터페이스 계층구조가 아닌 타입 프레임워크를 구성할 수 있게 한다.

인터페이스는 Item 16에서 살펴본 wrapper class 개념을 통해 안전하고, 강력한 기능성 증진을 가져다준다.
- 추상 클래스를 사용하여 타입을 정의하면, 개발자가 기능을 추가하고 싶을 때 상속만을 써야 한다. 그 결과 클래스가 wrapper 클래스보다 점점 약해지고 깨지기 쉬워진다.

인터페이스 장점과 추상 클래스 장점을 추상 skeletal 구현체 클래스를 제공하여 얻을 수 있다.
- skeletal 인터페이스 = Abstact인터페이스, 해당 인터페이스를 구현할 때 필요한 모든 작업을 포함하고 있다.
- 개발자가 해당 인터페이스를 쉽게 구현할 수 있도록 도와준다.
- 보통은 skeletal 인터페이스를 extends 하여 만들지만, 그렇게 못할 경우에는 인터페이스를 직접 implements해도 된다.
- 인터페이스를 구현하고 그 것을 구현할 때 skeletal 클래스를 상속 받은 private inner class에 매소드 호출을 위임하는 식으로 구현할 수 있다. (simulated multiple inheritance)

인터페이스를 배포 한 뒤 그 구현체가 많아졌다면 그것을 변경하는 건 불가능에 가깝다.
- 따라서 주의해서 설계해야한다.
- 인터페이스를 정하기 전 가능한 여러 개발자가 그것을 구현해보게 하는 것이 최선이다.

신고
top


스프링과 디자인 패턴

Spring/etc : 2009.01.16 10:22


참조, 요약: 프로 스프링 2.5

인터페이스 기반 프로그래밍
생성 패턴
- Singleton: BeanFactory
- Factory
- Builder: BeanDefinitionBuilder
- Prototype: 스프링 없이 만들려면 별도의 추상 클래스 만들고 makeCopy() 같은 매서드 필요해
구조 패턴
- Proxy: 스프링 AOP
- Adapter: MessageListenerAdapter, MessageListenerAdapter102(어댑터), MessageListener
- Wrapper와 Decorator: DisplayTag
- Facade
행동 패턴
- Observer: ApplicationListener
- Strategy
템플릿 매서드
- JdbcTemplate, HibernateTemplate, HibernateCallback, ...

흠~ 여기서 좀 걸리는건 템플릿 매서드 패턴의 예로 든 JdbcTemplate 및 기타 Template 들인데요. 빨간책에서는 템플릿 매서드 패턴으로 XXSqlQuery 클래스들을 꼽고 있고 XXTemplate들은 Stategy 패턴의 일종인 콜백이라고 하고 있는데 좀 혼란을 주네요. @_@
신고
top


한국에도 이런 개그가 통할까?

모하니?/Watching : 2009.01.15 13:27




중간 중간 진지한 표정 짓는게 압권이네요.
과연~~ 이 개그를 한국에서 개콘이나 개그야 같은 방송에서 한다면?

신고
top


EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.

Java : 2009.01.13 22:38


참조: Effective Java 2nd Edition Item 17: Design and document for inheritance or else prohibit it.

오버라이딩이 가능한 메소드에는 반드시 문서화를 해야 한다.
 - "This implementation ~~" 즉 "이 구현체에서는.." 이라는 식으로 다음 배포 때는 다르게 구현할 수도 있다는 의미를 내포하는 문서화와 그 내용을 자세히 알려줘야 한다.

상속을 고려한 클래스를 테스트하는 유일한 방법은 하위클래스를 작성하는 것이다.
- 배포하기 전에 반드시 하위클래스를 작성해서 테스트 해봐야 한다.

생성자에서 오버라이딩이 가능한 메소드를 직접적으로든 간접적으로든 호출하면 안 된다.
- 상위 클래스 생성자에서 하위 클래스가 오버라이딩한 메소드를 호출하다가 아직 필드 초기화도 안 된 하위 클래스의 필드를 사용해서 원치 않던 결과가 발생할 수 있다.

Cloneable과 Serializable 인터페이스를 구현한 클래스가 상위클래스가 될 여지가 있다면
- 생성자와 마찬가지로 clone과 readObject에서 오버라이딩이 가능한 메소드를 호출하지 않도록 한다.

안전하게 상속하도록 문서화 하거나 설계하지 않은 클래스를 상속할 수 없도록 하는 것이 최선책이다.
- 두 가지 방법 중에서 쉬운 방법은 클래스를 final로 만드는 것이다.
- 두 번째 방법은 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 추가한다. 이 방법은 내부적으로 상속이 가능해서 약간 더 유연하다. Item 15에서 다뤘다.




신고
top

Java : 2009.01.13 22:38 Trackback. : Comment.

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


재벌의 묘미(?)



출판사 부탁으로 다른 분이 번역한 걸 손보는 작업을 하고 있습니다. 물론 자봉은 아닙니다. 그리 내키는 작업은 아닙니다. 누군가의 자존심을 상하게 하는 일일 수도 있으니까요.

그런데 참 재밌습니다. 번역을 꾸준히 하다보면 자신 만의 패턴이 생기는데 같은 상황에서 다른 분들은 해당 표현을 어떻게 한글화 하는지 볼 수 있습니다. 그런 재미가 쏠쏠합니다.

가끔은 다른분이 표현한 것보다 더 좋은 표현이 떠올라서 그걸로 수정할 때가 있는데 그럴 때는 뭔가 '잘난채'하는 기분도 느껴집니다. 그래봤자 도토리 키재기지만 말이죠.ㅋㅋㅋ

이런 글을 쓰면 마치 제가 번역을 쫌 하는 것처럼 느끼실 수 있겠지만 전혀 그렇치 않으니까 그런 생각은 하지 말아주세요. 저는 관심있는 글을 번역하는게 재밌어서 하는거지 잘해서 한다거나 이름 한 줄 때문에 한다거나 돈을 보고 하는게 아니랍니다.(솔직히 말하면 이런 이유들도 어느 정도는 작용을 합니다.) 제가 책을 1년에 30권만 읽었어도(부끄럽게도 1년에 그 정도도 안 본다는 거죠..ㅋ) 번역 품질이 더 좋아졌을 수 있을텐데하며 아쉬워 하는 중입니다. 말이 나온김에 올해에는 한 달에 3권을 목표로 도전해야겠습니다.

예를 들어..

Listing 8-4. Mixed-Concern Interface

초벌: 목록 8-4. 잡다한 관심 사항이 있는 인터페이스

재벌: 코드 8-4. 혼잡한(Mixed-Concern) 인터페이스

요런 재미가 있다고나 할까요...
신고
top

TAG 재벌

그루비 사용자와 스프링

Spring/etc : 2009.01.07 10:55


스프링의 동적 언어 지원 기능은 2.0 버전부터 두각을 드러냈지만 '아.. 이런 기능이 있네..' '오.. 되는구나..' 정도로 간단한 예제를 몇 개 실험해봤을 뿐 어떻게 활용해야 할지는 전혀 몰랐는데 관련 기사가 developerWorks에 떴습니다. 두 개씩이나..

Groovier Spring, Part 1: Integration basics

Groovier Spring, Part 2: Change application behavior at run time

1부는 2월에 2부는 3월에 번역해서 한국 IBM developerWorks에 올릴 생각입니다.
신고
top


구글 토크 사전

Good Tools : 2009.01.05 13:48


사용자 삽입 이미지

이 녀석은 사부님이 준 새해 선물로.. 구글 토크에 en2ko.dict@bot.talk.google.com 을 추가해 준 다음 말을 걸면 됩니다.

캬~ 귿!!
신고
top


드디어 한국에서도 SpringSource에서 직접 교육을 하네요.

Spring/etc : 2009.01.05 13:19


아침에 사부님께서 링크를 줘서 들어가봤다가 깜짝 놀랬습니다.

http://www.springsource.com/training/spr001/icn20090302

한국에서 3월 2~5일까지 4일 과정으로 스프링 Core 교육을 합니다.
교육비는 호주 달러로 약 3000달러.. 현재 환율로 280만원 정도.

또 하나의 거대한 지름신이 유혹을 하네요. 저 떄 한국에 있을지 없을지도 모르는데 질러야 하나 말아햐 하나 고민입니다.

교육에 대한 자세한 설명은

http://www.springsource.com/training/spr001/syllabus

여기에 있습니다. 재밌는 건 Assumptions 인데..

We assume participants have a good understanding of the core Java APIs, as well as a basic knowledge of general J2EE concepts and APIs such as JTA and the Servlet API. As we cover object-relational mapping technologies (ORM) in this course as well, we assume basic knowledge of ORM concepts.

자바와 JEE 개념을 그렇다치고 ORM 기본 개념이 필요하다는 것 있다는 것과..
교육 과정 설명 중에 Hibernate란 단어가 대 여섯 번 정도 포함되어 있다는 것입니다.
그래서 인지 더욱 끌리네요~

Analysis and tradeoffs of relevant persistence strategies, including Hibernate and Spring JDBC
...
Understanding ORM and Hibernate with Spring
...

신고
top


스프링 시큐리티 맞춤확장(customization) - 파트 1. UserDetail 또는 GrantedAuthority 맞추기



참조 및 번역: http://blog.springsource.com/2009/01/02/spring-security-customization-part-1-customizing-userdetails-or-extending-grantedauthority/

이번 글은 스프링 시큐리티 맞춤확장과 관련된 실용적인 예제 중심의 여러 작은 글들의 시리즈 중 첫 번째 글이다. 이번 맞춤확장 요구 사항은 상상에서 온 것이 아니라 전부 현장에서 요구한 것이다.

다음과 같은 요구사항이 있다고 가정해보자. 역할(role) 목록이 있고 각각의 역할은 비즈니스 기능(business function) 목록을 가지고 있다.(아래를 참조하라.)

ROLE_ADMIN
  BF_QUOTE_CREATE
  BF_POLICY_CREATE
  BF_POLICY_DELETE

ROLE_AGENT
  BF_QUOTE_CREATE
  BF_POLICY_CREATE

ROLE_USER
  BF_QUOTE_CREATE

필요한 기술은 권한 결정을 ROLE과 BF 모두를 가지고 결정할 수 있어야 한다는 것이다.

예를 들어:

ROLE_ADMIN이라는 역할을 가진 사용자는 해당 역할로 보호하고 있는 모든 리소스에 접근할 수 있어야 한다.

<sec:authorize ifAllGranted="ROLE_ADMIN">
    <p><a href="http://www.google.com">Google</a>
</sec:authorize>

또는

@Secured("ROLE_ADMIN")
public void foo()
    . . .
}

또한 해당 사용자는 해당 역할이 가지고 있는 비즈니스 기능으로 제한하고 있는 모든 리소스에도 접근할 수 있어야 한다.

<sec:authorize ifAllGranted="BF_POLICY_DELETE">
    <p><a href="http://www.google.com">Google</a>
</sec:authorize>

또는

@Secured("BF_POLICY_DELETE")
public void foo()
    . . .
}

이 요구사항을 다루는 방법은 몇 가지가 있다. 그 중 하나는 RoleHierarchy를 만들고 RoleHierarchyVoter를 사용하여 역할의 계층 구조를 순회하는 것이다. 이 접근 방법의 단점은 현재 스프링 시큐리티 2.0.4 구현체의 태그 라이브러리(security: authority ...)가 AccessDecisionManager를 통해서 의사 결정을 하고 있지 않으며 게다가 어떤 Voter도 제한하고 있는 HTML 엘리먼트에 대한 결정을 할 때 Role을 신경쓰지 않는다. 하지만 스프링 시큐리티의 놀라운 유연함과 맞춤확장 힘으로 인해 이 요구사항을 매우 간단하게 해결할 수 있다.
스프링 시큐리티의 가장 큰 잇점 중 하나는 어떻게 Principal(UserDetail 객체)을 생성하는지 맞춤확장 할 수 있다는 것이다. UserDetail 객체를 만들 때 GrantedAutorities 목록을 생성한다. 이 목록은 나중에 리소스를 제한하고 있는 GrantedAutority와 비교해볼 때 사용된다.
맞춤확장을 하는 또 한 가지 방법은 UserDetail 객체를 생성할 때 GrantedAuthorities 목록을 수정하는 것이다.

아래에서 제공하고 있는 예제에는 두 개의 프로퍼티 파일이 있다.(간단하게 하려고 프로퍼티 파일을 사용했지만, 여러분은 쉽게 그것을 DB나 LDAP으로 바꿀 수 있을 것이다.)

파일 하나는 users.properties로 사용자를 role에 맵핑한다.

oleg=powder,ROLE_ADMIN

다른 파일 하나는 role-to-db.properties로 각각의 role에 비즈니스 기능 목록을 맵핑한다.

ROLE_ADMIN=BF_QUOTE_CREATE,BF_POLICY_CREATE,BF_POLICY_DELETE

이제 필요한 작업은 UserDetailsService 구현체를 정의하여 위 두 개의 프로퍼티 파일을 사용하여 GrantedAUthorities 목록을 만들고 그것을 UserDetails 객체에 주입하는 것이다. 이 작업은 GrantedAuthorityImpl같은 GrantedAuthority 인터페이스 구현체를 재사용할 수 있으며 매우 간단하다. 하지만, 우리는 이때 (디버깅 또는 다른 의도로) 비즈니스 기능을 표현하는 GrantedAuthority의 상위(parent) GrantedAuthority를 추적할 수 있도록 하고 싶다.
이 두 가지 목적을 달성하기위해 GrantedAuthorityImpl을 확장하는 BusinessFunctionGrantedAuthority 클래스를 정의하고 모든 상위 GrantedAuthority 객체에 대한 목록을 담고 있게 했다.

public class BusinessFunctionGrantedAuthority extends GrantedAuthorityImpl {
    private List<GrantedAuthority> parentAuthorities;
        . . .
}

그런 다음 UserDetailsService 구현체를 만들었고 loadUserByNames(..) 메소드를 구현하여 다음 작업을 수행하도록 했다.

1. users.properties 파일 내용을 기반하여 UserAttribute 객체를 만든다. UserAttribute는 role을 나타내는 GrantedAuthority 목록을 담고 있다.
2. role-GrantedAuthorities 목록을 순회하면서 각각의 role-GrantedAuthorities 마다 BusinessFunctionGrantedAuthority를 만들고 그것을 미리 만들어둔 GrantedAuthority 목록에 추가한다.
  2-1. 각각의 BusinessFunctionGrantedAuthority에 parent GrantedAuthority를 추가한다.
3. 전체 GrantedAuthority 목록을 가지고 있는 최종 UserDetail 객체를 만든다.

그런 다음 AuthenticationProvider를 스프링 시큐리티 설정 파일에 정의한다.

사용자 삽입 이미지

노트: AuthenticationProvider에 커스텀 UserDetailService 구현체 ComplexAuthorityUserDetailsService 클래스(더 자세한 내용은 샘플 코드를 살펴보라.)를 주입했다.

리소스를 보안하고 배포한 다음 http://localhost:8080/spring-security-sample-grantedAuthority/index.jsp에 접속하라.

로그인한 뒤 여러분은 GrantedAuthorities 목록을 볼 수 있을 것이다.

사용자 삽입 이미지

여기서 여러분은 비즈니스 기능을 나타내는 GrantedAuthority가 해당 비즈니스 기능을 가지는 상위 GrantedAuthorities 목록도 보여주고 있다는 것을 확인할 수 있을 것이다.
index.jsp를 살펴보고 어떻게 security:authorize 태그를 사용ㅇ하여 role과 비즈니스 기능 목록을 사용하여 HTML 엘리먼트를 제한하는지 살펴보라
이게 끝이다. 여러분은 최소한의 맞춤확장으로 쉽게 Principal 구조를 확장하고 커스터마이징 할 수 있는지 살펴보았다. 그리고 이것을 비즈니스 코드를 더럽히지 않고 커스텀 GrantedAuthorities에 기반하여 선언적으로 보안을 적용하는지 살펴보았다.

샘플 코드는 여기서 다운로드 할 수 있다. spring-security-sample-grantedauthority
신고
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







티스토리 툴바