IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  자바 | 웹 개발 | 오픈 소스  >

Seamless JSF, Part 3: JSF용 Ajax (한글)

Seam Remoting과 Ajax4jsf로 클라이언트와 서버 통합하기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Dan Allen, Senior Java engineer, CodeRyte, Inc.

2007 년 7 월 31 일

JSF의 컴포넌트 기반 방식은 추상화를 장려하고는 있지만, 대부분의 Ajax 구현들은 HTTP 를 기반으로 상호 작동하고 있습니다. Seamless JSF 시리즈의 마지막 글에서는 Seam Remoting API와 Ajax4jsf 컴포넌트를 사용하여, 서버의 빈들과 통신하는 방법을 설명합니다. JSF의 이벤트 중심 아키텍처로서 Ajax를 활용하는 것이 얼마나 쉬운지, 그리고 JSF 컴포넌트 모델을 희생하지 않고 이를 수행하는 방법을 설명합니다.
소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

대부분의 자바™ 개발자들은 매시업을 활용하고 있기 때문에, Seam과 Web 2.0으로 대표되는 기술들, 특히 Ajax가 어떻게 통합되는지 의아할 것이다. 여러분이 Seam을 사용하여 JSF에서 부분적인 페이지 업데이트를 실행하거나, JSF 애플리케이션 매시업을 Google Maps와 함께 사용한다면 매우 좋지 않겠는가? 그런데, 이것이 가능하다.

Seamless JSF 시리즈의 마지막 기술자료에서는, Seam Remoting API와 Ajax4jsf 컴포넌트를 사용하여 JSF 기반 애플리케이션에서 Ajax 스타일의 인터랙션을 이룩하는 방법을 설명하겠다. Seam과 Ajax 결합의 최고봉은 JavaScript XMLHttpRequest 객체 없이도 멋진 Web 2.0을 수행할 수 있다는 사실이다. Seam Remoting과 Ajax4jsf를 사용해서, 서버에서 내부 빈들과 통신할 수 있다. 브라우저와 서버 상태는 동기화 되고, 통신을 실행하는 저수준 API들을 다룰 필요가 없다.

Seam에서 Ajax 프로그래밍에 대한 새로운 컴포넌트 기반의 접근 방식을 활용하는 방법부터 살펴보자. Seam Remoting API를 사용하여 Ajax를 통해 JavaScript와 서버 측 객체들간 통신하는 방법을 배울 것이다. Ajax에 대한 새롭고 더 쉬운 방식을 이해한다면 다음과 같이 Open 18 애플리케이션을 강화할 수 있다.

  • Open 18 코스 디렉토리와 Google Maps 간 매시업을 만든다.
  • Ajax4jsf를 사용하여 애플리케이션의 코스 디렉토리 페이지와 코스 상세 페이지를 합병한다.
  • 애플리케이션의 Spring 통합을 활용하여 Seam Remoting 라이프 사이클 동안 Spring을 사용한다.
Seam 1.2.1.GA 업그레이드
Seam의 세계는 빠르게 변하고 있고, 본 시리즈의 마지막 기술자료 이후 새로운 버전의 프레임웍이 출시되었다. 이 글의 예제는 Seam 1.2.1 또는 이후 버전을 기반으로 한다. 초기 Seam 버전에서는, 서버 측 리모팅(remoting) 기능이 SeamRemotingServlet에 의해서 핸들되었다. 최신 배포판에서는, 이 기능은 Seam Remoting 패키지로 리팩토링 되었고, 일반적인 ResourceServlet은 리모팅 호출 같은 비 JSF 요청의 핸들링을 위임하는데 사용된다. 또한, 리모팅 라이브러리는 개별 JAR, jboss-seam-remoting.jar로서 패키징 된다. 애플리케이션의 클래스 경로에 JAR를 포함시키고, Seamless JSF, Part 1: JSF에 맞춘 애플리케이션 프레임웍"의 예제를 실행하기에 알맞은 JAR 리스트도 포함시켜야 한다.

Open 18과 Google Maps 간 매시업은 사용자가 지도 상에서 골프 코스 디렉토리에서 위치를 구상할 수 있도록 한다. 코스 디렉토리와 코스 상세 페이지를 결합하면 (그리고 기반 코드를 Ajax화 하면 (Ajaxifying)) 새로운 페이지를 로딩하지 않고 코스 상세를 나타낼 수 있다. Spring 빈과 Seam Remoting을 통합하면 Google Maps 위치 마커의 재 배치를 포착할 수 있고, 데이터베이스 상에 상응하는 코스의 위도와 경도를 저장할 수 있다. 결국, 골프 선수가 즐겨 사용할 수 있는 Web 2.0 스타일의 애플리케이션이 만들어지는 것이다.

과거의 과도하게 복잡한 JavaScript 중심의 Ajax 프로그래밍에 지쳤거나, 복잡함을 다루기 싫어서 지금까지 Ajax를 회피해왔다면, 이 글에서 그 두려움을 몰아내기 바란다. 애플리케이션을 리팩토링 할 때 약간의 JavaScript 코딩을 수행하지만, 대부분의 Ajax 구현들과는 달리, JavaScript가 코드에서 많은 부분을 차지하지 않을 것이다. 다만, 서버 측 자바 객체들을 확장시킬 것이다.

Ajax에 대한 또 다른 방식

애플리케이션의 메모리 관리를 피하듯이, 저급의 Ajax 요청 프로토콜을 다루고 싶지 않을 것이다. 이를 수행하면 멀티브라우저 지원, 데이터 마샬링, 동시성 위반, 서버 로드, 커스텀 서블릿과 서블릿 필터 같은 두통 거리들이 수반된다. 무엇보다도 가장 큰 골칫거리이자, 여러분이 진정으로 피하고 싶은 하나는 Stateless 요청-응답 패러다임의 의도하지 않은 노출로서, 이는 JSF 같은 컴포넌트 기반의 프레임웍이 피하고자 하는 바이다.

JSF 라이프 사이클에서는 기반 서블릿 모델에서 애플리케이션 코드를 제거함으로써 강력한 컴포넌트 지향 디자인을 만든다. Ajax로 작업할 때 이러한 추상화를 계속 유지하면, Seam Remoting 또는 Ajax4jsf에 저급의 작업에서 손을 뗄 수 있다. 이러한 라이브러리들은 JSF 컴포넌트를 Ajax를 통한 브라우저 인터랙션에 혼합하는데 필요한 것을 측정한다. 그림 1은 Seam Remoting의 실행 모습이다. 사용자가 버튼을 클릭할 때처럼 이벤트가 시행되면, 메시지는 서버에 있는 컴포넌트로 비동기식으로 보내질 수 있다. 응답을 받으면, 이는 페이지를 점증적으로 업데이트 하는데 사용될 수 있다. 브라우저와 서버 측 컴포넌트 간 인터랙션을 활용하는데 사용되는 저급 통신 프로토콜은 API 뒤에 남아있게 된다.

시리즈 소개
Seamless JSF는 JSF에 정말로 잘 맞는 최초의 애플리케이션 프레임웍인 Seam에 대해 설명합니다. 이 글을 읽고 Seam이 과연 JSF를 보완할 수 있을지를 여러분 스스로 평가해 보기 바랍니다.

그림 1에 나타난 유스 케이스에서, 사용자에게는 버튼을 클릭한 후에 발생하는 메소드 호출의 결과가 제공된다. 이 유스 케이스를 공부할 때 기억해야 할 두 개의 중요한 포인트가 있다. (1) 페이지는 결코 리프레쉬 되지 않고 (2) 클라이언트 코드는 컴포넌트에 있는 메소드와 투명하게 통신한다. URL을 구현하여 요청하지 않는다. 표준 HTTP 요청은 뒤에서 사용되고 있지만, 클라이언트 코드는 HTTP 프로토콜과 직접 상호 작동 할 필요가 없다.


그림 1. Seam Remoting과 JSF 컴포넌트와 브라우저 혼합
Seam Remoting use-case 다이어그램

Seam Remoting과 Ajax4jsf

Seam Remoting과 Ajax4jsf는 JSF의 "Ajaxification(Ajax화)"를 목표로 한 라이브러리들이다. 이 두 라이브러리는 Ajax를 사용하여 브라우저와 서버간 통신이 사용자에게 보이지 않게, 백그라운드에서 비동기식으로 발생하는 인터랙션 모델을 도입한다. 결국, 서버에서 메소드를 실행하기 위해 페이지 재 로드에 시간을 낭비할 필요가 없는 것이다. 이러한 라이브러리가 만든 Ajax 요청에서 서버에서 가져온 정보는 페이지의 상태를 "실시간"으로 업데이트 하는데 사용될 수 있다. 이 두 개의 라이브러리들은 브라우저가 필요로 할 때마다 컴포넌트의 상태를 복원하는 라이프 사이클을 만든다. 이러한 Ajax 인터랙션은 요청이 아니라 "복원 및 실행"에 가깝다. 브라우저는 서버에 가볍게 신호를 보내고, 서버 측 내부 빈들 중 하나에 메소드를 실행하고 결과를 리턴한다.

비록 이 두 개의 라이브러리는 다르게 작동하지만, 상호 배타적인 것은 아니다. 이 두 개 모두 JSF 컴포넌트 모델을 준수하고 있기 때문에, 쉽게 결합될 수 있다. Ajax 스타일의 인터랙션을 JSF 애플리케이션에 결합하는 접근 방식에 대해 알아보자.

  • Seam Remoting은 메소드 호출을 통해 데이터를 보내거나 가져오기 위해 JavaScript에 있는 서버 측 컴포넌트에 액세스 할 수 있는 JavaScript API를 제공한다. Seam Remoting은 커스텀, 비 JSF 라이프 사이클을 사용하여 브라우저가 서버 측 컴포넌트와 통신할 수 있도록 한다. Seam 컨테이너와 컴포넌트만 이러한 요청들 동안 복구된다. 전송 프로토콜은 Ajax이지만, 패킷들이 전송되는 상세한 방법들에 대해서는 신경 쓸 필요가 없다.

  • Ajax4jsf는 JavaScript 사용을 완벽하게 숨김으로써 추상화를 더욱 강력하게 실현한다. 모든 로직을 기본 UI 컴포넌트 안에 래핑한다. Ajax4jsf는 전체 JSF 라이프 사이클을 통해 Ajax 요청을 받는다. 따라서, Ajax 실행 컴포넌트들은 서버에서 액션 핸들러를 실행하고, JSF 컴포넌트 트리를 업데이트 하고, 페이지의 부분들을 재 렌더링 한다. 브라우저-네비게이션 이벤트를 실행할 필요가 없다. 통신은 Ajax를 통해 이루어지지만, 이 모든 것이 뒤에서 발생하기 때문에 페이지 개발자에게는 보이지 않는다. Ajax4jsf의 컴포넌트 지향 방식은 Ajax 기능이 JSF의 자연스러운 일부가 되도록 한다.

이 두 개의 접근 방식들을 자세히 설명하기에 앞서 Ajax 기초를 먼저 설명하겠다.




위로


차이 메우기

Ajax/Web 2.0 관점에서 "리치(rich)" 애플리케이션을 만들려면, 웹 브라우저(클라이언트)는 서버의 컴포넌트에 직접 액세스 할 수 있어야 한다. 하지만 클라이언트와 서버 간 틈 때문에 도전이 된다. 이 틈새의 한 측에는(네트워크) 클라이언트 브라우저가 있고, 다른 측에는 서버와 컴포넌트가 있다.

실제로, 대부분의 전통적인 웹 애플리케이션에서는, 클라이언트와 서버는 이미 잘 통신하고 있고, 다만 인터랙션은 일방향이다. 서버가 이야기하고, 브라우저가 듣는다. 여러분도 이러한 유형의 대화에 익숙해졌을 것이다. Ajax 통신이 없는 세상에서는, 브라우저가 URL에 대한 동기식 요청을 보낼 수 있지만, 서버가 다시 보내는 HTML을 렌더링 해야 한다. 이러한 유형의 인터랙션의 또 하나의 단점은 기다림이다.

HTTP라는 기초적인 언어에서는, 브라우저 클라이언트는 서버가 HTML을 생성하는 방법에 무지하고, 따라서 컴포넌트를 전혀 모른다. 브라우저의 관점에서, 페이지 생성 프로세스는 그저 블랙 박스일 뿐이다. 브라우저는 서버에 URL의 형태로 다른 질문을 요청하고, 요청 매개변수와 POST 데이터로 압축된 힌트를 전달하지만, 이것이 실제로 서버의 언어로 이야기 하는 것은 아니다. 브라우저가 애플리케이션의 서버 측 액티비티를 보게 하려면, 보다 고급의 통신 수단을 확립해야 한다. 결정적인, 페이지 지향 방식으로는 해결할 수 없다.

Ajax 클라이언트로서의 웹 브라우저

Seam Remoting과 Ajax4jsf는 클라이언트와 브라우저 컴포넌트 간 문제를 해결하는데 있어서 확실히 다른 접근 방식을 취한다. Seam Remoting은 브라우저의 네이티브 언어인 JavaScript를 제공한다. 이것을 통해서 서버 측 컴포넌트의 메소드가 액세스 될 수 있다. 액세스를 가능케 하려면, @Remote 주석을 통해 "원격"으로 분명히 표시되어야 한다.

Seam Remoting의 호출 메커니즘은 JavaScript가 로컬 프록시 객체 또는 "스텁"을 사용하여, 원격 서버에 있는 컴포넌트에 메소드를 호출하게 함으로써, 자바 RMI에 부응한다. 클라이언트가 관여되어 있는 한, 이 스텁 객체는 원격 객체이다. 스텁은 실제 원격 객체에 대해 메소드를 실행하는 책임을 맡는다. 원격 메소드가 호출될 때, 응답은 메소드 호출의 리턴 값을 캡슐화 한다. 리턴 값은 와이어(wire)를 통해 돌아올 때 JavaScript 객체로서 언마샬링(unmarshal)된다. 따라서, Seam Remoting은 브라우저가 서버의 네이티브 언어를 이야기 하게 한다. 자바 코드와 JavaScript는 결국 하나가 된다.

Ajax와의 대화
Ajax에 대한 Seam의 접근 방식은 브라우저가 어떤 대회가 활성화 되었는지 같은 서버 상태에 대한 복잡한 상세를 감추는 것이다. 대화 범위의 객체들에 대한 원격 호출을 할 때 Seam Remoting을 사용하여 대화 콘텍스트를 관리하는 기능은 중요하다.

한편, Ajax4jsf는 서버 측 내부 빈에서 UI 이벤트와 액션 핸들러를 제휴하는 JSF 컴포넌트 태그를 제공한다. 이러한 액션 핸들러 메소드는 "원격"으로 표시될 필요가 없다. 대신, 이들은 전통적인 JSF 액션 핸들러이고, 어떤 인자도 갖고 있지 않거나, ActionEvent를 받아들이는 내부 빈에 대한 퍼블릭 메소드이다.

Seam Remoting과는 달리, Ajax4jsf는 JSF 컴포넌트 트리의 변경 사항들을 브라우저에 알린다. 이러한 변경 사항들은 XHTML 조각의 형태로 리턴된다. 이 조간들은 페이지에서 개별 JSF 컴포넌트와 연관되고, 부분적인 페이지 업데이트로서 드러난다. 따라서, 페이지의 고립된 영역들은 새로운 마크업을 사용하여 브라우저에 의해 재 렌더링 될 수 있다. 이러한 조각들은 Ajax4jsf 컴포넌트 태그의 reRender 애트리뷰트를 사용하거나 ajaxRendered="true" 애트리뷰트로 표시된 Ajax 아웃풋 패널에 템플릿 영역들을 래핑함으로써, 이러한 조각들은 구체적으로 요청된다. reRender 애트리뷰트는 컴포넌트 ID에 의해 참조된 재 렌더링 되어야 하는 특정 컴포넌트 세트를 가리킨다. 반대로, ajaxRendered="true"의 사용은 블랭킷(blanket) 방식으로, 모든 "Ajax rendered" 영역들이 Ajax4jsf에 의해 관리되는 Ajax 요청이 끝날 때마다 업데이트 된다.

Ajax4jsf와 Seam Remoting은 기본적인 HTML 렌더러(renderer)에서 완전히 성숙한 Ajax 클라이언트로 성숙했다. 이러한 두 개의 프레임웍들을 통합하면 애플리케이션은 진정으로 흥미로워 진다. 사실, Seam Remoting과 Ajax4jsf의 기능을 결합하면 커스텀 Ajax JSF 컴포넌트를 개발하지 않아도 된다. 기존의, 비 Ajax 실행 JSF 컴포넌트를 Ajax 통신에 열거할 수 있다. a4j:support 태그를 선언 내에 중첩시킬 수 있다. UI 컴포넌트 밖에서 작업하고(Google Maps 매시업에서 나중에 수행) 서버 측 컴포넌트 정보를 검색, 업데이트 또는 연산을 수행해야 한다면 Seam Remoting을 사용하여 이러한 인터랙션을 관리한다.

Seam Remoting과 Ajax4jsf는 기능적으로 겹치는 부분이 있지만, 결국 이 두 방식 모두 Ajax 스타일의 인터랙션을 애플리케이션에 접목시킨다. 더욱이, 여러분도 곧 알게 되겠지만, 이 두 개의 라이브러리들은 JSF 애플리케이션에 완벽한 JSF 솔루션을 제공한다.




위로


Seam Remoting 시작하기

Seam Remoting이 구현하기가 복잡하지 않은 경우에만 이상적인 솔루션이라고 생각할 수도 있겠다. 두려워 말라! Seam Remoting은 여러분이 생각하고 있는 무시무시한 원격 EJB 객체들과는 다르다. Seam Remoting API를 사용할 때의 최대의 장점은 JavaScript 코드를 실행하여 서버 측 컴포넌트와 인터랙팅 하는 것이 매우 간단하다는 점이다. Seam은 이 모든 작업을 수행하기 때문에, 여러분이 단 한 줄의 XML을 편집 할 필요가 없다. (자바 프로그래밍 보다 XML 프로그래밍을 수행한다면 더욱 편리해진다.)

Seam Remoting을 사용하여 JSF 애플리케이션을 "Ajax화(Ajaxify)"하는 방법에 대해 알아보자.

빈(bean) 노출하기

서버 측 객체 메소드를 원격 Ajax 클라이언트에 노출하는데 두 가지 요구 사항이 있다. 메소드는 Seam 컴포넌트의 퍼블릭 멤버가 되어야 하고, @WebRemote 주석으로 준비되어야 한다. 이것이 전부이다.

Listing 1을 보면 이것이 매우 단순하다는 것을 알 수 있다. 여기에서, Seam 컴포넌트 ReasonPotAction은 하나의 메소드, drawReason()을 원격 실행을 위해 Ajax 클라이언트로 노출한다. 이 메소드가 스텁에서 호출될 때마다, 호출은 인터넷을 통해 서버로 전달되고, "10 reasons to use Seam for your next project" 리스트 중 하나를 무작위로 선택한다. 이때 서버 측 컴포넌트에 상응하는 메소드를 사용한다. 서버는 와이어를 통해 클라이언트로 값을 리턴한다. (참고자료)


Listing 1. 원격 메소드 호출 노출하기
                @Name("reasonPot")

@Scope(ScopeType.SESSION)

public class ReasonPotAction {

	private static String[] reasons = new String[] {

		"It's the quickest way to get \"rich\".",

		"It's the easiest way to get started with EJB 3.0.",

		"It's the best way to leverage JSF.",

		"It's the easiest way to do BPM.",

		"But CRUD is easy too!",

		"It makes persistence a breeze.",

		"Use annotations (instead of XML).",

		"Get hip to automated integration testing.",

		"Marry open source with open standards.",

		"It just works!"

	};

	

	private Random randomIndexSelector = new Random();

	

	@WebRemote

	public String drawReason() {

		return reasons[randomIndexSelector.nextInt(reasons.length)];

	}

}


리소스 제공하기

서버 측 컴포넌트가 설정되면, @WebRemote 메소드를 호출하도록 브라우저를 준비시켜야 한다. Seam은 커스텀 서블릿을 사용하여 HTTP 요청을 핸들하여 원격 메소드를 실행하고 결과를 리턴한다. 걱정할 필요가 없다. 여러분은 서블릿과 직접 교류할 필요가 없다. Seam Remoting JavaScript 라이브러리가 XMLHttpRequest 객체의 모든 측면들을 관리하는 것과 같은 방식으로 Seam 서블릿과의 모든 인터랙션을 관장한다.

서블릿에 대해 생각해야 하는 유일한 때는 애플리케이션에 Seam Remoting을 설정해야 할 때이다. 이 때에도, 여러분은 Seam의 커스텀 서블릿을 한번만 설정한다. 커스텀 서블릿의 서비스에 필요한 Seam 기능들의 수에 상관이 없다. 브라우저에 에셋(JavaScript 파일)을 제공하거나 비 JSF 요청(Ajax 리모팅 호출)을 처리하는 기능을 위해 특화된 서블릿을 사용하는 대신, Seam은 이러한 의무를 하나의 컨트롤러 Resource Servlet에 지운다. 이 서블릿은 위임 체인 모델을 사용하여, 그 의무를 등록된 핸들러로 전달한다. 예를 들어, Remoting 객체는 스스로를 등록하여 Seam Remoting JavaScript 라이브러리에 의해 보내진 모든 Ajax 요청들을 받는다.

Listing 2의 Resource Servlet에 대한 XML 정의는 애플리케이션의 web.xml 파일에 만들어져야 한다. 이것은 보통 Faces Servlet 밑에 있다.


Listing 2. Seam Resource Servlet용 XML 정의
                <servlet>

  <servlet-name>Seam Resource Servlet</servlet-name>

  <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>

</servlet>

<servlet-mapping>

  <servlet-name>Seam Resource Servlet</servlet-name>

  <url-pattern>/seam/resource/*</url-pattern>

</servlet-mapping>


API 부트스트랩

Seam Remoting 메커니즘의 진짜 마법은 JavaScript 라이브러리에서 발생한다. Resource Servlet은 작업을 Remoting 객체에 위임하여 이러한 라이브러리들을 제공하도록 한다. 두 개 중에서 방출된 JavaScript 라이브러리들은 JavaScript를 통해 원격 메소드를 호출할 수 있도록 브라우저에 사용할 수 있는 적절한 후크를 만든다.

여러분은 Listing 3에 나타난 두 개의 JavaScript 라이브러리들을 페이지로 가져와야 한다. 첫 번째 라이브러리인 remote.js는 정적이고 Seam의 클라이언트 측 원격 프레임웍을 브라우저로 가져간다. 두 번째 라이브러리인 interface.js는 각 요청에 대해 동적으로 생성된다. 서버 측 컴포넌트와 인터랙팅에 필요한 원격 스텁과 복합 유형들을 포함하고 있다. Seam은 메소드를 원격으로 지정하는 모든 컴포넌트에 대해 스텁과 복합 유형들을 생성하지 않는다. 대신, interface.js URL의 쿼리 스트링 다음에 ? 심볼을 파싱하여 노출할 컴포넌트의 이름들을 모은다. 각 컴포넌트 이름은 &amp; 심볼로 분리된다. 이러한 스택 방식을 사용하면 외부 JavaScript 파일에 대한 브라우저에 의한 요청의 수가 최소한으로 유지된다. Seam은 그러한 컴포넌트에 스텁과 복합 유형들을 생성한다. 예를 들어, Listing 3의 두 개의 JavaScript는 Seam Remoting 라이브러리를 로딩하고 Seam에게 reasonPotanotherName 컴포넌트를 준비할 것을 명령한다.


Listing 3. 클라이언트 측 프레임웍과 API 반입하기
                <script type="text/javascript"

src="seam/resource/remoting/resource/remote.js"></script>

<script type="text/javascript"

src="seam/resource/remoting/interface.js?reasonPot&amp;anotherName"></script>


이제 다 준비가 되었다. Seam Remoting 설정을 완료했으니 원격 호출을 시작할 차례이다.




위로


원격 호출하기

Seam Remoting 라이브러리에 대한 모든 클라이언트 측 프레임웍 코드는 Seam JavaScript 객체로 캡슐화 된다. 실제로, 이러한 탑 레벨 객체는 간단히 네임스페이스이다. 모든 기능들을 고유 이름으로 묶을 수 있는 컨테이너이다. JavaScript에서, 네임스페이스는 정적 프로퍼티, 정적 메소드, 기타 중첩 네임스페이스 객체들을 보유하고 있는 객체에 지나지 않는다. 중요한 점은 Seam Remoting 라이브러리의 JavaScript가 다른 JavaScript 라이브러리와 잘 작동한다는 점이다. 다시 말해서, Prototype 또는 Dojo 같은 라이브러리를 활용하여 JSF UI가 서버 측 컴포넌트와 인터랙팅 하도록 할 수 있다는 의미이다. JSF UI에서 서드 파티 JavaScript 라이브러리를 사용하는 유연성은 과거에는 JSF 개발자들에게는 거대한 도전이었기 때문에, Seam JavaScript 객체는 환영할만하다.

Seam Remoting 라이브러리의 기능은 Seam.ComponentSeam.Remoting 네임스페이스 객체들로 나뉜다. Seam.Component의 정적 메소드는 원격 Seam 컴포넌트들로의 액세스를 제공하는 반면, 정적 Seam.Remoting 메소드는 원격 설정을 제어하고 커스텀 데이터 유형들을 만드는데 사용된다. 나중에 예제에서 보게 되겠지만, 커스텀 데이터 유형은 비 프리머티브 객체로서, 여러분이 로컬에서 생성해야 한다.

실행 상세

원격 메소드를 실행하려면, 메소드를 보유하고 있는 컴포넌트의 인스턴스를 획득해야 한다. (서버에 있는)기존 Seam 컴포넌트 인스턴스를 사용하기 원하면 Seam.Component.getInstance()를 사용할 수 있고, 또는 호출하기 전에 새로운 인스턴스를 만들려면 Seam.Component.newInstance()를 사용한다. 이러한 인스턴스들은 Seam에 의해 동적으로 구현된 원격 스텁들이다. 이 스텁은 모든 메소드 호출을 대신하면서, 컴포넌트 이름, 메소드, 인자들을 XML로 정렬하고, Ajax 요청을 사용하여 서버 측 컴포넌트로 와이어를 통해 XML 페이로드를 전달한다. 서버에 있는 원격 프레임웍 코드는 이러한 요청들을 선택하여, XML을 정렬 해제(unmarshal)하고, 컴포넌트 이름, 메소드, 인자를 추출하고, 서버 측 컴포넌트 인스턴스에서 메소드를 호출한다. 마지막으로, 서버는 리턴 값을 Ajax 호출에 대한 응답으로 클라이언트로 보낸다. 다시 말해서, 이 모든 작업은 스텁 뒤에 숨겨지기 때문에, JavaScript 코드를 매우 단순화 할 수 있다.

지금까지는 배경 설명이었다. Listing 4는 세션 범위의 reasonPot 컴포넌트에 drawReason() 메소드를 실행하는 방법을 나타낸다. 이것은 Listing 1의 원격 서비스로서 플래그가 달렸다. 컴포넌트 스텁이 생기면, 메소드 실행은 다른 JavaScript 메소드 호출과 비슷하다.


Listing 4. 원격 메소드 호출하기
                <script type="text/javascript">

Seam.Component.getInstance("reasonPot").drawReason(displayReason);



function displayReason(reason) {

  alert(reason);

}

</script>


Listing 4에서, 스텁의 메소드와 서버 측 컴포넌트의 "실제" 메소드 사이에 중요한 차이가 있음을 알 수 있다. 무엇인지 알겠는가? 컴포넌트 스텁에서 이루어진 모든 호출이 비동기식으로 수행된다고 생각해 보자. 원격 메소드 호출의 결과는 실행 쓰레드에 즉각 사용할 수 없다. 마찬가지로, 스텁의 메소드에는 리턴할 값이 없다. 매칭하는 서버 측 메소드가 값을 갖고 있는지 여부와는 상관 없다. 원격 스텁의 메소드가 실행될 때, 여러분이 제공한 숫자는 "콜백" JavaScript 함수이다. 이것이 하는 일은 실제 리턴 값을 캡쳐하는 것이다.

콜백 함수는 비동기식 API에서의 표준 구조이다. Ajax 요청에서 응답이 다시 브라우저에 도착할 때 Seam Remoting JavaScript 프레임웍에 의해 실행된다. 서버 측 컴포넌트의 메소드가 유효 리턴 값을 갖고 있다면, 이 값은 유일한 인자로서 콜백 함수로 전달된다. 한편, 서버 측 컴포넌트에 대한 메소드가 무효 리턴 유형을 갖고 있다면, 이 매개변수를 무시할 수 있다.

컨버세이션 범위 빈

원격에 대한 필요 대부분, 지금까지 설명했던 API를 사용하여 얻을 수 있다. 서버 측 컴포넌트를 검색하거나 노출된 메소드들 중 하나를 실행하기 위한 요청을 하면, 컴포넌트는 원격 라이프 사이클 범위 내에 보여야 한다. 세션 범위 빈을 참조하기 위해서 추가 작업이 필요 없다. 같은 브라우저 세션 내에서 이루어진 HTTP 요청을 사용할 수 있기 때문이다.

컨버세이션 범위 빈들은 트릭이 필요하다. 이들은 컨버세이션 토큰의 상태에 기반하여 HTTP 요청과 제휴되기 때문이다. 컨버세이션 범위 빈들로 작업할 때, 원격 호출 동안 정확한 컨버세이션 콘텍스트를 구축할 수 있어야 한다. 이 컨버세이션은 컨버세이션 토큰과 원격 요청을 보냄으로써 재활성화 된다. 이러한 인터랙션 상세는 여러분이 보낼 컨버세이션 토큰을 제공하는 한 Seam Remoting 프레임웍에 의해 핸들된다.

같은 창에서 만들어진 Ajax 요청이 서버 상의 두 개 이상의 컨버세이션과 통신할 수도 있다. 컴포넌트 인스턴트 스텁을 가져오기 전에 컨버세이션 ID를 지정할 경우, Seam은 이들을 구별할 수 있다. Seam.Remoting.getContext().setConversationId("#{conversation.id} ")를 사용하여 이를 수행한다.

Seam은 언제나 #{conversation.id} 값 바인딩 식에 현재 컨버세이션 ID를 노출한다. JavaScript는 식 보다는 숫자로 되어있는 최종 값을 본다. 여러분은 이 값을 원격 콘텍스트와 함께 등록하는데, 이는 뒤따르는 요청과 함께 전파된다. 반대로, 원격 호출이 이루어진 후에 Seam.Remoting.getContext().getConversationId()를 호출함으로써 이전 요청 동안 할당된 컨버세이션 ID를 읽을 수 있다. 이러한 방식으로, 원격 호출은 컨버세이션 범위의 혜택을 누릴 수 있고, Stateful 작동에 참여할 수 있다.




위로


Open 18 애플리케이션을 매핑하는 Google

Listing 1 ("Ten good reasons to use Seam")의 Reason Pot은 재미있었지만, 이제는 Seam Remoting 라이브러리가 몇 가지 실제 작업을 수행하도록 할 때이다. 여러분의 관심을 "Seamless JSF, Part 2: Seam의 컨버세이션(Conversation)" 에서 소개했던 Open 18 애플리케이션으로 돌려보자. Open 18은 골프 코스 디렉토리이다. 사용자는 코스 리스트를 검색하고 한 코스의 상세를 자세히 볼 수 있다. Open 18을 이용해서 사용자는 코스를 생성, 업데이트, 삭제할 수 있다. 원래의 Web 1.0에서, 사용자와 애플리케이션과의 모든 인터랙션은 페이지 재 로드로 이어진다.

Ajax로 Open 18 애플리케이션을 강화하는 다양한 방법들이 있고, 몇 가지 기회들을 설명하겠다. 첫 번째는 Open 18과 Google Maps 간 매시업을 만드는 것이다. 매핑 구현 없이는 인터넷에서 멀리 여행할 수 없고, 여러분의 사용자는 Open 18의 매핑 기능의 추가로 혜택을 볼 것이다. Seam Remoting API와 Google Maps Geocoder API를 결합하면 Google 지도 상의 코스 디렉토리에서 각 코스의 위치를 구상할 수 있다.

Google Maps API 사용하기
Google Maps API를 사용하기 위해서는, API 키에 등록해야 한다. 무료이다. Google은 API 사용에 대한 통계를 관리하고 서비스의 남용 상태를 요약하는 키만 필요로 한다. 여러분에게는 키를 요청하는 Google 계정이 필요한데, 서비스 조건에 따라서 이 키는 공유될 수 없다. 이러한 조건에 순응하기 위해 이 예제에서는 키를 위조했다. 여러분의 컴퓨터에서 샘플 애플리케이션을 실행하려면, GOOGLE_KEY 스트링을 여러분 각자가 갖고 있는 키로 대체해야 한다.

Google의 세계적 시각 빌려오기

Geo-spatial 매핑은 트릭이 필요한 것처럼 보이지만, Google Maps JavaScript API가 많은 일들을 수행한다. GMap2 클래스는 지도를 그리고 뷰포트(viewport)에서 이벤트의 스크롤 및 줌(zoom)에 대응한다. 또 다른 Google Maps 클래스, GClientGeocoder는 주소 스트링에 기반하여 geo-spatial 좌표를 분석한다. 여러분이 하는 일은 맵을 초기화 하고 각 코스용 마커를 추가하는 일이다.

마커를 지도 상에 배치하려면, 원격 메소드 호출을 통해 서버 측 컴포넌트에서 코스의 컬렉션을 가져와야 한다. 그런 다음, 각 코스의 주소를 GClientGeocoder를 사용하여 geo-spatial 포인트(경도와 위도)로 바꾼다. 마지막으로, 이 포인트를 사용하여 상응하는 좌표에 있는 지도 위에 마커를 배치한다. 추가 기능으로, 디렉토리 리스팅에 각 행에 나침반 아이콘을 입힌다. 이는 편집 아이콘 옆에 배치된다. 디렉토리 행들 중 하나에서 나침반 아이콘이 클릭되면, 지도는 선택된 코스가 뷰로 들어올 때까지 이리저리 움직이다가 줌을 수행한다. 동시에, 지도는 해당 코스의 이름, 주소, 전화 번호, 웹 사이트를 디스플레이 하는 마커 위에 풍선도 렌더링 한다. 같은 풍선이 마커들 중 하나를 클릭하면 지도 상에 직접 나타날 수 있도록 한다. 그림 2는 이것이 수행될 때 애플리케이션의 모습이다.


그림 2. Google Maps 매시업의 스크린샷
Google Maps 매시업의 스크린샷

지도 컴포넌트와 위치 지향 데이터의 통합은 흥미롭지만, 이 매시업은 특별히 깊은 뜻이 있다. 사용자들은 실제로 지도 상의 각 코스의 경계들을 볼 수 있다. Map 모드에서, 골프 코스 프로퍼티는 밝은 녹색으로 렌더링 된다. 줌을 지나치게 확대하면, 레이블이 코스 영역에 나타나면서 코스의 이름을 드러낸다. 더 재미있는 것은, Satellite 모드인데, 이는 코스 조망까지 나타낸다. 충분한 줌을 하면, 티 박스, 페어웨이, 그린 같은 각 홀의 특징도 드러낸다. 골프 코스 위치와 인터랙티브 지도 뷰의 매시업은 여러분의 노력에 대한 확실한 보상이라고 할 수 있다.




위로


Google Maps로 짜기

Google Maps는 웹 애플리케이션으로 통합 및 삽입하기가 매우 쉽다. 이미 언급했지만, 지도를 렌더링하는 모든 상세들을 관리하고, 그 지도 상의 geo-spatial 위치들을 정하는 API를 제공한다. GClientGeocoder 객체는 geo-spatial 위치를 분석하는 작업을 수행하고, 여러분이 필요로 하는 위도와 경도를 제공한다.

주소를 geo-spatial 포인트로 변환하는 메소드 스텁은 Seam Remoting 메소드 스텁들과 비슷하게 작동한다. 콜백 함수는 리턴 값을 캡쳐하도록 호출될 때 메소드로 전달된다. 그 때, Ajax 요청은 Google HQ로 보내지고 콜백 함수는 응답이 브라우저로 돌아올 때 실행된다. Google Maps API의 지능적인 인터페이스는 우편 주소에 기반하여 매우 자유롭게 위치를 정한다. 여러분은 각 코스에 대해 geo-spatial 좌표를 관리할 필요가 없다. 이 API에 대한 추가 루틴들은 위도와 경도 데이터를 사용하여 지도 상에 이러한 위치에 대한 마커들을 구현 및 렌더링 한다.

커스터마이징!
Google 지도의 디스플레이을 설정하는 수 많은 옵션들이 있다. 이 맵을 설정하는 것이 이 글의 주 초점이지만, 여러분 스스로 시도해보기 바란다. 참고자료 섹션을 참조하면 큰 도움이 될 것이다. 또한, 로직을 캡슐화 하기 위해 객체 지향 구조를 사용하기 보다는 탑 레벨 JavaScript 함수를 사용하게 될 것이다. 코드를 자유롭게 커스터마이징 할 수 있다.

두 개의 API 메소드는 지도 통합 Geocoder.getLatLng(), GMap.addOverlay()와 관련이 있다. 우선 Geocoder.getLatLng() 메소드는 주소 스트링을 GLatLng 포인트로 바꾼다. 이 데이터 객체는 위도와 경도 값 쌍을 래핑한다. 이 메소드에 대한 두 번째 인자는 Google HQ와의 통신이 완료될 때 실행하는 콜백 JavaScript 함수이다. 이 함수는 GMap.addOverlay()를 사용하여 지도에 마커 오버레이를 추가한다. 기본적으로, 이 마커는 빨간색 핀으로 렌더링 된다. 이 핀의 끝은 지도 상의 주소 위치를 가리킨다.

Listing 5는 Google 지도를 설정하고 마커를 여기에 추가하는 JavaScript 코드를 보여준다. 이 함수는 실행 순서대로 리스팅 된다. Listing 3에 사용되었던 Seam Remoting 스크립트와 Google Maps API 스크립트의 새로운 반입을 인식해야 한다.


Listing 5. Open 18과 Geocoder 매핑하기
                <script type="text/javascript"

    src="http://maps.google.com/maps?file=api&amp;v=2.x&amp;key=GOOGLE_KEY"></script>

<script type="text/javascript"

    src="seam/resource/remoting/resource/remote.js"></script>

<script type="text/javascript"

    src="seam/resource/remoting/interface.js?courseAction"></script>

<script type="text/javascript">

// <![CDATA[

var gmap = null;

var geocoder = null;

var markers = {};

var mapIsInitialized = false;



GEvent.addDomListener(window, 'load', initializeMap);



/**

 * Create a new GMap2 Google map and add markers (pins) for each of the

 * courses.

 */

function initializeMap() {

    if (!GBrowserIsCompatible()) return;

    gmap = new GMap2(document.getElementById('map'));

    gmap.addControl(new GLargeMapControl());

    gmap.addControl(new GMapTypeControl());

    // center on the U.S. (Lebanon, Kansas)

    gmap.setCenter(new GLatLng(38.2, -95), 4);

    geocoder = new GClientGeocoder();

    GEvent.addDomListener(window, 'unload', GUnload);

    addCourseMarkers();

}



/**

 * Retrieve the collection of courses from the server and add corresponding

 * markers to the map.

 */

function addCourseMarkers() {

    function onResult(courses) {

        for (var i = 0, len = courses.length; i < len; i++) {

            addCourseMarker(courses[i]);

        }



        mapIsInitialized = true;

    }



    Seam.Remoting.getContext().setConversationId("#{conversation.id}");

    Seam.Component.getInstance("courseAction").getCourses(onResult);

}



/**

 * Resolve the coordinates of the course to a GLatLng point and adds a marker

 * at that location.

 */

function addCourseMarker(course) {

    var address = course.getAddress();

    var addressAsString = [

        address.getStreet(),

        address.getCity(),

        address.getState(),

        address.getPostalCode()

    ].join(" ");

    geocoder.getLatLng(addressAsString, function(latlng) {

        createAndPlaceMarker(course, latlng);

    });

}



/**

 * Instantiate a new GMarker, add it to the map as an overlay, and register

 * events.

 */

function createAndPlaceMarker(course, latlng) {

    // skip adding marker if no address is found

    if (!latlng) return;

    var marker = new GMarker(latlng);

    // hide the course directly on the marker

    marker.courseBean = course;

    markers[course.getId()] = marker;

    gmap.addOverlay(marker);



    function showDetailBalloon() {

        showCourseInfoBalloon(this);

    }



    GEvent.addListener(marker, 'click', showDetailBalloon);

}



/**

 * Display the details of the course in a balloon caption for the specified

 * marker.  You should definitely escape the data to prevent XSS!

 */

function showCourseInfoBalloon(marker) {

    var course = marker.courseBean;

    var address = course.getAddress();

    var content = '<strong>' + course.getName() + '</strong>';

    content += '<br />';

    content += address.getStreet();

    content += '<br />';

    content += address.getCity() + ', ' +

        address.getState() + ' ' +

        address.getPostalCode();

    content += '<br />';

    content += course.getPhoneNumber();

    if (course.getUri() != null) {

    content += '<br />';

    content += '<a href="' + course.getUri() + '" target="_blank">' +

        course.getUri().replace('http://', '') + '</a></div>';

    }



    marker.openInfoWindowHtml(content);

}



// ]]>

</script>


Listing 5는 언뜻 보기에는 복잡하지만, 그렇게 어려운 것은 아니다. 페이지가 로딩되면, Google 지도는 GMap2 생성자를 사용하여 인스턴스화 되고, 목표 DOM 엘리먼트로 삽입되고, 미국을 중심으로 모인다. Et Voila! -- 디스플레이가 완성되었다. 여러분이 생각했던 것 보다 쉽다. 지도가 초기화 되면, 코스 마커가 추가된다. 이 코드의 난점은 그러한 마커들을 만드는 것이기 때문에, addCourseMarkers() 함수에 집중해야 한다. 여기에서 Seam Remoting API가 사용된다.

코스 가리키기

서버 리소스에 저장하기 위해, 원격 호출을 통해 페이지가 JSF에 의해 렌더링 되는 동안 컨버세이션 범위로 로딩되는 같은 코스 리스트를 검색해야 한다. 이러한 컬렉션을 보유하고 있는 컨버세이션이 원격 호출 동안 재 활성화 되도록 하려면, 컨버세이션 ID가 앞서 언급했던 것처럼 원격 콘텍스트에 구축되어야 한다. 검토하려면, 현재 컨버세이션 ID, #{conversation.id}를 참조하는 값 바인딩 식이 페이지가 렌더링 할 때 해석되어야 하며, 이것의 값은 setConversationId() 메소드를 통해 Remoting Context로 전달되어야 한다. 컴포넌트 스텁을 통한 원격 메소드로의 후속 호출은 값을 전달하고 제휴된 컨버세이션을 활성화 할 것이다. 컨버세이션을 활성화 시키면, 같은 코스 리스트를 사용할 수 있다.

액티비티 인디케이터
예제를 실행할 때, Seam은 "Loading..." 이라는 메시지를 페이지의 우측 상단 코너에 두고, 호출은 연결 중이다. 이 메시지는 백그라운드에서 액티비티가 발생하고 있다는 것을 사용자에게 알려주는 좋은 방식이다. 이 메시지를 보여주고 싶지 않거나 이를 커스터마이징 하고 싶다면, Seam.Remoting JavaScript 객체에 이를 수행하는 메소드를 사용해도 된다.

코스를 아래로 잡아당기려면, Seam.Remoting.getInstance()를 사용하여 courseAction이라고 하는 컴포넌트에 대한 레퍼런스를 찾고, 그 스텁에 getCourses() 메소드를 실행하는 것이다. 앞서 설명했던 것처럼, 이 스텁은 마지막 인자로서 콜백 함수를 취하는데, 이는 응답에 코스의 리스트를 캡쳐하는데 사용된다. 리턴 값은 더 이상 프리머티브 유형이 아닌 사용자 정의 JavaBeans의 컬렉션이다. Seam은 JavaScript 구조를 만들어서 서버 측 컴포넌트의 메소드 서명에 사용되는 JavaBean 클래스를 에뮬레이트 한다. 이 경우, Seam은 같은 getter와 setter를 사용하여 Course 엔터티를 나타내는 JavaScript 객체들에 대한 응답을 정렬 해제한다. 코스 객체에 대한 getter 메소드는 Listing 5의 다양한 위치에 사용되어 코스 데이터를 읽는다.

코스를 지도에 배치하는 마지막 단계에서, 각 코스용 주소는 GClientGeocoder를 사용하여 GLatLng 포인트로 변환된다. 이 값은 지도 상의 오버레이로서 추가되는 GMarker 위젯을 만드는데 사용된다.

나침반 없는 지도

이제, 지도가 이 모든 마커들로 꾸며졌지만, 코스 디렉토리의 행들을 지도 상의 마커와 제휴시킬 방법이 없다. 바로 이곳에서 나침반 아이콘이 역할을 한다. Google Maps API를 사용하는 JavaScript를 추가하여 코스에 대한 나침반 아이콘이 클릭될 때 그 코스상의 지도를 줌 또는 중앙으로 보낸다. Listing 6은 나침반 아이콘의 컴포넌트 태그를 보여준다. 이 컴포넌트는 편집 아이콘 바로 전에 이 디렉토리의 각 행에 삽입된다. (코스를 편집하는 기능은 "Seamless JSF, Part 2: Seam의 컨버세이션(Conversation)"에 설명되어 있다.)


Listing 6. 나침반 아이콘을 렌더링 하는 컴포넌트 태그
                <h:graphicImage value="/images/compass.png" alt="[ Zoom ]" title="Zoom to course on map"

  onclick="focusMarker(#{_course.id}, true);" />


JavaScript 이벤트 핸들러, focusMarker()는 나침반 아이콘 상에서 onclick 이벤트를 핸들하도록 등록된다. Listing 7의 focusMarker() 메소드는 글로벌 레지스트리에서 그 코스에 이전에 등록된 GMarker를 찾고, 작업을 Listing 5showCourseInfoBalloon() 함수에 위임한다.


Listing 7. 선택된 코스에 포커스를 맞추는 focusMarker() 이벤트 핸들러
                /**

 * Bring the marker for the given course into view and display the

 * details in a balloon.  This method is registered in an onclick

 * handler on the compass icons in each row in the course directory.

 */

function focusMarker(courseId, zoom) {

    if (!GBrowserIsCompatible()) return;

    if (!mapIsInitialized) {

        alert("The map is still being initialized. Please wait a moment and try again.");

        return;

    }

    var marker = markers[courseId];

    if (!marker) {

        alert("There is no corresponding marker for the course selected.");

        return;

    }



    showCourseInfoBalloon(marker);

    if (zoom) {

        gmap.setZoom(13);

    }

}


코스 컬렉션 복원하기

Listing 8은 이전에 가져온 코스를 노출하는 서버 측 컴포넌트 상의 메소드를 나타내고 있다. ("Seamless JSF, Part 2: Seam의 컨버세이션(Conversation)"에서 Open 18을 위해 생성된 CRUD 액션 핸들러는 여기에서 배제하기로 한다.)


Listing 8. CourseAction 컴포넌트가 getCourses 메소드를 노출한다.
                

@Name("courseAction")

@Scope(ScopeType.CONVERSATION)

public class CourseAction implements Serializable {

    /**

     * During a remote call, the FacesContext is <code>null</code>.

     * Therefore, you cannot resolve this Spring bean using the

     * delegating variable resolver. Hence, the required flag tells

     * Seam not to complain.

     */

    @In(value="#{courseManager}", required=false)

    private GenericManager<Course, Long> courseManager;



    @DataModel

    private List<Course> courses;



    @DataModelSelection

    @In(value="course", required=false)

    @Out(value="course", required=false)

    private Course selectedCourse;



    @WebRemote

    public List<Course> getCourses() {

        return courses;

    }



    @Begin(join=true)

    @Factory("courses")

    public void findCourses() {

        System.out.println("Retrieving courses...");

        courses = courseManager.getAll();

    }



    // .. additional CRUD action handlers ..

}

앞서 설명했던 것처럼, 메소드 스텁은 마지막 인자로서 선택적인 콜백 JavaScript 함수를 포함시킬 수 있다. 바로 이것이 클라이언트 측 스텁에서의 getCourses() 객체가 단일 인자를 취하는 이유이자, 서버 측 컴포넌트의 상응하는 메소드가 어떤 것도 취하지 않는 이유이다. getCourses() 메소드는 페이지가 렌더링 될 때 나타나는 코스의 컬렉션을 리턴한다.




위로


FacesContext 없는 삶

지금까지, 이 모든 Seam Remoting 요청들이 JSF 라이프 사이클에서 어떻게 작동하는지 궁금했을 것이다. 사실을 말하자면, 어떤 것도 하지 않는다. 적어도 일반적인 방식으로는 수행하지 않는다. JSF 라이프 사이클을 깨우지 않는 것은 요청을 가볍게 유지하는 것이다. 메소드가 JavaScript에서 컴포넌트 스텁에서 호출될 때, 이 호출은 Seam Resource Servlet를 통해 전달되고 Remoting 위임 객체에 의해 핸들된다. Resource Servlet은 JSF 라이프 사이클을 통해 요청을 취하지 않기 때문에, FacesContext는 원격 호출 동안 사용할 수 없다. 대신, Resource Servlet은 Seam 컴포넌트 컨테이너에만 해당되는 고유의 라이프 사이클을 사용한다. Seam에 의해 관리되는 객체로 바꾸지 않은 값-바인딩 식은 Seam Remoting API를 통해 메소드가 실행될 때 null이 된다. 게다가, Backing bean에 의해 사용되는 JSF 컴포넌트 바인딩도 사용할 수 없게 된다.

Open 18 애플리케이션의 초기 설정에서, CourseAction 빈은 Spring 프레임웍의 JSF 변수 리졸버에 의존하여 CourseManager 객체를 값 바인딩 식 #{courseManager}을 통해 Backing bean으로 투입했다. 문제는, Spring 변수 리졸버가 FacesContext에 의존하여 Spring 컨테이너를 배치한다. FacesContext를 사용할 수 없다면, 변수 리졸버는 Spring 빈의 어떤 것에도 액세스 할 수 없다. 따라서, 값-바인딩 식, #{courseManager}은 컴포넌트가 Seam Remoting API를 통해 액세스 될 때 null이 될 것이다.

이러한 상황은 원격 메소드 호출 동안 어떤 것에 대해 CourseManager 객체를 사용해야 할 경우 많은 어려움이 있다. Seam의 기본 작동은 의존성을 유지하는 것이기 때문에, @In 주석에 required=false 애트리뷰트를 표시해야 하는데, 이는 선택적인 것이다. 이를 수행하면 호출이 원격 스텁을 통해 만들어 질 때 문제가 없다.

다음 섹션에서는 Ajax4jsf를 사용하여 애플리케이션의 코스 디렉토리 페이지와 코스 상세 페이지를 합병하는 방법을 보여준다. 서비스 레이어 객체가 코스에 대한 변경 사항을 유지하도록 하는 것이 필요하다. 하지만, Ajax4jsf는 액션 핸들러를 호출할 때 완전한 라이프 사이클을 사용하기 때문에, #{courseManager} 식은 일반적인 방식으로 식을 푼다.




위로


Ajax4jsf 사용하기

Google Maps 매시업은 매우 흥미롭지만, 이것을 Open 18 애플리케이션에 추가하는 것에도 약간의 문제가 있다. 사용자가 디렉토리의 코스 이름을 클릭할 때, 코스에 대한 상세 정보를 보여주기 위해 페이지가 리프레쉬 된다. 결국, 지도는 자주 재 로딩된다. 이러한 재 로드는 브라우저와 서버에 너무나 많은 오버헤드를 추가하고, 지도를 초기 위치로 복원한다.

Ajax4jsf 프로젝트
Ajax4jsf 프로젝트는 처음에는 Exadel에 의해 시작되었고, Java.net에서 호스팅 되었다. 최근, JBoss는 Exadel 제품의 인수 과정에서 Ajax4jsf 라이브러리를 통합했다. Ajax4jsf가 JSF와 결합될 때, 이벤트 중심 인터페이스의 비전은 진정으로 실현된다. 이벤트는 페이지 재 로딩이라는 비용 없이 서버에서 액션을 실행할 수 있지만, UI는 페이지 구조에서 변경 사항들을 반영하기 위해 업데이트 된다. Ajax4jsf는 Seam의 목표에 잘 부합하고 JSF에 구현된 "리치" 웹 플랫폼을 제공한다.

지도의 재 로딩을 방지하려면, 모든 액티비티를 하나의 페이지 로드로 제한해야 한다. 안타깝게도, 폼을 제출하는 프로세스는 페이지 요청으로 연결된다. 이러한 강결합은 웹 브라우저에 있어서 기본 작동이다. 여러분이 해야 할 일은 브라우저가 페이지를 다시 요청하지 않고 데이터가 서버로 보내지도록 하는 것이다. 이는 Ajax4jsf 컴포넌트 라이브러리가 해결해야 할 진정한 문제이다. 디렉토리 리스팅의 Name 컬럼에서 h:commandLink 태그를 a4j:commandLink 태그로 바꾸면 Ajax4jsf는 코스 링크가 클릭될 때마다 마법을 만든다. Ajax4jsf 버전의 링크가 활성화 될 때, 액션 애트리뷰트, #{courseAction.selectCourseNoNav}의 메소드-바인딩 식에서 가리키는 메소드를 실행하고, 디렉토리 밑에 삽입될 코스 상세들을 포함하는 대체 마크업을 리턴한다.

selectCourseNoNav() 메소드는 selectCourse() 메소드와 같은 로직을 수행하지만, JSF가 네비게이션 이벤트를 쫓아가지 않도록 하는 어떤 리턴 값도 없다. 결국, Ajax 요청의 포인트는 브라우저가 페이지를 다시 요청하지 못하도록 하는 것이다. 리턴된 XHTML 마크업은 지도의 상태를 망치지 않고 지도 아래에 있는 영역으로 삽입된다. Listing 9의 컴포넌트는 원래 버전의 courses.jspx에 사용된 h:commandLink를 대체한다. ("Seamless JSF, Part 2: Seam의 컨버세이션(Conversation)" 참조)


Listing 9. 코스 선택을 위한 Ajax4jsf 관리 링크
                

<a4j:commandLink id="selectCourse"

  action="#{courseAction.selectCourseNoNav}" value="#{_course.name}" />


앞서 언급했던 것처럼, 페이지의 어떤 부분들이 점증적으로 업데이트 되는지를 나타내는 두 가지 방법이 있다. 한 가지 방법은 Ajax4jsf 액션 컴포넌트의 reRender 애트리뷰트에 있는 컴포넌트 ID를 지정함으로써 개별 컴포넌트를 목표로 정하는 것이다. 다른 방법은 a4j:outputPanel 태그에 있는 페이지의 영역을 래핑하고 그러한 영역을 그 태그의 ajaxRendered 애트리뷰트를 사용하여 "Ajax rendered"로서 표시하는 것이다. Listing 10은 아웃풋 패널 방식을 사용하여 선택된 코스의 상세를 나타내는 뷰 템플릿 영역 모습이다.


Listing 10. Ajax 실행 코스 상세 패널
                

<a4j:outputPanel id="detailPanel" ajaxRendered="true">

  <h:panelGroup id="detail" rendered="#{course.id gt 0}">

  <h3>Course Detail</h3>

  <!-- table excluded for brevity -->

  </h:panelGroup>

</a4j:outputPanel>


Ajax4jsf 설정하기

Ajax4jsf의 커스텀 뷰 핸들러
JSF 스팩의 뷰-핸들러 위임 메커니즘은 매우 제한적이고, 핸들러 체인을 잘 다루지 못한다. Ajax4jsf는 커스텀 뷰 핸들러를 사용하여 실제 핸들러를 꾸민다.

업데이트 된 코드를 실행하기 전에, 애플리케이션에 Ajax4jsf를 설정해야 한다. 먼저, jboss-ajax4jsf.jar 라이브러리를 추가하고, 이것의 종속물 oscache.jar를 애플리케이션의 클래스 경로에 추가한다. 두 번째, Ajax4jsf 필터를 web.xml에 추가한다. 이 필터의 정의는 Listing 11에 나타나 있다. Ajax4jsf 필터는 web.xml 파일에서 정의된 최초의 파일이어야 한다. Seam의 Resource Servlet과 마찬가지로, 이 필터는 Ajax4jsf 컴포넌트에서 시작된 원격 호출을 다룬다. Seam의 원격 호출과는 달리, JSF 라이프 사이클 비동기식 Ajax4jsf 요청 동안 활성화 되므로, 정식 서블릿 보다는 서블릿 필터가 필요하다. 또한, faces-config.xml 파일에서 web.xml로 Facelets 뷰 핸들러 설정을 옮겨야 하는데, 서블릿 콘텍스트 매개변수에 이것이 정의된다.


Listing 11. Ajax4jsf 설정하기
                <context-param>

    <param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>

    <param-value>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</param-value>

</context-param>



<filter>

    <filter-name>Ajax4jsf Filter</filter-name>

    <filter-class>org.ajax4jsf.Filter</filter-class>

</filter>



<filter-mapping>

    <filter-name>Ajax4jsf Filter</filter-name>

    <url-pattern>*.action</url-pattern>

</filter-mapping>


라이브러리를 포함시키고 web.xml 파일에 Listing 11의 마크업을 설치했다면, Ajax4jsf를 사용할 준비가 된 것이다. Ajax4jsf 태그 라이브러리 정의, xmlns:a4j="https://ajax4jsf.dev.java.net/ajax"를 Ajax4jsf 태그를 사용할 뷰 템플릿에 추가한다. 물론, 이 글에서 필자가 설명한 것 보다 이 라이브러리를 사용하여 더 많은 일을 할 수 있다. Ajax4jsf 사용에 대해서는 참고자료 섹션을 참조하라.




위로


Holler back!

Open 18-Google Maps 매시업은 전적으로 읽기 전용이다. Ajax에서 가장 흥미로운 부분은 페이지를 재 로딩 하지 않고 백엔드 데이터를 변경할 수 있다는 점이다. 이제는 Google 지도를 사용하여 데이터를 보내는 것을 연습할 차례이다. Google이 우편 주소를 위도와 경도 좌표들로 바꿀 수 있지만, 사용자는 여전히 커스텀 위치를 지정하고 싶을 것이다. 정확히 말해서, 사용자는 클럽하우스나 해당 코스의 첫 번째 티(tee) 장소를 가리키는 핀이 필요할 것이다. 다행히도, Google Map API는 Open 18 애플리케이션을 향상시켰다.

마커를 끌어오는 것으로 시작한다. 이 기능을 실행하려면, 드래그 가능한 플래그를 GMarker에 추가한다. 그런 다음, 사용자가 마커를 드래그 하는 것을 끝낼 때 실행되는 "dragend" 이벤트를 리스닝 하는 JavaScript 함수를 등록한다. 콜백 함수 내에서, courseAction 스텁, setCoursePoint()에 새로운 메소드를 실행하여 새로운 포인트를 저장한다.

Listing 12는 수정된 createAndPlaceMarker 함수이다. addCourseMarker() 함수 역시 수정되어 마커를 배치할 때 Course 엔터티에 대한 커스텀 포인트를 고려한다.


Listing 12. 마커 드래그가 가능하도록 업데이트 된 JavaScript
                function addCourseMarker(course) {

    var address = course.getAddress();

    if (course.getPoint() != null) {

        var point = course.getPoint();

        var latlng = new GLatLng(point.getLatitude(), point.getLongitude());

        createAndPlaceMarker(course, latlng);

    }

    else {

        var addressAsString = [

            address.getStreet(),

            address.getCity(),

            address.getState(),

            address.getPostalCode()

        ].join(" ");

        geocoder.getLatLng(addressAsString, function(latlng) {

            createAndPlaceMarker(course, latlng);

        });

    }

}



function createAndPlaceMarker(course, latlng) {

    // skip adding marker if no address is found

    if (!latlng) return;

    var marker = new GMarker(latlng, { draggable: true });

    // hide the course directly on the marker

    marker.courseBean = course;

    markers[course.getId()] = marker;

    gmap.addOverlay(marker);



    function showDetailBalloon() {

        showCourseInfoBalloon(this);

    }



    function assignPoint() {

        var point = Seam.Remoting.createType("com.ibm.dw.open18.Point");

        point.setLatitude(this.getPoint().lat());

        point.setLongitude(this.getPoint().lng());

		var courseActionStub = Seam.Component.getInstance("courseAction");

		courseActionStub.setCoursePoint(this.courseBean.getId(), point);

    }



    GEvent.addListener(marker, 'click', showDetailBalloon);

    GEvent.addListener(marker, 'dragstart', closeInfoBalloon);

    GEvent.addListener(marker, 'dragend', assignPoint);

}


아직 끝나지 않았지만, 거의 끝나간다. GLatLng 포인트를 저장하는 서버 측 컴포넌트에 메소드를 추가해야 한다. 이 매시업의 마지막을 끝내기 전에 Open 18 애플리케이션의 Spring 통합을 다시 다루어야 한다. ("Seamless JSF, Part 2: Seam의 컨버세이션(Conversation)" 참조).




위로


Spring 통합

이전에 언급했던 것처럼, Spring 컨테이너를 Seam으로 통합하는데 사용했었던 변수-리졸버 방식은 한계를 갖고 있다. 솔직히 말해서, 이제는 작별을 고해야 할 때이다. 대부분의 Seam 기능에는 JSF가 포함되어 있지만, Seam의 어떤 부분들은 JSF 라이프 사이클 밖에서 작동한다. Spring과의 진정한 통합을 이룩하려면, 커스텀 변수 리졸버 보다 더 나은 솔루션이 필요하다. 다행히도, Seam의 개발자들은 Spring용 Seam 확장을 추가함으로써, 이러한 필요성을 다루고 있다. Spring 통합 패키지는 스키마 기반 확장 포인트를 만드는데 Spring 2.0의 새로운 기능을 활용하고 있다. 이러한 확장은 빈 정의 파일에서 커스텀 XML 네임스페이스를 사용하고, Spring 컨테이너 시작 프로세스 동안 그러한 태그들에 대해 연산을 수행하는 네임스페이스 핸들러를 실행한다.

Spring용 Seam 네임스페이스 핸들러는 Spring 컨테이너와 통합하는 여러 방식들을 갖고 있다. 커스텀 변수 리졸버를 제거하기 위해, Spring 빈을 Seam 컴포넌트로 노출해야 한다. seam:component 태그는 이 목적에 사용된다. 이 태그를 Spring 빈 선언 안에 배치하면 Seam에게 Spring 빈이 Seam 컴포넌트로서 저장(proxy) 또는 래핑되어야 한다는 것을 알려준다. 이 부분에서, Seam bijection 메커니즘은 빈에 @Name 주석이 달린 것처럼 취급한다. 지금으로서는, Seam과 Spring간 차이를 메우는 FacesContext가 필요 없다.

Seam-Spring 통합을 설정하기는 매우 쉽다. 첫 번째 단계에는 어떤 코드 수정도 필요 없다. 여러분이 해야 할 일은 Spring 2.0과 Seam 1.2.0 또는 후속 릴리스를 사용하기만 하면 된다. (Seam은 본 시리즈의 첫 번째 기술자료가 발표된 이후 급속도로 변했기 때문에, 업그레이드가 필요하다.) IOC 통합은 개별 JAR, jboss-seam-ioc.jar로 압축되기 때문에, 이미 언급된 JAR 외에도 애플리케이션의 클래스 경로에 이를 추가시켜야 한다.

두 번째 단계는 Seam 설정이 포함되지 않는다. 이번에는, Spring 설정을 보아야 한다. Seam XML 스키마 선언을 Spring 설정 XML에 추가한다. 이 곳에서 빈들이 정의된다. 이 파일의 표준 이름은 applicationContext.xml이다. 비록, 이 예제 애플리케이션은 더 적합한 이름인 spring-beans.xml을 사용한다. 그런 다음, JSF로 노출해야 할 Spring 빈의 정의에 seam:component 태그를 추가한다. Open 18 애플리케이션에서, 이 태그를 courseManager 빈 정의 안에 중첩시킨다. 전체 빈 리스팅을 보려면 예제 애플리케이션spring-beans.xml 파일을 참조하라.

Spring 통합을 완성했다면, courseManager 프로퍼티에 @In 주석에 값 바인딩을 지정할 필요가 없고, required=false 애트리뷰트도 필요 없다. 이 곳에서, create=true 애트리뷰트를 사용하여 Seam이 Spring 컨테이너에서 빈을 보낼 곳을 알 수 있도록 하고, Seam 컴포넌트로서 이를 장식한다. Listing 13의 코드는 이 코스의 geo-spatial 포인트를 업데이트하는데 필요한 CourseAction 클래스의 관련 부분이다.


Listing 13. CourseAction의 새로운 setPoint 메소드
                @WebRemote

public void setCoursePoint(Long id, Point point) {

    System.out.println("Saving new point: " + point + " for course id: " + id);

    Course course = courseManager.get(id);

    course.setPoint(point);

    courseManager.save(course);

}


이제 여러분이 Joe Cool인 것처럼 행동하라. 이제 여러분의 애플리케이션은 공식적으로 "Ajax화"되었다!




위로


결론

이 글에서는 단순한 CRUD 구현을 Ajax 스타일 페이지 렌더링과 Google Maps를 통합한 고급 매시업으로 확장하는 Open 18 애플리케이션을 설명했다. Seam Remoting API와 Ajax4jsf를 어떻게 사용하는지, Seam과 Spring 프레임웍을 통합하는 방법에 대해서도 설명했다.

Seam Remoting과 Ajax4jsf는 Ajax 통신을 포함시키기 위해 추상화를 확장하여 JSF의 컴포넌트 모델을 보완한다. 이 글에 제시된 코드도 XMLHttpRequest JavaScript 객체와 직접 인터랙팅 해야 할 부분은 없다. 대신, 모든 작업은 고급의 API 뒤로 숨겨지고, 여러분은 서버 측 컴포넌트들을 직접 쿼리 및 조작하거나 주어진 페이지의 액션과 업데이트 영역을 호출하면 된다. 또한, Ajax의 덕택에, 모든 작업이 비동기식으로 발생한다.

본 시리즈의 세 편의 글을 통해, JSF와 Seam에 대해 많은 것들을 배웠지만, Seam의 모든 것을 아직 드러내지 못했다. Seamless JSF 시리즈에서 논의된 것과 예제들은 JSF 프로젝트에 Seam이 어떤 도움이 될 수 있는지를 배우는 출발점에 불과하다. Seam을 알아갈수록 더욱 사랑하게 될 것이다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
Open 18 sample application - Phase 21j-seam3.zip277KHTTP
다운로드 방식에 대한 정보

Note

  1. 샘플 애플리케이션은 Maven 2 프로젝트로서 구성된다. 빌드가 실행될 때 모든 종속물들은 온 디맨드 방식으로 제공된다. 본 애플리케이션은 Appfuse 프로젝트에서 여러 라이브러리들을 사용하여 서비스와 DAO 레이어들을 구현한다.


참고자료

교육

제품 및 기술 얻기

토론


필자소개

Dan Allen

Dan Allen은 CodeRyte, Inc.의 자바 엔지니어이다. 열정적인 오픈 소스 옹호가이다. 코넬대학교를 졸업한 후에, 리눅스와 오픈 소스 소프트웨어의 세계에 매료되었다. 그 이후, 웹 애플리케이션에 열중하고 있으며, 지난 몇 년 동안, Spring, Hibernate, Maven 2, JSF 같은 자바 기술에 주력했다. (http://www.mojavelinux.com)




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



아니오잘 모르겠음
 


 


12345
 



위로


Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Linux is a trademark of Linus Torvalds in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의