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.

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로 사용한다. 만약 둘 다 없는 경우에는 어떤 플로우도 실행하지 않는다.

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 같은 빌드 넘버를 나타낸 것이다. 최근 빌드 넘버를 알고 싶다면 나이틀리 빌드 영역을 참조하라.

EJ2E Item 6. 사용하지 않는 객체 레퍼런스를 제거하라

참조: Effective Java 2nd Edition Item 6. Eliminate obsolute object references

C나 C++처럼 메모리 관리를 직접 하다가 GC를 사용하는 자바로 넘어오면 편해진거 같지만, 조심하지 않으면 메모리 부족 현상이 나타날 수 있다.

public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  return elements[–size];
}

배열에서 필요없는 객체 레퍼런스를 가지고 있기 떄문에, 여러 객체를 담고 빼고 하다보면 계속 쌓여서 결국엔 메모리 부족 현상이 발생한다.

public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  Object result = elements[–size];
  elements[size] = null; // Eliminate obsolete reference
  return result;
}

명시적으로 null 로 설정하는 방법은 예외적인 경우에 한해서 사용하는게 좋다. 객체 레퍼런스를 제거하는 가장 좋은 방법은 scope를 이용하는 것이다. 자연스럽게 제거 되도록..

또 다른 메모리 부족 주요 원인은 캐시.

객체 레퍼런스를 캐시에 넣어두고 삭제하는 걸 깜빡하는 경우가 있다. 대안책으로 WeakHashMap을 사용하는 것이다.

Remember that WeakHashMap is useful only if the desired lifetime of cache
entries is determined by external references to the key, not the value.

흠.. Weak Reference를 사용하는 HashMap인것 같은데, 잘 몰겠다. API를 봐야겠네.

메모리 부족 세 번째 주요 원인은 리스너나 콜백.

콜백을 등록하고 나서 나중에 해지할 때 제대로 하지 않아서 문제 발생. 콜백을 GC 하는 최선의 방법은 WeakHashMap을 사용해서 콜렉션에 오직 weak reference만 담는 것이다.

메모리 부족 문제는 힙 프로파일러(heap profiler)를 사용해서 디버깅 하지 않는 이상 찾아내기 힘들다.

6.2. Defining references to OSGi services

스프링 DM은 OSGi 서비스 레지스트리를 통해 사용할 수 있는 서비스를 나타내는 빈을 선언하는 기능을 제공한다. 이런 방법으로 통해 OSGi 서비스는 애플리케이션 컴포넌트로 주입될 수 있다. 서비스를 룩업할때는 해당 서비스가 지원하는 서비스 인터페이스 타입과 레지스트리에 등록된 서비스 속성에 부합하는 부가적인 필터 표현식을 사용한다.

몇몇 경우에, 간단하게 애플리케이션에서 하나의 서비스만을 필요로 할 때가 있다. 이 때는 reference 엘리먼트를 정의해서 단일 서비스를 참조하도록 정의할 수 있다. 다른 경우, 특히 OSGi 화이트보드 패턴을 사용할 때, 모든 가용한 서비들을 필요로 할 때가 있는데, 스프링 DM은 이거슬 List나 Set 콜렉션으로 이들 집합을 관리할 수 있는 기능을 제공한다.

6.2.1. 단일 서비스 참조하기

reference 엘리먼트는 서비스 레지스트리에 등록된 서비스를 참조할 때 사용한다.

선언한 것에 부합하는 서비스가 여러 개일 수 있기 때문에, BundleContext.getServiceReference에 의해 반환되는 서비스를 참조하게 된다. 이게 무슨 뜻이냐면 가장 높은 랭킹의 서비스가 반환된다는 것이다. 또는 만약 랭킹이 같을 때는 서비스 id가 가장 낮은 순서(프레임워크에 먼저 등록될 수록 id가 낮다.)로 반환된다.(OSGi 스펙에 보면 보다 자세하게 서비스 선택 알고리즘이 설명되어 있다.)

6.2.1.1. 가져온 서비스의 인터페이스 제어하기

interface 속성은 해당 서비스가 반드시 구현한 인터페이스를 나타낸다. 예를 들어, 다음의 선언은 messageService 빈 하나를 등록한건데, 이것은 MessageService 인터페이스를 제공하는 서비스를 서비스 레지스트리로부터 질의하여 반환받은 서비스가 된다.

<reference id=”messageService” interface=”com.xyz.MessageService”/>

service 선언과 마찬가지로, 여러개의 인터페이스를 기술할 떄는 interface 속성 대신에 내부 엘리먼트로 interfaces를 사용하면 된다.

<osgi:reference id=”importedOsgiService”>
  <osgi:interfaces>
     <value>com.xyz.MessageService</value>
     <value>com.xyz.MarkerInterface</value>
  </osgi:interfaces>
</osgi:reference>

interface 속성과 interfaces 엘리먼트를 둘 다 사용하는 것은 문법에 어긋나며, 항상 둘 중 하나만 사용하도록 한다.

reference 엘리먼트로 정의한 빈은 번들이 참조할 수 있는 서비스의 인터페이스들을 구현하고 있다.(greedy proxing 이라고 한다.) 만약 등록된 서비스 인터페이스가 자바 클래스 타입을 포함하고 있다면(인터페이스가 아니라), 해당 타입들은 스프링 AOP를 따르게 되ㅇㄴ다. 간략하게 말하자면, 명시된 인터페이스들이 인터페이스가 아니라 클래스라면, cglib이 반드시 가용한 상태여야 하며, final 메소드가 없어야 한다.

6.2.1.2. filter 속성

부가적인 속성 filter는 OSGi 필터 표현식을 명시하고 서비스 레지스트리 룩업을 할 때 오직 주어진 필터에 해당하는 것들에서만 찾도록 제약을 가할 수 있다.

<reference id=”asyncMessageService” interface=”com.xyz.MessageService”
  filter=”(asynchronous-delivery=true)”/>

이것은 asynchronous-delivery 속성이 true이고 MessageService 인터페이스로 등록해둔 OSGi 서비스만 참조할 것이다.

6.2.1.3. bean-name 속성

bean-name 속성은 service 엘리먼트를 사용하여 공개한 빈의 이름에 해당하는 서비스를 찾아주는 필터 표현식의 단축키 정도에 해당한다.

<bean id=”(1)messageServiceBean” scope=”bundle” class=”com.xyz.MessageServiceImpl”/>
<!– service exporter –>
<osgi:service id=”messageServiceExporter” ref=”(1)beanToBeExported” interface=”com.xyz.MessageService”/>

<osgi:reference id=”messageService” interface=”com.xyz.MessageService”
   bean-name=”(1)messageServiceBean”/>

1    the name used with bean-name attribute

위에 선언한 reference 빈은 MessageService 인터페이스로 등록된 서비스 중에서 org.springframework.osgi.bean.name 속성에 이름이 messageServiceBean 인 OSGi 서비스를 찾는다. 다시 간략하게 말하자면, 모든 공개된 빈 중에서 MessageService 인터페이스를 구현했고, 빈의 이름이 messageService인 빈을 참조하게 된다.

6.2.1.4. cardinality 속성

cardinality 속성은 항상 해당하는 서비스가 존재해야 하는지 아닌지를 기술할 때 사용한다. cardinality 값이 1..1(이게 기본값) 이면 해당하는 서비스가 반드시 가용해야 한다는 것을 뜻한다. cardinality 값이 0..1이면, 해당하는 서비스가 항상 필요한 것은 아니라는 것을 뜻한다.(4.2.1.6에 자세히 나와있다.) reference에 cardinality 1..1로 설정되어 있는 것은 필수mandatory 서비스 레퍼런스라고도 하며, 기본적으로 해당 레퍼런스가 참조 가능할 때까지 애플리케이션 컨텍스트 생성이 지연된다.

노트

해당 번들 자신이 공개한 서비스를 필수 서비스 레퍼런스로 참조하는 것은 에러다. 이런 행위로 인해 애플리케이션 컨텍스트 생성이 데드락이나 타임아웃으로 실패하게 될 것이다.

6.2.1.5. depends-on 속성

dependes-on 속성은 서비스 레지스트리에서 해당 서비스 레퍼런스를 룩업하기 전에 이 속성에 명시한 빈을 먼저 생성하라는 것이다.

6.2.1.6. context-class-loader 속성

OSGi 서비스 플랫폼 코어 스펙(현재 4.1 작성중)에는 서비스 레지스트리에서 얻어온 서비스를 통하여 컨텍스트 클래스 로더에서 가용한 타입이나 리소스들을 명시하고 있지 않다. 따라서 어떤 서비스들은 컨텍스트 클래스 로더로부터 특정 라이브러리가 가용하리라고 예상하고 작성하기도 한다. 스프링 DM은 서비스 호출 시점에 컨텍스트 클래스 로더 제어대해 명시적으로 제어한다. reference 엘리먼트의 context-class-loader 속성으로 이를 달성할 수 있다.

context-class-loader에 가용한 값은 다음과 같다.

  • client – 서비스 호출 기간중에, 컨텍스트 클래스 로더는 서비스를 호출하고 있는 번들의 클래스패스에 있는 타입들을 볼 수 있다. 기본값이다.
  • service-provider – 서비스 호출 기간중에, 컨텍스트 클래스 로더는 서비스를 공개한 쪽 번들의 클래스패스에 있는 타입들을 볼 수 있다.
  • unmanaged – 서비스 호출 기간중에 컨텍스트 클래스 로더 관리를 하지 않음

6.2.1.7. reference 엘리먼트 속성

지금까지 살펴본 속성들 표 생략.

6.2.1.8. reference와 OSGi 서비스 동적특성Dynamics

reference 엘리먼트로 정의한 빈은 애플리케이션 컨텍스트의 생명주기 동안 바뀌지 않는다. 하지만, 해당 빈이 참조하는 OSGi 서비스는 언제든지 서비스 레지스트리에서 나가고 등록될 수 있다. 필수 서비스 레퍼런스(cardinality 1..1)인 경우에는, 해당 서비스가 가용할 떄 까지 애플리케이션 컨텍스트 생성을 일정 시간 지연시킨다. 부가 서비스 레퍼런스(cardinality 0..1)인 경우에는 현재 해당 서비스가 가용한지 여부와 관계없이 해당 빈이 바로 생성된다.

만약 참조해야 하는 서비스가 없어지면, 스프링 DM은 해당 reference에 정의한 것에 해당하는 다른 서비스로 기존 서비스를 교체 시도를 한다. 애플리케이션은 리스너를 등록하여 자신이 참조하는 서비스의 변경을 알아차려야 한다. 만약 해당하는 서비스가 없다면, reference는 unsatisfied가 되고, 이 reference에 의존하는 다른 공개된 서비스들을 해당 reference의 의존성이 해결될때까지 서비스 레지스트리에서 해지한다. 자세한건 6.5에서..

만약 unsatistied 상태인 reference 빈에 어떤 요청이 발생했다면, 해당 요청은 레퍼런스가 다시 satisfied 상태가 될때까지 잠시 대기시킨다. timeout 속성에 이 때 대기할 시간(millisesecond)을 설정할 수 있다. 몇시한 타임아웃 값이 지나고 나서도 해당 빈이 가용하지 않다면 ServiceUnavailableException을 던진다.

6.2.1.9. 관리하고 있는 서비스 레퍼런스 참조하기

스프링 DM은 자신이 관리하고 있는 서비스 레퍼런스를 ServiceReference 타입으로 자동변환해준다. 따라서, ServiceReference 타입의 속성을 가지고 있는 빈에 해당 서비스를 (명시된 인터페이스 타입이 아니라 ServiceReference 타입으로)injection 할 수 있다.

public class BeanWithServiceReference {
    private ServiceReference serviceReference;
    private SomeService service;
           
    // getters/setters ommitted
}

<reference id=”service” interface=”com.xyz.SomeService”/>
       
<bean id=”someBean” class=”BeanWithServiceReference”>
  <property name=”serviceReference” ref=”service”/>                                      (1)
  <property name=”service” ref=”service”/>                                               (2)
</bean>

1    Automatic managed service to ServiceReference conversion.
2    Managed service is injected without any conversion

노트

주입된 ServiceReference 타입의 빈은 참조하고 있는 OSGi 서비스 객체가 바뀔 때마다 같이 바뀐다.


6.2.2. 서비스 콜렉션 참조하기

때때로 애플리케이션은 간단하게 특정 범위 중에서 하나의 서비스를 참조할 뿐만 아니라, 범위 해당하는 모든 서비스를 참조해야 할 때도 있다. 스프링 DM은 이를 List 또는 Set(정렬은 부가적으로) 엘리먼트로 지원한다.

List와 Set의 차이는 오직 동일성뿐이다. 레지스트리에 등록되어 있는 두 개 이상의 서비스들(각자 다른 서비스 id를 가지고 있을 것이다.)이 해당 서비스의 equals 메소드 구현에 따라 같을 수도 있는데. Set에는 그들 중에 하나만 가지고 있을 것이고, List는 전부다 반환해 준다.

set과 list 스키마 엘리먼트는 서비스 집합체를 나타낼 때 사용한다.

이 엘리먼트들도 interface, filter, bean-name, cardinality 그리고 context-class-loader를 지원한다. 단 cardinality의 값으로는 0..n 과 1..n 만 사용할 수 있다.

0..n은 해당 콜렉션에 대응하는 서비스가 하나도 없어도 상관없다는 것이고 1..n은 필수 레퍼런스를 나타낸다.

list는 java.util.List 타입으로 정의되고, set 엘리먼트는 java.util.Set 타입의 빈으로 정의된다.

다음의 예제는 EventListener 인터페이스를 구현한 모든 등록된 서비스들을 List 타입으로 반환할 것이다.

<list id=”myEventListeners” interface=”com.xyz.EventListener”/>

리스트의 요소들은 스프링에의해 동적으로 관리될 것이다. 대응하는 서비스가 레지스트리에 등록되고 해지됨에 따라 콜렉션의 구성요소도 갱신될 것이다.

스프링 DM은 정렬된 콜렉션도 지원한다. set과 list 둘다.

정렬하는 방법은 두 가지인데, 하나는 Comparator를 사용하는 방법(custom ordering)이고 다른 하나는 Comparable 인터페이스를 구현한 것 끼리 비교하는 것(natural ordering)이다.

6.2.2.1. Greedy Proxing

스프링 DM 서비스 콜렉션으로 가져온 모든 OSGi 서비스는 interface 속성에 설정한 클래스에 타입 호환가능하다. 하지만, 때때로 서비스가 부가적으로 설정한 클래스들을 사용하고 시을 수 있다.

그런 경우, 스프링 DM 콜렉션은 greedy-groxing 속성을 지원하는데, 이 속성을 사용하여 참조하는 서비스에 부가적으로 설정한 모든 클래스들을 사용할 수 있도록 프록시들을 생성하게 할 수 있다. 따라서, 가져온 프록시를 interface에 설정한 클래스가 아닌 다른 타입으로 캐스팅을 할 수 있다. 다음의 예제를 보자.

<list id=”services” interface=”com.xyz.SomeService” greedy-proxying=”true”/>

다음과 같이 할 수 있다.

for (Iterator iterator = services.iterator(); iterator.hasNext();) {
    SomeService service = (SomeService) iterator.next();
    service.executeOperation();
    // if the service implements an additional type
    // do something extra
    if (service instanceof MessageDispatcher) {
        ((MessageDispatcher)service).sendAckMessage();
    }
}

노트

greedy proxy와 instanceof를 사용하기 전에 다른 인터페이스나 클래스 사용을 고려해 보아라. 보다 나은 다형성과 객체 지향 스러운 서비스를 구성할 수 있을 것이다.

6.2.2.2. 콜렉션(list와 set) 엘리먼트 속성

timeout 이 없는거만 뺴면 reference 랑 똑같다.

6.2.2.3. list/set 그리고 OSGi 서비스 동적특성

OSGi 서비스들의 집합은 OSGi 공간의 상태를 반영해야 하기 때문에 애플리케이션 컨텍스트의 라이프사이클 동안 계속해서 변한다. 서비스가 등록되고 해지되는 동안 콜렉션에 해당 서비스들이 추가 또는 삭제 된다.

reference 선언은 자신이 참조하는 서비스가 해지 되면 대체제를 찾는 반면, 콜렉션은 그냥 서비스를 콜렉션에서 제거한다. reference와 마찬가지로, 1..n 인 cardinality는 필수 레퍼런스라고 하며, 0..n은 부가적인 레퍼런스라고 한다. 만약에 대응하는 서비스가 없다면 필수 콜렉션만 unsatisfied 상태가 되고 ServiceUnavailableException을 던진다.

6.2.2.4. Iterator 제약사항과 서비스 콜렉션

콜렉션을 순회하는 방법중에 권장하는 방법은 Iterator를 사용하는 것이다. 하지만, OSGi 서비스는 언제든지 등록되고 해지될 수 있으므로, 콜렉션 또한 그에 따라 변경되어야 한다. 스프링 DM은 깔끔하게Transparenlty 사용자가 참조하는 모든 Iterator들을 수정해 준다. 따라서 사용자는 콜렉션이 변경되는 와중에도 안전하게 순회할 수 있다. 게다가, 그 Iterator들은 콜렉션의 모든 변경 사항을 반영한다. 변경이 Iterator 객체를 만든 후에 발생했더라도 반영된다. 만약 순회를 시작하자 마자 서비스들이 왕창 내려갔다고 했을때, 이 상태에서 순회를 계속하면 “죽은” 서비스들을 호출하게 될 것이다. 스프링 DM은 그래서 이 Iterator들이 스냅샷이 아니라 가장 최신 콜렉션 상태를 반영한 것이 된다. 순회가 얼마나 빠르고 느리냐는 상관없다.

서비스 수정은 Iteraor에서 순회하기 전에 있는 것들에만 반영이 된다는 것에 주의하자. 이미 순회를 마친 서비스에는 어쩔 도리가 없다. 만약 이미 해지된 서비스에 어떤 동작을 요구한다면 ServiceUnavailableException이 발생한다.

정리하자면, reference 선언은 자신이 참조하는 서비스가 해지되면 대체제를 찾지만 콜렉션은 대체제를 찾지 않는다. 그냥 자신의 콜렉션에서 해당 서비스를 제거할 뿐이다. 다음 순회할 때 그들을 사용하지 않도록.

Iterator 제약사항은 next() 메소드가 항상 hasNext() 호출 결과를 따른다는 것을 알아두자.

hasNext()가 true일 때 next()를 하면 항상 null이 아닌 값을 반환한다.
hasNext()가 false일 때 next()를 하면 NoSuchElementException을 던진다.

간단하게 Iterator를 리프레쉬하려면 hasNext()를 다시 호출하면 된다. 그럼 Iterator가 현재 콜렉션 에서 다음 순회 엔트리를 확인할 것이다.

6.2.3. 가져온import OSGi 서비스의 동적특성 다루기

reference나 set, list를 사용할 때 스프링 DM이 뒷단의 서비스를 관리할 것이다. 하지만, 하지만 때론 애플리케이션이 뒷단의 서비스 변경을 알고 싶을 수도 있다.

언제 reference 빈이 묶이고 풀리는지 알고 싶은 그런 애플리케이션들은 내부 엘리먼트로 하나 이상의 listener 엘리먼트를 등록할 수 있다. 이 엘리먼트는 reference, set, list에서 사용할 수 있다. 서비스 공개 리스너 설정과 비슷하다. listener 엘리먼트에 org.springframework.osgi.service.importer.OsgiServiceLifecycleListener 인터페이스를 구현한 빈을 참조하도록 설정하면, 해당 인터페이스의 bind와 unbind 메소드를 호출하게 된다. 커스텀 바인드 언바인드 메소드를 사용하려면 메소드 이름을 사용하면 된다.

<reference id=”someService” interface=”com.xyz.MessageService”>
  <listener ref=”aListenerBean”/>
</reference>

<reference id=”someService” interface=”com.xyz.MessageService”>
  <listener bind-method=”onBind” unbind-method=”onUnbind”>
     <beans:bean class=”MyCustomListener”/>
  </listener>
</reference>

만약 OsgiServiceLifecycleListener와 커스텀 메소드 둘다 등록되어 있다면 인터페이스 구현체 먼저 호출하고 그 다음 커스텀 메소드를 호출한다.

커스텀 메소드의 시그너쳐는 다음과 같다.

public void anyMethodName(ServiceType service, Dictionary properties);

public void anyMethodName(ServiceType service, Map properties);

public void anyMethodName(ServiceReference ref);

ServiceType 자리에는 어떤 타입이든 선언할 수 있다. 해당 타입에 해당하는 서비스가 묶이거나 풀릴때 호출된다. 만약 모든 타입에 대해 콜백을 호출하고 싶으면 java.lang.Object 타입으로 선언하면 된다.

properties 파라미터는 등록된 서비스가 가지고 있는 속성들의 집합을 나타낸다.

리스너가 reference에 등록되어 있다면:

  • 레퍼런스가 초기에 서비스와 묶일 때와 서비스가 새로운 서비스로 교체될 때마다 bind 메소드가 실행된다.
  • 현재 서비스가 해지되거나, 그 즉시 교체가능한 대체 서비스가 없을 때 unbind 콜백이 호출된다.(물론 해당 reference는 unsatisfied 상태가 된다.)

리스너가 콜렉션에 등록되어 있다면:

  • 새로운 서비스가 콜렉션에 추가되면 bind 메소드를 호출한다.
  • 서비스가 해지되고 콜렉션에서 제거될 때 unbind 메소드를 호출한다.

콜렉션에는 서비스 교체가 없다는 것을 주목하라. 콜렉션은 그냥 서비스를 추가하고 제거할 뿐이다.

뒷단의 OSGi 서비스에 대한 OSGi serviceChanged 이벤트 처리의 일부로 바인드와 언바인드 콜백은 동기적으로 처리된다.

아래의 테이블은 reference listener 서브 엘리먼트로 가용한 속성들이다.

생략

6.2.4. 리스너와 서비스 프록시들

임폴트 리스너들은 특정 시점에 묶이는 OSGi 서비스에 접근할 수 있는 방법을 제공하지만, 여기서 중요한 것은 해당 메소드로 넘어온 아규먼트가 실제 서비스 객체가 아닌 프록시라는 것이다. 프록시를 사용하는 이유는 언제 어떻게 바뀔지 모른느 객체에 대하여 강력한 레퍼런스를 가지는 것을 방지하기 위함이다. 서비스들을 추적하는 것이 주목적인 리스너들은 instance equality나 object equlity에 연연해서는 안된다. 이 들 메소드가 해당 인터페이스나 클래스의 public 메소드로 구현한 것이 아니라면, 프록시의 메소드가 호출될 것이다.

따라서 추적은 그냥 서비스 인터페이스나, 서비스 속성(org.osgi.framework.Constants#SERVICE_ID 참조) 또는 서비스 노티(바인드/언바인드)로만 하는 것을 추천한다.

6.2.5. 호출하는 BundleContext에 접근하기

가져온 서비스가 특정 시점에 어떤 번들을 사용하고 있는지 알고 싶을 수 있다. 이런 시나리오를 돕기 위해, 스플이 DM이 가져온 서비스는 가져온 번들 BundleContext를 LocalBundleContext 클래스로 공개한다. 가져온 것에 대한 메소드가 호출될 때마다, ThreadLocal을 사용하여 호출하는 BundleContext를 사용할 수 있다. getInvokerBundleContext()를 통해서..

단 이걸 사용할 때 해당 클래스가 스프링 DM 코드에 의존하게 된다는 것을 주의해야 한다.