달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

출처 : http://www-903.ibm.com/developerworks/kr/java/library/j-integrate.html

Struts, Tiles, JavaServer Faces 통합의 힘

Level: Advanced

Srikanth Shenoy, J2EE Consultant, Objectseek Inc.
Nithin Mallya , J2EE Consultant, Objectseek Inc.

2003년 9월 23일

JavaServer Faces (JSF)의 프론트-엔드 성능, Tiles의 콘텐트 포맷 능력, Struts 콘트롤러의 유연성이 J2EE 웹 애플리케이션에 들어있다면? 엔터프라이즈 자바 전문가 Srikanth Shenoy와 Nithin Mallya는 이 세 개의 특징을 통합하는 방법을 보여준다.

Struts, Tiles, JavaServer Faces (JSF)를 함께 사용함으로서 개발자들은 관리 및 재활용이 쉬운 강력하고 표현성이 뛰어난 웹 애플리케이션을 만들 수 있다.

Struts 프레임웍은 꽤 오랫동안 존재했고 사실상 J2EE 웹 애플리케이션을 개발할 때 의존하게 되는 표준이다. Tiles 프레임웍은 Struts 이후에 등장했고 컴포넌트 부분들을 사용하여 프리젠테이션 페이지를 어셈블링하는 기능을 선보임으로서 위상을 다졌다. 웹 애플리케이션 프레임웍의 신생아인 JSF는 인풋의 타당성검사와 유저 이벤트의 핸들링을 위한 메커니즘을 제공한다. 무엇보다 중요한것은 이것이 프로토콜에서 독립적인 방식으로 사용자 인터페이스 컴포넌트들을 렌더링한다는 점이다. ("The major players")

Struts와 JSF의 몇몇 기능들은 겹치기는 하지만 다른 방식으로 보완이 된다. 이 세 기술의 결합은 웹 애플리케이션을 개발하고 프리젠테이션을 구성하고 커스텀 유저 인터페이스 컴포넌트를 프로토콜과 독립적으로 렌더링하는 효율적인 방법을 제공할 수 있다.

이 글의 샘플 코드를 실행하려면 Struts 1.1, Tiles, JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0, Struts-Faces 0.4가 필요하다.

JSF
JSF 애플리케이션은 JSF 프레임웍을 사용하는 일반적인 J2EE 웹 애플리케이션이다. 이 프레임웍은 풍부한 GUI 컴포넌트 모델을 제공한다. 어떤 특정 기술이 놀라워도 룩앤필은 더욱 발전할 필요가 있다는 말을 들어본적이 있을 것이다. HTML 컴포넌트로 평이한 바닐라 페이지가 있던 시절은 지나갔고 뛰어난 GUI 룩앤필을 구현할 수 있다. 트리 컴포넌트, 메뉴 컴포넌트, 그래프는 기존에 존재했던 UI 컴포넌트이고 이것은 JSF가 당연히 제공해야 하는 것이다. JSF는 이에 더하여 쉽게 API를 사용할 수 있는 방법을 제공하여 커스텀 컴포넌트의 생성을 돕는다.

Note: 위에 언급한 UI 컴포넌트는 Sun에서 제공한 샘플의 일부이다.

Model-View-Controller (MVC) 패턴을 사용하는 전통적인 웹 애플리케이션에서, GUI 컴포넌트들은 프리젠테이션과 비지니스 로직을 핸들하는 커스텀 태그에 의해 표현된다. 이는 클라이언트 장치까지 코딩해야하는 문제를 낳았다.

JSF는 구조적으로 프리젠테이션 로직과 UI 컴포넌트의 비지니스 로직이 분리되어있다. JSP 페이지에서 JSF 태그를 사용하여 렌더러(RENDERER)와 UI 컴포넌트를 결합할 수 있다. 하나의 UI 컴포넌트는 다른 렌더러를 사용하여 다양한 방식으로 렌더링 될 수 있다. UI 컴포넌트 스팩의 코드는 서버에서 실행되고 사용자 액션으로 생성된 이벤트에 응답한다.

JSF-RI는 UI 컴포넌트에서 HTML을 렌더링하는 데 쓰이는 커스텀 태그 라이브러리와 함께 렌더 킷을 제공한다. 컴포넌트들의 룩앤필을 커스터마이징 하는 기능도 제공한다. 특별한 컴포넌트가 필요하면 특정 클라이언트 장치용 커스텀 태그를 만들 수 있고 UI 컴포넌트와 커스텀 렌더러와 이를 결합할 수 있다. 다른 디바이스의 경우 다양한 렌더러를 지정하면 된다.

JSF와 UI 컴포넌트
Java AWT 또는 Swing API를 사용하여 Java GUI 애플리케이션을 만든 경험이 있다면 JSF의 UIComponent 에도 익숙할 것이다. 이것은 자식 컴포넌트의 트리를 저장하고 클라이언트 쪽에서 발생한 액션들에 대해 표준 이벤트를 만든다. 이 이벤트들은 FacesContext에 캐싱된다. 커스텀 태그를 사용하여 이들 각 이벤트용 핸들러들을 결합할 수 있다. 예를 들어 커스텀 ActionListener가 있다면 사용자 클릭 또는 형식 제출을 핸들하게 된다.

JSF UIComponent, Renderer, 태그는 언제나 함께한다. 모든 JSF 커스텀 태그는 UIComponentTag를 하위클래스로 나눔으로서 생성된다. doStartdoEnd 메소드는 UIComponentTag 클래스에 이미 구현되었다. 이 태그 클래스에 추가 기능만 제공하면 된다.

그림 1은 커스텀 태그, UI 컴포넌트, 렌더러의 관계이다. 클라이언트 브라우저가 UI 컴포넌트 (MyComponent)용 JSF 태그(jsf:myTag)를 이용하여 JSP 페이지에 액세스한다. UI 컴포넌트는 서버에서 실행되고 적절한 렌더러(MyRenderer)를 사용하는 HTML로서 다시 클라이언트로 렌더링된다. JSP 페이지는 HTML에서 이들을 코딩하기 보다는 JSF-RI의 커스텀 태그를 사용하여 사용자 인터페이스 컴포넌트를 표현한다.

예를 들어, 그림 1은 h:panel:group 태그의 사용방법을 보여주고 있다. 이 태그는 한 부모 밑의 컴포넌트들을 그룹핑하는데 사용된다. panel_gridpanel_data 같은 다른 패널 태그와의 결합에 사용될 때 이것은 런타임시 HTML 테이블의 칼럼을 위한 마크업을 만든다. JSF-RI가 제공되는 html_basic 태그 라이브러리는 텍스트 필드, 버튼 등의 HTML 컴포넌트를 표현하는데 사용된다.

그림 1. JSF 페이지 렌더링
Rendering a JSF page

JSF 수명 주기
JSF 수명 주기는 6개의 단계로 구성되어 있다. 인커밍 리퀘스트는 모든 것을 통과하거나 리퀘스트, 밸리데이션, 수명 주기안에 일어나는 변환 에러, 응답 유형에 따라 어떤 단계도 통과하지 않을 수 있다. JSP 페이지에서 생성된 faces request는 JSF 프레임웍에 의해 핸들되고 faces 또는 non-faces 응답이 리턴된다.

JSF 폼(form)이 제출되거나 사용자가 URL에 /faces 라는 접두사를 가진 페이지를 가르키는 링크를 클릭할 때 faces request가 발생한다. 모든 faces requestFacesServlet에 의해 핸들된다.

어떤 JSF 컴포넌트도 없이 요청이 서블릿이나 JSP 페이지로 보내진 리퀘스트는 non-faces request 라고 한다. 결과 페이지에 JSF 태그가 있으면 faces response 라고 한다. JSF 태그가 없으면 이것은 non-faces response이다.

JSF 수명 주기에는 여섯 단계가 있다:

  • Reconstitute request tree(리퀘스트 트리 재구성)
  • Apply request values(리퀘스트 값 적용)
  • Process validations(타당성 검사)
  • Update model values(모델 값 업데이트)
  • Invoke application(애플리케이션 호출)
  • Render response(응답 렌더링)

JSF 스팩에 따르면 각 단계는 리퀘스트 프로세싱 수명주기의 논리적 개념을 나타내고 있다. 하지만 JSF-RI에서 이 단계들은 실제 클래스와 이것의 이름으로 표현된다.

faces 리퀘스트 다루기
JSF 리퀘스트 프로세싱을 이해하려면 FlightSearch.jsp를 연구해야 한다.(Listing 1) JSF 형식에는 도착/출발 도시용 인풋 텍스트 필드, 출발/도착 날짜, 형식 제출 및 리셋용 버튼이 포함되어 있다.

리퀘스트가 FacesServlet에 의해 받아들여지면 응답이 클라이언트에게로 렌더링되기 전에 다양한 단계를 거친다. 그림 2는 JSF 리퀘스트가 처리되는 방법이다.

1. 리퀘스트 받기
FacesServlet은 리퀘스트를 받아 FacesContextFactory 에서 FacesContext의 인스턴스를 취한다.

2. 수명 주기 프로세싱 전달하기
FacesServlet은 faces 콘텍스트로 전달된 Lifecycle 구현에 대한 execute 메소드를 호출함으로서 Lifecycle 인터페이스로 수명 주기 프로세싱을 전달한다.

3. 각 단계 마다 라이프사이클 실행
Lifecycle 구현은 Reconstitute Component Tree 단계를 시작으로 하여 각 단계를 실행한다.

4. 컴포넌트 트리 생성
Reconstitute Component Tree 단계에서 컴포넌트 트리가 travelForm의 컴포넌트들과 함께 만들어진다. 이 트리는 루트(root)로서 UIForm을, 이것의 자식으로서 다양한 텍스트 필드와 버튼을 갖는다.

fromCity 필드는 공백이 될 수 없음을 지정하는 타당성 규칙을 갖고있다. (validate_required 태그) 이 태그는 JSF Validator가 있는 fromCity 텍스트 필드를 링크한다.

JSF는 여러 빌트인 밸리데이터가 있다. 상응하는 Validator는 이 단계에서 초기화된다. 이 컴포넌트 트리는 FacesContext에 캐싱되고 콘텍스트는 다음 단계에서 사용되어 트리에 액세스하고 이벤트 핸들러를 호출한다. UIForm 스테이트는 자동으로 저장된다. 따라서 이 페이지가 리프레쉬 되면 이 형식의 원리 콘텐츠가 디스플레이된다.

5. 트리에서 값 뽑아내기
Apply Request Values 단계에서 JSF 구현은 컴포넌트 트리를 트래버스하고 decode 메소드를 사용하여 리퀘스트에서 값을 뽑아 각 컴포넌트에 맞게 로컬로 설정한다. 이 프로세스 동안 에러가 있다면 FacesContext에 놓이게되고 Render Response 단계에서 사용자에게 디스플레이된다.

또한 사용자 액션의 결과로 만들어져서 이 단계에 놓이게 된 이벤트들은 등록된 리스너에게 브로드캐스팅된다. 리퀘스트 버튼을 클릭하면 텍스트 필드의 값이 원래 값으로 설정된다.

6. Process Validations
Process Validations 단계에서 각 컴포넌트와 관련된 밸리데이션은 Apply Request Values 단계에서 설정된 로컬 값에 대해 수행한다. 이것은 JSF 구현이 각각의 등록된 밸리데이터에 대해 validate 메소드를 호출할 때 발생한다.

밸리데이션이 실패하면 수명 주기는 같은 페이지가 렌더링되는 Render Response 단계로 진행된다. 하지만 에러 메시지도 함께다. 이 단계에서 놓여진 이벤트들은 등록된 리스너에게 브로드캐스팅 된다.

JSF 구현은 소스 필드에 대한 밸리데이터를 처리한다. 데이터가 무효가 되면 콘트롤은 Render Response 단계로 전달되는데 이곳은 FlightSearch.jsp가 관련 컴포넌트에 대해 디스플레이된 밸리데이션 에러와 함께 다시 렌더링 되는 곳이다. JSP 페이지에서 output_errors를 선언함으로서 이 페이지의 모든 에러는 페이지 하단에 디스플레이된다.

7. 모델 객체 값 설정하기
Update Model Values 단계에서 모든 밸리데이션이 성공적으로 처리된 후에 JSF 구현은 각 컴포넌트에 대한 updateModel 메소드를 호출하여 유효한 것으로 모델 객체 값을 설정한다. 로컬 데이터를 모델 객체 속성에 의해 지정된 유형으로 변환하는 동안 에러가 발생하면 수명 주기는 Render Response 단계로 옮겨간다. 이곳에서 에러가 디스플레이된다. 형식 필드 속성의 값은 모델 객체의 애트리뷰트 값으로 파퓰레이트 된다.

8. ActionListener 호출
ActionListener와 사용자 액션을 결합할 수 있다.(Listing 1) Invoke Application 단계에서 processAction 메소드는 FlightSearchActionListener에 호출된다. 호출 시 processAction 메소드는 실제 시나리오에서 기준에 맞는 비행 데이터베이스를 검색하고 컴포넌트의 액션 애트리뷰트에서 결과를 검색한다.

9.응답 렌더링
Render Respons 단계에서 faces 설정 파일에서 검색되어 얻어진 페이지(FlightList.jsp)는 faces 콘텍스트에 에러가 없다면 디스플레이된다. 이전 페이지의 에러 때문에 콘트롤이 이 단계에 오면 FlightSearch.jsp는 에러 메시지와 함께 다시 디스플레이된다.

그림 2. JSF 리퀘스트 프로세스
그림보기.

Listing 1. FlightSearch.jsp
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <f:use_faces> <h:form id="flightForm" formName="flightForm" > <h:input_text id="fromCity" valueRef="FlightSearchBean.fromCity"> <f:validate_required/> <h:input_text/> <h:input_text id="toCity" valueRef="FlightSearchBean.toCity"> <h:input_text id="departureDate" valueRef="FlightSearchBean.departureDate"> <h:input_text id="arrivalDate" valueRef="FlightSearchBean.arrivalDate"> <h:command_button id="submit" action="success" label="Submit" commandName="submit" > <f:action_listener type="foo.bar.FlightSearchActionListener"/> </h:command_button> <h:command_button id="reset" action="reset" label="Reset" commandName="reset" /> <h:output_errors/> </h:form> </f:use_faces> 

JSF-RI의 두 개의 태그 라이브러리가 이 코드에 사용된다. html_basic 태그 라이브러리는 일반적으로 사용되는 HTML 컴포넌트용 태그를 정의하고 jsf-core 태그 라이브러리에는 리스너와 벨리데이터를 등록하는데 사용되는 태그가 포함되어 있다. 기타 태그로는:

  • f:use_faces 태그는 JSF 구현에 이 태그가 faces 태그라는 것을 가르킨다.

  • f:validate_required 태그는 어태치 되는 필드가 형식이 제출되기 전에 값을 가져야 한다는것을 나타낸다.

  • h:formh:input_text 태그는 flightSearchForm이라고 하는 HTML 형식과 다양한 텍스트 필드를 각각 나타낸다.

  • h:command_button 태그는 Submit과 Reset버튼을 구현에 사용된다.

  • 마지막으로 h:output_errors 태그는 Struts html:errors 태그와 비슷하고 형식 필드의 밸리데이션 동안 발생한 에러를 디스플레이한다.

FlightSearchBean이라고 하는 JavaBean은 Updated Model Values 단계에 있는 동안 UIComponent 데이터 부터 업데이트되는 모델을 나타낸다. 일반적으로 JavaBean은 jsp:useBean 태그와 함께 JSP 페이지에서 선언된다. FlightSearch.jsp에서 수행되지 않았다는 것에 주목하라. faces 설정 파일의 JSP 페이지들에 의해 사용되는 모든 JavaBean 컴포넌트들을 선언할 수 있는 곳에서 Managed Beans 라고 하는 JSF 기능을 사용할 수 있기 때문이다. 설정할 때 서블릿 콘테이너는 이러한 JavaBean 컴포넌트를 초기화한다. faces-config.xml 파일의 FlightSearchBean용 엔트리는 Listing 2에 나타나있다:

Listing 2. TravelInfoBean용 faces-config.xml 엔트리
 <managed-bean> <managed-bean-name>FlightSearchBean</managed-bean-name> <managed-bean-class> foo.bar.FlightSearchBean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> 

faces 응답 렌더링
결과 JSP 페이지들이 JSF 태그를 포함하고 있을 때 faces 응답은 Faces 애플리케이션에 의해 생성된다. 이 응답은 faces 또는 non-faces 리퀘스트의 결과가 될 수 있다.

우리 예제에서 Listing 1의 페이지 렌더링은 faces 응답이다. doStartTag()doEndTag()메소드에 익숙할 것이다. JSF와 Struts-Faces에서 각 태그는 UIComponentTag에서 확장된다. UIComponentTagdoStartTag()doEndTag() 메소드를 구현한다.

또한 추상 메소드인 getComponentType()getRendererType()을 갖고있다. 이 구체적 태그 클래스에 이 두 개의 메소드를 구현함으로서 컴포넌트와 렌더러를 각각 지정할 수 있다.

텍스트 필드를 가진 JSF 형식을 생각해보자. 다음 순서는 JSF 형식이 렌더링 될 때 실행된다.

1. doStartTag() 메소드 호출하기
Servlet 콘테이너는 FormTagdoStartTag() 메소드를 호출한다.

2. UIComponent 얻기
FormTaggetComponentType() 메소드에서 UIComponent를 얻는다. UIComponentTaggetComponentType()을 사용하여 faces-config.xml 파일로부터 이 컴포넌트에 대한 클래스 이름을 살피고 UIComponent(FormComponent)의 인스턴스를 만든다.

3. 렌더러 얻기
FormTaggetRendererType 메소드에서 Renderer를 얻는다. 컴포넌트 유형에서처럼 렌더러 이름은 faces-config.xml 파일에서 검색된다.

4.인코딩 메소드 호출
FormComponentFormRenderer가 만들어진 후에 encodeBegin() 메소드는 FormComponent에 호출된다. 각 태그를 위해 렌더링은 encodeBegin()으로 시작하고 encodeEnd()로 끝난다. encodeBegin() 메소드는 중첩 순서로 호출된다.

5. 태그의 끝과 HTML 렌더링
서블릿 컨테이너는 이 태그에 대해 doEndTag() 메소드를 호출한다. encodeEnd() 메소드는 각 컴포넌트에 대해 중첩의 역순으로 호출된다. 결국 Form과 모든 중첩된 컴포넌트들은 HTML로서 렌더링된다. 이 지점에서 HTML 생성은 종료되고 JSP와 동등한 HTML이 렌더링된다.

그림 3은 faces 응답의 생성을 구성하는 이벤트 순서이다.

그림 3. faces 응답 렌더링
그림보기.

통합의 이유
JSP와 관련 스팩이 발전함에 따라 JSF와 JSP Standard Tag Library 같은 새로운 표준이 등장하고 있다. 다음은 이 새로운 기술들을 하나의 통합된 전체로서 사용할 때의 이점들이다:

  • 작동과 프리젠테이션의 깨끗한 분리. 태그, 렌더러, 컴포넌트의 분리로 페이지 작성자와 애플리케이션 개발자의 역할은 정의가 더 분명해진다.

  • 컴포넌트의 프리젠테이션 변경이 큰 영향을 끼치지 못한다. 이제는 렌더러만 쉽게 변경하면 된다. 전통적인 MVC 모델에서 태그의 모든 변경은 비지니스 로직의 변경까지 수반했다.

  • 렌더러 독립. 다중의 렌더러로 다중의 프리젠테이션 디바이스용 컴포넌트 로직을 재사용함으로서 프로토콜 독립도 가능하다. 다양한 렌더러를 사용할 수 있다는 것은 특정 디바이스에 대한 전체 프리젠테이션 티어의 코딩 필요성도 감소한다는 것을 의미한다.

  • 커스텀 컴포넌트의 어셈블링과 재사용 표준. JSF는 "형식과 필드"를 넘어 생각하고 커스텀 GUI 컴포넌트 렌더링에 풍부한 컴포넌트 모델을 제공한다. JSF를 사용하여 각 페이지마다 컴포넌트의 모습과 작동을 커스터마이징 할 수 있다. 개발자들은 그들만의 GUI 컴포넌트를 만들 수 있는 데다가 간단한 커스텀 태그로 모든 JSP 페이지에 쉽게 포함시킬 수 있다. 자바 프론트엔드 GUI 컴포넌트인 AWT와 Swing과 마찬가지로 웹 페이지에 대한 커스텀 컴포넌트들이 있다!

Struts는 이미 사용자 기반이 넓게 형성되어 있는 프레임웍이다. 많은 IT 부서들은 MVC 프레임웍의 가치를 인식했고 꽤 오랫동안 사용해왔다. JSF는 Struts의 강력한 컨트롤러 아키텍쳐를 갖고있지 않다. 마찬가지로 표준화된 ActionFormActions도 없다. Tiles를 통합하면 완벽한 방식으로 레이아웃을 재사용 및 변경할 수 있다.

JSF가 가능한 Struts 애플리케이션의 마이그레이션은 두 가지 도전과제가 있다. 하나는 Struts 태그가 JSF 호환이 아니라는 점이다. 다시말하면 JSF 스팩에서 요구하는 대로 UIComponentTag를 확장하지 않는다. 따라서 JSF는 UIComponentRenderers를 해석할 수도 결합할 수도 없다.

두 번째, FacesServletRequestProcessor 사이에 어떤 연결도 없다. Struts 애플리케이션에서 RequestProcessor는 콜백 메소드로 ActionFormActions 클래스로의 모습을 관리한다. ActionForm 프로퍼티용 게터와 세터와 validate()ActionForm의 콜백 메소드이다. Action의 경우 execute()가 콜백 메소드이다. RequestProcessor가 호출되지 않는한 Struts ActionFormActions 클래스의 콜백 메소드는 비지니스 로직을 호출할 기회를 가질 수 없다.

Struts와 JSF를 Struts-Faces와 통합하기
이쯤되면, Struts와 JSF를 통합할 수 있는 소프트웨어가 있다면 또는 통합 소프트웨어를 직접 작성하는 방법에 대해 궁금할 것이다.

좋은 소식이 있다. 소프트웨어는 이미 존재하고 있다. Struts-Faces는 Struts JSF 통합 라이브러리의 초기 릴리스이다. 이 라이브러리는 Craig McClanahan이 만들었고 기존 Struts 애플리케이션을 JSF에 마이그레이션할 수 있도록 했다. Struts-Faces는 JSF를 이용한 깔끔한 통합을 위해 노력하고 있다. JSF가 프론트엔드에서 사용되고 백엔드에는 익숙한 Struts 컴포넌트가 있도록 말이다.

그림 4는 Struts-Faces와 JSF 클래스 관계이다. 파란색 클래스들은 Struts-Faces에 속한다.

그림 4. Struts-Faces 클래스 다이어그램
그림보기.

다음은 Struts-Faces의 주요 컴포넌트들이다:

  • FacesRequestProcessor 클래스는 모든 faces 리퀘스트를 핸들한다. Non-faces 리퀘스트는 부모인 RequestProcessor로 보내진다.

  • ActionListenerImpl 클래스는 ActionEvents를 핸들한다.

  • FormComponent는 JSF Form Component 로 부터 확장하지만 Struts 수명 주기 내에 호출된다.

  • 렌더러 FormComponent용 태그.

  • 아웃풋 전용으로 렌더링되는 데이터용 태그와 렌더러.

  • ServletContextListener의 구현은 초기화 동안 적절한 RequestProcessor를 등록한다.

  • filefaces-config.xml 파일. struts-faces.jar 파일에 번들된다.

Listing 3은 Struts-Faces 태그를 사용하는 FlightSearch.jsp 이다.

Listing 3. Struts-Faces 태그를 사용하는 FlightSearch.jsp
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-faces" prefix="s" %>  <f:use_faces> <s:form action="/listFlights"> <h:input_text id="fromCity" valueRef="FlightSearchForm.fromCity"/> <h:input_text id="toCity" valueRef="FlightSearchForm.toCity"/> <h:input_text id="departureDate" valueRef="FlightSearchForm.departureDate"> <h:input_text id="arrivalDate" valueRef="FlightSearchForm.arrivalDate"> <h:command_button id="submit" action="success" label="Submit" commandName="submit" /> <h:command_button id="reset" action="reset" label="Reset" commandName="reset" /> <s:errors/> </s:form> </f:use_faces> 

s:form 태그는 HTML 형식을 만드는데 사용된다. 이 형식 액션 애트리뷰트는 Listing 1에 지정된 형식 이름 flightForm 대신 /listFlights 이다. JSF에서 형식 이름은 UIForm에 할당된 이름일 뿐이다.

FlightSearchBean은 JSF Form용 모델이고 Update Model Values 단계에서 값을 얻는다. 하지만 Struts에서 형식 액션은 Struts Configuration File, struts-config.xml의 ActionMapping을 가르킨다. 작동 방법을 알려면 Listing 4의 struts-config.xml 파일을 참조한다.

/listFlightsActionMapping이, URI-path용 ActionFormfoo.bar.FlightSearchForm 이고 Action 클래스는 foo.bar.FlightSearchAction을 나타낸다는 것을 알게될 것이다.

Listing 4. struts-config.xml의 Action 선언
 <form-bean name="FlightSearchForm" type="foo.bar.FlightSearchForm"/> <!-- ========== Action Mapping Definition ========================= --> <action-mappings> <!-- List Flights action --> <action path="/listFlights" type="foo.bar.FlightSearchAction" name="FlightSearchForm" scope="request" input="/faces/FlightSearch.jsp"> <forward name="success" path="/faces/FlightList.jsp"/> </action> </action-mappings> 

Struts와 Tiles를 통합하는 다섯 단계

1. JSP를 만들어 사이트 레이아웃을 표현한다. 이것이 마스터 JSP로서 헤더, 바디, 풋터를 위한 플레이스홀더를 갖고있다. 이들 각각은 Tiles 태그를 사용하여 메인 JSP 페이지에 추가된다.

2. Tiles 정의 파일을 만들고 어떤 JSP 페이지가 전체 페이지의 플레이스홀더에 포함되어야 하는지를 정한다. 모든 페이지를 고유의 이름으로 지정한다.

3. struts-config.xml 파일에서 글로벌과 로컬 포워드를 변경하여 이전 단계에서 지정한 고유 이름들을 사용한다.

4. TilesPlugIn을 사용하여 시작하는 동안 Tiles 정의 파일을 로딩한다. TilesPlugIn 엔트리를 struts-config.xml 파일에 추가한다.

5. TilesRequestProcessor 엔트리를 struts-config.xml 파일에 추가한다. 이것이 Tiles-Struts 애플리케이션용 디폴트 요청 프로세스이다.

.do에 액션 애트리뷰트가 없는것을 눈치챘을 것이다. Struts-Faces가 형식 이름으로 형식 액션을 사용하기 때문이다.

또한 JSF validation 태그를 여기서는 사용하지 않았다. Struts에서 밸리데이션은 ActionForm 클래스에 대한 validate() 메소드에서 발생하기 때문이다. s:errors 태그는 Struts 에러 태그와 비슷하고 밸리데이션 동안 발생한 에러 메시지를 디스플레이하는데 사용된다.

어떤 ActionListener도 Submit 버튼과 결합되지 않았다. ActionListener가 이미 Struts-Faces에 제공되고 언제나 ActionEvents와 함께 faces 리퀘스트를 FacesRequestProcessor로 보내기 떄문이다.

Struts 애플리케이션을 JSF에 마이그레이션 하기
다음은 Struts 웹 애플리케이션을 JSF와 통합하는 단계이다:

  • JSF-specific JARs (jsf-api.jar, jsf-ri.jar)와 함께 struts-faces.jar 파일을 웹 애플리케이션의 WEB-INF/lib 디렉토리에 추가한다.

  • JSF와 JSTL을 사용 할 계획이면 JSTL-specific JARs (jstl.jar, standard.jar)를 WEB-INF/lib 폴더에 추가한다. 이 단계는 일반적 Tomcat에 전개 할 경우에만 필요하다. JWSDP는 이미 이러한 JAR를 제공한다.

  • 웹 애플리케이션 전개 디스크립터 (/WEB-INF/web.xml)를 변경하여 Faces Servlet 정의(Listing 5)용 엔트리를 갖는다.

  • JSP 페이지를 변경하여 Struts 태그 대신 JSF와 Struts-Faces 태그를 사용한다. 특히 html, base, form, errors 태그를 Struts-Faces의 해당하는 것으로 대체한다. text, textarea, radio 태그를 이에 상응하는 JSF 태그로 대체한다. Struts-Faces는 이를 위한 개별 태그를 갖고있지 않다. 필수사항은 아니지만 Struts Logic 태그를 해당 JSTL 태그로 대체하는 것도 고려할 수 있다.

  • JSF 태그를 사용하고 struts-config.xml 파일을 변경하여 JSP을 가르키는 Action Mappings의 global-forwardslocal-forwards/faces 접두사를 추가한다.

  • 웹 애플리케이션이 자신이 만든 커스텀 컴포넌트를 사용한다면 이들을 JSF 구현의 디폴트 RenderKit으로 등록해야한다. WEB-INF 폴더의 faces-config.xml에 파일을 만들고 각 컴포넌트와 렌더러용 엔트리를 추가하여 수행한다. 하지만 faces-config.xml 파일은 Struts-Faces자 파일에 이미 번들되어 있다. struts-faces.jar 파일에서 이를 빼어내어 콘텐츠에 추가하고 WEB-INF 폴더 밑에 놓는다.

Listing 5. web.xml의 FacesServlet 선언
 <!-- JavaServer Faces Servlet Configuration --> <servlet> <servlet-name>faces</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- JavaServer Faces Servlet Mapping --> <servlet-mapping> <servlet-name>faces</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> 

Struts-Faces와 Tiles 통합의 문제점
Struts-Faces 라이브러리는 Struts와 JSF 사이에 효율적인 다리를 제공한다. J2EE 웹 애플리케이션에 풍부한 프리젠테이션 레이어를 가능하게 한다. 이 결합물에 Tiles를 추가하여 보다 풍부하게 프리젠테이션 레이어를 만들 수 있다. 따라서 Struts와 JSF 결합에서 이득도 보고 다양한 JSP 페이지를 효율적으로 재사용할 수 있다.

이 글에서는 이미 Struts와 JSF의 통합을 설명했다. 그렇다면 Tiles를 여기에 추가하는 것은 식은죽 먹기 아니겠는가?

불행히도 JSF는 아직 초기 단계에 머물러있다. Struts-Faces 통합 소프트웨어는 계속 개발되어 JSF의 다양한 기능을 수용하지만 아직 Tiles는 지원하지 않는다.

Struts와 Tiles는 완벽히 상호 작동할 수 있다. 하지만 통합할 때 장애도 있다. 다음 섹션에서는 Struts-Faces 통합 라이브러리를 Tiles와 함께 사용할 때의 일반적인 문제점을 설명한다.

Listing 6은 Flight Search 페이지용 레이아웃이다. 이를 FlightSearch.jsp가 아닌 Flight Search로 부르는것에 주목하라. FlightSearch JSP가 foobar 여행사 웹 사이트상에서 사용자가 보는 전체 페이지의 바디이기 때문이다.

이제부터 FlightSearch.jsp를 있는 그대로 적용할 것이다. 앞으로 진행해가면서 변경할 것이다. 여러분은 Flight Search 페이지용 정의와 함께 Tiles 정의 파일을 만들어야 한다. Listing 7은 Tiles 정의 파일의 Flight Search Page용 엔트리이다. 마스터 레이아웃 템플릿과 extends 애트리뷰트가 재사용되었다.

Listing 6. Flight Search 레이아웃 예제
 <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %> <!-- Layout component parameters: header, menu, body, footer --> <s:html> <head> <title> <tiles:getAsString name="title"/></title> <s:base/> </head> <body> <TABLE border="0" width="100%" cellspacing="5"> <tr> <td><tiles:insert attribute="header"/></td> </tr> <tr> <td><tiles:insert attribute="body"/></td> </tr> <tr><td><hr></td></tr> <tr> <td><tiles:insert attribute="footer" /></td> </tr> </TABLE> </body> </s:html> 


Listing 7. Flight Search Page에 대한 Tiles 정의.
 <!-- Master Layout definition --> <definition name="foobar.master-layout" path="/faces/layout/MasterLayout.jsp"> <put name="title" value="Welcome to Foo Bar Travels" /> <put name="header" value="/faces/common/header.jsp" /> <put name="footer" value="/faces/common/footer.jsp" /> <put name="body" value="" /> </definition> <!-- Definition for Flight Search Page --> <definition name="/foobar.flight-search" extends="foobar.master-layout"> <put name="body" value="/faces/FlightSearch.jsp" /> </definition> 

응답 커밋
Flight Search Form에 액세스를 시도하자마자 보게될 첫 번째 문제가 바로 이것이다. 스택 트레이스를 자세히 살펴보자. 문제는 com.sun.faces.lifecycle.ViewHandlerImpl 클래스에 있다는 것을 알 수 있다. 이것은 ViewHandler 인터페이스를 구현하는 JSF-RI 클래스이다.

그림 2ViewHandler가 수행하는 역할이다. 이것은 리퀘스트를 다음 페이지로 보내는 클래스이다. 리퀘스트를 보낼 때 보내기 전에 응답 상태를 검사하지 않는다. 이것은 Tiles를 사용할 때만 발생하는데 Tiles는 JSP 페이지를 응답에 포함시키기 때문이다. 그리고 JSF-RI는 첫 번째 포워드 후에 응답을 커밋하고 그런다음 다시 다음 Tiles로 보내기를 시도한다.

이 문제를 해결하려면 응답이 커밋 되었는지의 여부를 결정할 수 있도록 응답 상태를 검사할 커스텀 ViewHandler를 구현해야 한다. 응답이 커밋되지 않으면 리퀘스트는 다음 페이지로 넘어간다. 그렇지않으면 리퀘스트는 포함되고 적당한 JSP가 디스플레이된다. ViewHandler 인터페이스를 구현하고 필요한 renderView() 메소드를 구현하는 STFViewHandlerImpl 클래스를 만들 것이다. (Listing 8):

Listing 8. STFViewHandlerImpl의 renderView() 메소드
 RequestDispatcher rd = null; Tree tree = context.getTree(); String requestURI = context.getTree().getTreeId(); rd = request.getRequestDispatcher(requestURI); /** If the response is committed, include the resource **/ if( !response.isCommitted() ) { rd.forward(request, context.getServletResponse()); } else { rd.include(request, context.getServletResponse()); } 

커스텀 ViewHandler를 구현했으니 JSF-RI에게 디폴트 구현 대신 자신의 ViewHandler를 사용한다고 어떻게 공지하겠는가? 이 질문에 대한 답으로 FacesServlet의 작동을 이해해야 한다.

Faces 초기화 과정 중 FacesServletLifecycleFactory 구현에 Lifecycle 클래스 구현을 리턴할 것을 의뢰한다. (Listing 9):

Listing 9. FacesServlet의 Faces 초기화
 //Get the LifecycleFactory from the Factory Finder LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory"); //Get the context param from web.xml String lifecycleID = getServletContext().getInitParameter("javax.faces.lifecycle.LIFECYCLE_ID"); //Get the Lifecycle Implementation Lifecycle lifecycle = factory.getLifecycle(lifeCycleID); 

Lifecycle 구현 객체는 Render Response 단계에서 사용될 ViewHandler를 갖고 있다. Lifecycle 구현에 setViewHandler 메소드를 호출하여 ViewHandler 구현이 디폴트가 되도록 할 수 있다.

이제 질문은 디폴트 Lifecycle 구현을 어떻게 얻을 수 있느냐이다. 답은 이럴 필요가 없다는 것이다. 그저 새로운 구현을 만들어 고유 ID를 가진 LifecycleFactory로 이를 등록하면된다. (Listing 10):

Listing 10. 커스텀 ViewHandler와 Lifecycle 등록하기
 //Get the LifecycleFactory from the Factory Finder LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory"); //Create a new instance of Lifecycle implementation - //com.sun.faces.lifecycle.LifecycleImpl //According to the documentation, factory.getLifecycle("STFLifecycle") //should work, but JSF-RI has a defect. //Hence this workaround of creating a RI class explicitly. LifecycleImpl stfLifecycleImpl = new LifecycleImpl(); //Create a new instance of our STFViewHandler and set it on the Lifecycle stfLifecycleImpl.setViewHandler(new STFViewHandlerImpl()); //Register the new lifecycle with the factory with a unique //name "STFLifecycle" factory.addLifecycle("STFLifecycle", stfLifecycleImpl); 

lifecycleIdSTFLifecycle 처럼 하드코딩된 것을 볼 수 있다. 사실 그렇지 않다. Listing 9를 다시 보면 명확해진다. FacesServlet은 lifecycle ID를 javax.faces.lifecycle.LIFECYCLE_ID라는 이름과 함께 web.xml 파일에서 선언된 콘텍스트 매개변수에서 취한다:

 <context-param> <param-name>javax.faces.lifecycle.LIFECYCLE_ID</param-name> <param-value>STFLifecycle</param-value> </context-param> 

FacesServlet이 초기화 동안 Lifecycle 구현 클래스에 대해 결정하기 때문에 Listing 10의 코드는 FacesServlet이 초기화 되기 전에 실행되어야 한다. 또 다른 서블릿을 만들어 FacesServlet 전에 이를 초기화하여 이를 수행할 수 있다.

보다 현명한 방법은 ServletContextListener 인터페이스를 구현하는 것이다. 이 클래스는 두 개의 메소드인 contextInitialized()contextDestroyed()를 선언한다. 이것은 웹 애플리케이션이 구현될 때와 웹 애플리케이션이 없어지기 전에 호출된다. Listing 10의 코드는 contextInitialized() 메소드에서 실행되고 커스텀 ViewHandlerSTFLifecycle이라는 이름으로 구분된 Lifecycle로 등록된다. FacesServlet도 가능하다. ServletContextListener 클래스 자체는 web.xml 파일에서 선언된다.

 <listener> <listener-class>foo.bar.stf.application.STFContextListener </listener-class> </listener> 

이것이 Lifecycle을 커스텀 ViewHandler로 등록하는 유일한 접근방식은 아니다. 사실 FactoryFinder는 고유의 발견 알고리즘을 구현하여 LifecycleFactory를 포함하여 Factory 객체를 발견한다. 이 메커니즘은 쉽기는 하지만 매력적이지는 않다.

404 Resource Not Found
커밋된 응답 문제가 해결되면 아무 Tiles 스팩 링크를 클릭하거나 Faces 응답을 렌더링하는 URL을 입력한다. 이 경우 URL을 입력하여 FlightSearchForm을 디스플레이 할 수 있다.

이렇게 하면 foobar.flight-search - 404 Resource Not Found 에러가 나타난다. foobar.flight-search는 Flight Search 페이지용 Tiles 정의 이름이다. FacesRequestProcessor는 Tiles 리퀘스트를 처리 할 능력이 없다. 따라서 에러가 생긴다.

이 문제를 해결하려면 STFRequestProcessor라는 새로운 리퀘스트 프로세서를 만들어야 한다. 지금까지는 FacesRequestProcessor의 모든 코드를 새로운 클래스에 복사했다. 유일한 차이점은 일반적인 RequestProcessor를 하위클래스로 나누는 대신 STFRequestProcessorTilesRequestProcessor를 하위클래스로 나눈다는 점이다. 이 새로운 RequestProcessor가 Tiles 리퀘스트를 핸들할 수 있다. (Listing 11)

Listing 11. STFRequestProcessor.java
 public class STFRequestProcessor extends TilesRequestProcessor { protected void doForward(String uri, HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException { //copy code from FacesRequestProcessor } protected void doInclude(String uri, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //copy code from FacesRequestProcessor } protected String processPath(HttpServletRequest request, HttpServletResponse response) throws IOException { //copy code from FacesRequestProcessor } protected String processPopulate(HttpServletRequest request, HttpServletResponse response) throws IOException { //copy code from FacesRequestProcessor } private void selectTree(FacesContext context, String uri) { //copy code from FacesRequestProcessor } } 

알다시피 Struts 프레임웍용 RequestProcessor는 struts-config.xml 파일에서 지정된다. STFRequestProcessor는 다음 엔트리가 struts-config.xml 파일에 추가될 때 프로세서가 된다.

 <controller processorClass="foobar.stf.application.STFRequestProcessor" /> 

Form submission
STFRequestProcessor 덕택에 Flight Search 페이지를 검색하고 볼 수 있다. 하지만 Flight Search 형식을 제출하자마자 응답으로 같은 형식을 받게된다. 그렇지만 헤더와 풋터가 없다. 밸리데이션 에러도 없다. 사실 밸리데이션 자체가 없는것이다.

어떤 상황인지 알고싶다면 Flight Search 페이지로 다시가서 HTML 소스를 보자. 다음과 같은 엔트리를 보게 될 것이다.

 <form name="FlightSearchForm" method="post" action="/flightapp/faces/FlightSearch.jsp"> 

형식 액션이 대신 JSP 페이지를 가르키고 있다. 이것이 바로 문제이다. Tiles와 Struts-Faces를 사용했기 때문에 발생하는 새로운 문제는 아니다. 이것은 형식 액션과 같은 JSP 이름을 갖기위한 Struts-Faces의 디폴트 작동이다. 이것은 하나의 JSP 페이지를 갖게 되면 문제없이 실행된다. Listing 3은 원래 FlightSearch.jsp이다. 액션을 다음과 같이 변경해보자.

 <s:form action="/listFlights.do> 

물론 변경 자체로는 문제를 해결할 수 없다. 이정도의 변경으로는 STFRequestProcessorActionForm을 찾을수 없다는 것도 알게될 것이다. 더 많은 변경이 필요하다.

계속 진행하기 전에 그림 5를 보자. Struts-Faces 형식용 faces 응답을 렌더링할때의 이벤트 결과를 보여주고 있다. FormComponent의 하이라이트된 createActionForm()메소드를 제외하고는 그림 3과 같다. Struts-Faces API에 의해 제공된 FormComponent 클래스는 javax.faces.component.UIForm의 특별한 서브클래스고 리퀘스트 또는 세션 범위에서 형식 빈들의 자동 생성을 지원한다.

그림 5. Struts-Faces 응답 렌더링
그림보기.

createActionForm() 메소드는 액션 이름을 사용하여 Struts 설정 파일에서 ActionMapping을 얻는다. /listFlights.doActionMapping이 없기때문에 Struts는 ActionForm을 찾을 수 없다.

org.apache.struts.util.RequestUtils를 사용하여 문제를 해결할 수 있다. RequestUtils의 정적 메소드인 getActionMappingName()은 경로(/x/y/z)나 접미사(.do) 매핑을 적절한 ActionMapping 으로 분해할 수 있다.

Listing 12는 createActionForm 메소드의 변경사항을 굵게 표시하여 보여주고 있다. Struts-Faces의 FormComponent를 변경하는 대신 FormComponent를 하위클래스로 나누고 createActionForm()메소드를 오버라이드 하여 새로운 STFFormComponent를 만든다.

Listing 12. FormComponent의 변경된 createActionForm() 메소드
 // Look up the application module configuration information we need ModuleConfig moduleConfig = lookupModuleConfig(context); // Look up the ActionConfig we are processing String action = getAction(); String mappingName = RequestUtils.getActionMappingName(action); ActionConfig actionConfig = moduleConfig.findActionConfig(mappingName); .... .... 

새로운 STFFormComponent에 변경이 더 필요하다. Struts-Faces는 액션 이름을 형식 이름으로 취급한다. 액션에는 .do 접미사가 있고 형식 이름에는 .do 접미사가 없기 때문에 변경이 필요하다. 따라서 새로운 속성인 actionSTFFormComponent에 추가하고 getAction()setAction() 메소드를 오버라이드한다.

FormRenderer 변경
Listing 10에서 했던 변경을 FormRendererencodeBegin 메소드에도 비슷하게 적용해야 한다.

FormRenderer를 하위클래스로 나누면 된다. HTML로 작성된 형식 액션도 변경해야 한다. (Listing 13):

Listing 13. FormRenderer 변경
 protected String action(FacesContext context, UIComponent component) { String treeId = context.getTree().getTreeId(); StringBuffer sb = new StringBuffer (context.getExternalContext().getRequestContextPath()); sb.append("/faces"); // sb.append(treeId); -- This is old code, replaced with // the two lines below. STFFormComponent fComponent = (STFFormComponent) component; sb.append(fComponent.getAction());  return (context.getExternalContext().encodeURL(sb.toString())); } 

FormTag 변경
이미 알고있겠지만 컴포넌트와 렌더러를 변경할 때 이 태그 역시 변경해야 한다. 이 경우 Struts-Faces의 FormTag에서 하위클래스로 나누어 STFFormTag 라는 새로운 태그를 만든다. 기능적인 부분은 바

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] Log4J...  (0) 2004.03.30
[펌] JSF 사용하기  (0) 2004.03.29
[펌] 리팩토링(Refactoring)  (0) 2004.03.29
[펌] 스트럿츠 이디엄  (0) 2004.03.29
[펌] 동적 서블릿 이미지 메이킹을 마음대로!  (0) 2004.03.29
Posted by tornado
|