GXT RIA App 만들기2011. 3. 1. 23:08
오늘은 GWT에서 Service라는 개념에 대해 알아보고 실습해보자. GWT에서는 서버와 통신(RPC)하는 하나의 행위를 서비스라는 이름으로 불리운다. 지금까지 배웠던 부분들은 모두 Client단으로 최종 javascript로 변환되어 사용자에게 UI로 보여지는 것이다. 그러나 화면만으로는 프로그램이 데이터를 가질 수 없으므로 서버단과 통신하는 무엇인가가 필요한데 이것을 Service라 지칭한다.
서비스는 아래와 같이 2개의 인터페이스와 1개의 클래스가 한 쌍이된다.


1. com.gxt.riaapp.client.RiaAppPgmService 
   - 패키지를 보면 client아래 존재한다. 이 인터페이스에는 서비스에서 사용할 메소드를 명시하게 된다.
   - 이클립스에서는 최초 이 인터페이스를 생성하면 자동으로 나머지 인터페이스와 클래스를 생성해준다.
   - 우리가 메소드를 추가하고 싶다면 이 인터페이스에 메소드를 추가하게 되는데 추가할 경우 자동으로 xxxAsync, Impl 두개의 파일에 해당 메소드가 기본 구현되어지게 된다.

2. com.gxt.riaapp.client.RiaAppPgmServiceAsync
   - 이 인터페이스는 1번 인터페이스를 생성하면 자동으로 생성되게 된다. 이 인터페이스는 이클리스가 알아서 제어 해주므로 사실 개발자는 손댈 필요가 없다. 이 인터페이스는 서버와 통신을 주고 받는 역할을 한다고 보면되겠다.

3. com.gxt.riaapp.server.RiaAppPgmServiceImpl
  - 패키지에 주목하자. server아래 존재한다. 이 클래스는 Client와는 반대로 Server에서 실행 할 메소드들이 구현되어야 한다. 즉 위의 1,2번에 명시된 메소드가 최종 구현되는 곳이다. 이 클래스에서 SQL을 날리고 데이터를 불러와 리턴하면 Async인터페이스를 통해 xxxService가 클라이언트에게 전달한다고 생각하면 되겠다.

▶ 서비스를 생성해 보자. com.gxt.riaapp.client를 선택하자 여기서 마우스 우측클릭을 하고 아래와 같이 선택하자.


▶ RiaAppPgmService라는 이름으로 생성해보자.


▶ 위에서 설명했듣 client에 2개의 인터페이스 server에 한개의 클래스가 생성되었을 것이다. 여기서 한가지 주목할 부분이 web.xml파일이다. war/WEB-INF/web.xml파일을 열어보자. 아래와 같이 web.xml파일이 변경되었을 것이다. 이는 서버스가 Servlet기반이기 때문에 하나의 서비스가 추가되었을 경우 해당 서비스 이름으로 서블릿 설정이 web.xml파일에 추가되게 된다. 이 또한 자동으로 생성되게 되므로 손댈 필요는 없겠다


  
  
  
  
  
    RiaApp.html
  
  
  	RiaAppPgmService
  	com.gxt.riaapp.server.RiaAppPgmServiceImpl
  
  
  	RiaAppPgmService
  	/riaapp/RiaAppPgmService
  



▶ 이제 우리는 서비스에 메소드를 추가하고 그 메소드를 구현 해보 도록 하자. 구현이 끝나면 클라이언트에서 해당
 메소드를 호출하면 서버는 클라이언트에게 정보를 전달하는 지 확인해볼 수 있을 것이다.
▶ RiaAppPgmService.java를 열어 메소드를 선언해보자
package com.gxt.riaapp.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("RiaAppPgmService")
public interface RiaAppPgmService extends RemoteService {
	public int getSessionData() throws Throwable ; // 서버에서 세션을 전달받아 리턴하자.
	/**
	 * Utility class for simplifying access to the instance of async service.
	 */
	public static class Util {
		private static RiaAppPgmServiceAsync instance;
		public static RiaAppPgmServiceAsync getInstance(){
			if (instance == null) {
				instance = GWT.create(RiaAppPgmService.class);
			}
			return instance;
		}
	}
}

▶ 이제 Async 인터페이스와 Impl클래스를 열어보라 위에서 선언한 메소드가 선언 또는 구현되어 있을 것이다.
package com.gxt.riaapp.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface RiaAppPgmServiceAsync {
	public void getSessionData(AsyncCallback callback) ; // 서버에서 세션을 전달받아 리턴하자.
}
package com.gxt.riaapp.server;

import javax.servlet.http.HttpSession;

import com.gxt.riaapp.client.RiaAppPgmService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class RiaAppPgmServiceImpl extends RemoteServiceServlet implements RiaAppPgmService {
	private HttpSession getSession() {
		return this.getThreadLocalRequest().getSession();
	}
	@Override
	public int getSessionData() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("여기는 서버예요...");
		return 0;
	}
}



▶ 이제 client에서 getSessionData메소드를 호출해보자 Impl클래스의 메소드가 실행되어 System.out.println("여기는 서버예요..");가 실행되는 지 보도록 하자. 또한 실행됨과 동시에 리턴되는 값이 무엇인지도 확인해보자. 이러한 형태로 서버와 통신해서 최종적으로는 데이터베이스의 데이터등을 리턴하는 것이다.
▶ RiaApp.java를 수정해서 위의 getSessionData메소드를 호출해보자.
	public void onModuleLoad() {
		model = new RiaAppModel();
	    Registry.register(MODEL, model);  // 최초 실행시 Registry에 RiaAppModel을 등록한다.
		dispatcher = Dispatcher.get();
	    dispatcher.addController(new AppController());
	    dispatcher.addController(new NavigationController());  // 좌측
	    dispatcher.addController(new ContentController());
	    dispatcher.dispatch(AppEvents.Init);
	    
	    if(model.getEntries().size()>0) // 등록된 첫번째 프로그램을 보여준다.
			showPage(model.getEntries().get(0));

	    onSubmit(); // 서버쪽 메소드를 호출해서 세션이 유효한지 확인한다.
	   
	}

	public int onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onSuccess(Integer result) {
				String time = new Date().toString();
				//if(result == -1){
					System.out.println("서버에서 리턴해준 결과는 "+result);
				//}			}
			@Override
			public void onFailure(Throwable caught) {
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", null);
			}
		});
		return 0;
	}


▶ 이제 실행해 보자.  아래 그림처럼 server 쪽 메소드에서 "여기는 서버예요"를 출력하고 그 다음 클라이언트쪽 메소드에서 "서버에서 리턴해준..."라고 출력된다. 여기서 주목할 부분은 위의 onSubmit()메소드에서 보여주는 것처럼 서버쪽 메소드를 호출하고 결과를 리턴받는데 성공할 경우 onSuccess()메소드가 실행되고 그렇지 않고 문제가 발생할 경우 onFailure()메소드가 호출되게 된다.
이 두개의 메소드는 두고두고 나올테니 기억하도록 하자.

▶ 이제 getSessionData메소드를 역할에 맞게 수정하도록 하자. 이 메소드는 세션이 유효한지 체크하는 것이다.
일단 메소드의 리턴 type을 변경하자 단지 세션이 유효한지 여부를 판단 할 것이라면 유효하지 않은 경우에는 onFailure메소드가 실행되도록 하고 onFailure메소드에서는 유효하지 않다는 메시지와 함께 로그인 페이지로 이동하도록 하면 될것이므로 리턴타입은 void로 변경하는것이 좋을 듯 하다.

// RiaAppPgmService.java
	public void getSessionData() throws Throwable ; // 서버에서 세션을 전달받아 리턴하자.

// RiaAppPgmServiceAsync.java
	public void getSessionData(AsyncCallback callback) ; // 서버에서 세션을 전달받아 리턴하자.

// RiaAppPgmServiceImpl.java
	@Override
	public void getSessionData() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("여기는 서버예요...");
	}

// RiaApp.java 
	public void onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onFailure(Throwable caught) {
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", null);
			}
			@Override
			public void onSuccess(Void result) {
				// TODO Auto-generated method stub
				
			}
		});
	}
이제 기존 int형이었던 메소드를 void로 변경하였으니 Impl클래스의 메소드를 구현해보도록 하자. 이 메소드는 세션을 구해와 유효한지 확인하는 메소드이므로 일단 세션정보를 얻어오도록 코딩하자. RiaAppPgmServiceImpl 클래스에 아래의 메소드를 추가하고 세션을 구해와 유효한지 확인해보자.
package com.gxt.riaapp.server;

import javax.servlet.http.HttpSession;

import com.gxt.riaapp.client.RiaAppPgmService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class RiaAppPgmServiceImpl extends RemoteServiceServlet implements RiaAppPgmService {
	private HttpSession getSession() {
		return this.getThreadLocalRequest().getSession();
	}
	@Override
	public void getSessionData() throws Throwable {
		HttpSession session = getSession();
		if( null == session.getAttribute("loginid")) // index.html페이지에서 전달해주고 loginChk.jsp에서 생성한 세션정보
			throw new Throwable("세션이 종료되었습니다.");
		else
			System.out.println("로그인 아이디는 : "+(String)session.getAttribute("loginid"));
	}
}


이제 실행해 보자. RiaApp.html파일을 실행할 경우와 index.html파일을 통해 로그인을 했을 경우와 비교를 해보자. 일단 RiaApp.html을 실행할 경우 아래 그림처럼 세션이 종료되었다는 경고창이 보일 것이다. 이는 로그인을 하지 않았으므로 세션이 존재하지 않아 경고창을 보여주는 것이다.

이제 index.htlm파일을 실행하여 정상적으로 로그인해보자. 아래 그림처럼 서버쪽 메소드에서 세션정보를 확인할 수 있다.

▶ 자 이제 생각을 해보자. RiaApp.html은 실제 프로그램이 돌아가는 영역이고 index.html은 로그인 페이지다. RiaApp.html을 실행해서 세션이 존재하지 않을 경우 즉 로그인이 되어 있지 않거나 일정시간 사용하지 않아 세션이 끊겼을 경우 "세션이 종료되었습니다.."라는 메시지를 출력해주었다. 그럼 이제 무엇을 해야할 까? 경고창이 나오고 ok버튼을 클릭했을 경우 로그인 페이지로 유도하는 것이 필요할 듯 하다. 로그인 페이지로 유도하고 로그인을 하고 다시 RiaApp.html을 호출하여 프로그램을 보여줄수 있도록 하는것이다.
▶ 그럼 무엇이 필요할까 생각해보자. 우선 위의 경고창에서 보듣 "ok"버튼을 클릭했을 경우 이 클릭한 이벤트를 이용해서 로그인 페이지로 이동하는 코드를 만들어 보자. 우선 "ok"버튼을 감시할 리스너를 선언하자.
// RiaApp.java를 수정하자.
	/**
	 * Native Javascript Call
	 */
	private native void goIndex() /*-{ 
		$wnd.location.href = "/index.html"; 
	}-*/;

	/**
	 * 세션이 유효하지 않을 경우 뛰워주는 경고창 내의 OK버튼을 감시할 리스너
	 */
	final Listener btnListener = new Listener() {  
		public void handleEvent(MessageBoxEvent ce) {  
			goIndex();
		}  
	};  
	public void onSubmit() {
		service.getSessionData( new AsyncCallback() {
			@Override
			public void onFailure(Throwable caught) {
				// 버튼 리스트를 설정
				MessageBox.alert("로그인", "세션이 종료되었습니다. \n 다시 로그인 해주세요", btnListener);
			}
			@Override
			public void onSuccess(Void result) {
				// TODO Auto-generated method stub
				
			}
		});
	}

이제 실행해 보자. RiaApp.html을 실행해 보면 로그인을 하지 않았으므로 "세션이 종료되었습니다. 다시 로그인해주세요"라는 메시지를 출력하고 "OK"버튼을 클릭하면 로그인 페이지로 이동하게 될것이다. 이렇게 하면 기본적으로 로그인에 대한 처리가 완료되었다.
Posted by 1 베니94

댓글을 달아 주세요

  1. soul

    이렇게 계속 강좌를 올려주셔서 정말 많은 도움이 됩니다. 감사합니다.

    이건 강좌와는 상관없는 질문일수 있는데요..

    제가 만든 jar 파일을 import 시켜서 사용할순 없나요?

    실행하면 계속 오류가 나고 자료를 찾아봐도 잘 나와있지 않아서요 ^^

    2011.03.07 12:09 [ ADDR : EDIT/ DEL : REPLY ]
    • gwt에는 제약사항이 있습니다.
      참고글: http://blog.naver.com/moviel0ve?Redirect=Log&logNo=130035105414
      위의 글을 자세히 읽어보시면 gwt의 제약사항이 무엇인지 나와있을 겁니다.
      추가하신 라이브러리가 어떤 자바패키지를 이용했는지는 알수 없으나 예측컨데 gwt에서 제한된 클래스를 사용해서 개발된 라이브러리일 가능성이 높고 이 경우 프로젝트 내의 client 모듈에서는 사용이 안될것입니다.

      2011.03.07 13:38 신고 [ ADDR : EDIT/ DEL ]
    • soul

      답변 정말 감사드립니다.
      정말 알수록 어려운길이군요 ㅎㅎ
      앞으로도 깨알같은 강좌 계속 부탁드려요~

      2011.03.07 13:57 [ ADDR : EDIT/ DEL ]
  2. zeno

    안녕하세요.. 우연하게 블로그를 찾게 되었는데요.. 좋은 정보 정말 감사드립니다.. 저도 gwt 프로젝트를 하는 중인데요.
    저 같은 경우에, 서버쪽에서 session.setAttiribute("id") 로 세팅을 함에도 불구하고... F5 키를 누르면 세션이 해제되어
    다시 로그인 페이지로 돌아갑니다... 혹시 이런 문제 없으셨는지요 ?
    저는 로그인 페이지를 html 에서 작업한건 아니구요. gwt 로 UI 를 그려서, id / pw 값을 서블릿단에서 받아서,
    DB 검증 후, session 에 담는 로직입니다.. 세션 해제 되는 문제에 대해 구글링을 아무리해도 .. 명확한 답이 없네요.
    쿠키로 처리하라는 말도 있는데.. 쿠키는 좀 아닌것 같구요.. 그래서.. 조언 부탁드립니다...

    2012.02.20 10:36 [ ADDR : EDIT/ DEL : REPLY ]
    • F5키를 눌렀을때 세션이 해제되어 로그인 페이지로 가는것이 확실한가요?
      코딩에 따라 세션과는 무관하게 로그인페이지로 가는경우도 있어요~ 세션이 어떤 상태인지 정확히 로그를 찍어볼 필요가 있겠습니다.

      2012.02.21 09:11 신고 [ ADDR : EDIT/ DEL ]
  3. zeno

    안녕하세요.. 위의 문제는 EntryPoint 페이지를 분기문으로 분리해서 발생하는 것 같습니다. 그러니까.. EntryPoint 페이지가 2개가 있고, 세션에 따라 분기되게 했는데요.. RootPanel 에 페이지가 분기되면 세션 쪽이 문제가 되는 것 같습니다.
    뭐가 문제인지는 정확히 파악을 못했구요. ㅡㅡㅋ.. 시간이 없는 관계로 우선 RootPanel 분기처리 안되게 하니까.
    세션이 정상작동 하고 있습니다.

    2012.03.06 19:21 [ ADDR : EDIT/ DEL : REPLY ]
  4. zeno

    여기에 문의를 계속드려도 모르겠습니다만, 현재.. 여러가지 문제에 직면하고 있는데.. 그중에 2가지만 우선 질문을 드려도 될지 모르겠습니다..
    첫째는, 클라이언트쪽 로그가 안찍힙니다.
    http://allen-sauer.com/com.allen_sauer.gwt.log.demo.LogDemo/LogDemo.html?log_level=TRACE
    위 사이트를 참조해서 세팅을 했구요. 어디가 문제인지 모르겠습니다.
    xxx.gwt.xml 파일에 <inherits name="com.allen_sauer.gwt.log.gwt-log-DEBUG" /> 추가하고..
    클라이언트 페이지에 Log.debug.. 등을 호출했으나.. 위 사이트 처럼 로그가 찍히지 않네요.
    혹시, 제가 더 세팅해야 될 곳이 있는지.. 문의 드립니다.

    둘째는, ListGrid 를 서버에서 ibatis + json 으로 가져오는 로직으로 crud 를 구현하고 있는데요.
    getGrid().setSelectionAppearance(SelectionAppearance.CHECKBOX);
    옵션으로 자동으로 header에 checkbox 가 만들어지도록 했는데.... checked 된 row 값에 접근하는 방법을 모르겠습니다. 아무리 찾아봐도 방법을 못찾아서.. ListGridField Boolean 형태로 만들어서 다시 구현을 하긴 했는데..
    문제는 전체선택을 하기 위한 , 메소드를 구현했는데요. for() { dataGrid.getRecord(i).setAttribute("SEL", true); }
    이 부분이 실행이 안됩니다.. Grid 에 데이터가 있는 상태에서 강제로 seleected 되도록 했는데.. 작동을 안하네요.

    혹시, 이부분 해결을 하셨는지 문의드립니다.

    죄송합니다.. 이렇게 긴글을 남겨서요.. 이틀째.. ListGrid 쪽만 보고 있는데.. 생각처럼 잘 안되서 답답하네요..
    그럼.. 답변 부탁드립니다.. 꾸벅~

    2012.03.06 19:34 [ ADDR : EDIT/ DEL : REPLY ]
    • zeno

      저는 SMARTGWT 로 현재 개발중이구요..
      getGrid().setSelectionAppearance(SelectionAppearance.CHECKBOX);

      접근문제는 해결했습니다.

      -> getGrid().getSelectedRecords()
      라는 메소드가 지원을 하네요..
      간단한 문제를 놓치고 있었습니다.. 질문을 드리고 나니.. 좀 정리가 된 것 같습니다..^^;

      로그 문제는 아직 잘 안되고 있습니다.
      시간되시면 답변 부탁드릴께요..

      염치없지만.. 앞으로 종종 문의드리겠습니다.

      2012.03.06 20:29 [ ADDR : EDIT/ DEL ]
  5. zeno

    안녕하세요.. 위 로그는 그냥 브라우저 디버깅 모드를 사용하기로 했습니다. 브라우저 상에 div 태그로 로그가 보이게 하는 건 .. 잘 안되네요..

    베니님께서 올리신 글중에 데이터 처리방식에 대한 내용이 없는 것 같아서... 질문드립니다.
    저는 쿼리 조회시, 서블릿에서 넘겨받은 (조회조건)파라미터로 ibatis 를 이용해서, 받은 데이터를 json 변환 후 callback 시, string 을 grid 에 넘기는 방식으로 코딩을 했는데요.. 여기까지는 문제가 없습니다.
    이부분은 smartgwt 부분이긴한데.. ( gwt 는 아직 해보지 못했습니다 )..
    DataSource 를 이용해서, 서버에 DB 결과값을 json 형태의 파일로 저장후, Grid 에서 해당 Datasource 파일에서 데이터를 가져오는 방식으로 변환하려고 합니다. ( LiveGrid 등이 지원이 안되서요..)
    그런데, RemoteServiceServlet 를 상속받은 서블릿에서 FileWrite 가 안되고 있습니다. ( 앱엔진 지원안함 )
    혹시, list 성 프로그램 조회는 어떻게 구현하셨는지요 ?

    여기에 계속 글을 남겨도 되는지 모르겠습니다.. ^^:;

    2012.03.13 11:40 [ ADDR : EDIT/ DEL : REPLY ]
  6. 저는 return this.getThreadLocalRequest().getSession();
    이 코드에서 아래와 같은 에러가 납니다.ㅠ
    The method getThreadLocalRequest() is undefined for the type

    GWT + Maven Project 로 만들어서 사용하고있는데 왜그런건가요?ㅠ

    2014.06.24 14:52 신고 [ ADDR : EDIT/ DEL : REPLY ]