Symfoware

Symfowareについての考察blog

Google Web Toolkit 2.1 GWT RPCの使用

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


簡単なアプリケーションを作成しましたが、クライアント側の
JavaScriptの動作で完結する内容でした。

実際は、サーバーと通信してデータの取得や登録を行う
アプリケーションが多いと思いますので、RPCの使用に関する
章も試してみることにします。


Making Remote Procedure Calls
こちらを参考に、プログラムを作成しました。




1. Creating a service



これまでのチュートリアルで作成した「StockWatcher」で、株価の値は
StockWatcherクラスのrefreshWatchListメソッドでランダムに生成していました。

該当する処理の箇所はここ。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // 価格情報の更新処理
        final double MAX_PRICE = 100.0; // $100.00
        final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

        StockPrice[] prices = new StockPrice[stocks.size()];
        for (int i = 0; i < stocks.size(); i++) {
            double price = Random.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE
                    * (Random.nextDouble() * 2.0 - 1.0);

            prices[i] = new StockPrice(stocks.get(i), price, change);
        }

        updateTable(prices);
        
    }





この部分をサーバー側で処理するように変更します。
まずは、「RemoteService」を継承して「StockPriceService」を作成します。

パッケージ・エクスプローラーで「com.google.gwt.sample.stockwatcher.client」を
選択しておきます。

10_001_20101107191601.png


Eclipseツールバーの[ファイル]-[新規]-[インターフェース]を選択。

10_002_20101107191601.png


名前に「StockPriceService」を入力して「完了」を押します。
その他はデフォルトのままでOK

10_003_20101107191601.png


StockPriceService.javaを以下の内容に変更します。
※変更した時点ではエラーが表示されると思いますが、次のソースを
作成することでエラーが解消されますので、一旦無視で。


package com.google.gwt.sample.stockwatcher.client;

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

@RemoteServiceRelativePath("stockPrices")
public interface StockPriceService extends RemoteService {
    StockPrice[] getPrices(String[] symbols);
}





次に、「RemoteServiceServlet」を継承したクラスを作成します。

Eclipseのツールバーから[ファイル]-[新規]-[クラス]を選択。

10_004_20101107191601.png


パッケージ名を
「com.google.gwt.sample.stockwatcher.client」から
「com.google.gwt.sample.stockwatcher.server」に変更します。

名前は「StockPriceServiceImpl」

スーパークラスに「com.google.gwt.user.server.rpc.RemoteServiceServlet」を
入力します。

10_005.png


インターフェースは右側にある追加ボタンを押して、
「com.google.gwt.sample.stockwatcher.client.StockPriceService」
を入力し候補を表示。OKを押します。

10_006.png


インターフェースの項目に、指定したクラスが追加されたことを確認して、
完了ボタンを押下します。

10_007.png


Eclipseで自動生成された「StockPriceServiceImpl.java」は
以下のようになるはずです。



package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {

    @Override
    public StockPrice[] getPrices(String[] symbols) {
        // TODO 自動生成されたメソッド・スタブ
        return null;
    }

}





この「StockPriceServiceImpl」クラスの「getPrices」メソッドに
クライアントのJavaScriptで動作していた株価生成の処理を移動します。

処理記載後のStockPriceServiceImpl.javaはこうなりました。


package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.Random;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {
    
    private static final long serialVersionUID = 1L;
    private static final double MAX_PRICE = 100.0; // $100.00
    private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
    
    @Override
    public StockPrice[] getPrices(String[] symbols) {
        Random rnd = new Random();
        
        StockPrice[] prices = new StockPrice[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            double price = rnd.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f);
            
            prices[i] = new StockPrice(symbols[i], price, change);
        }
        return prices;
    }

}







この作成したサービスはservletで動作します。
そのため、web.xmlへservletの追加が必要になります。

http://localhost:8888/stockwatcher/stockPrices

というURLでサービスを公開しようと思いますので、
StockWatcher/war/WEB-INF/web.xml
を以下のように編集しておきます。

※雛形としてで出力されている「greetServlet」という定義があるかと
思いますが、ここで消してしまいます。


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Servlets -->
  <servlet>
    <!--====== stockPriceServiceImplというサーブレットを定義 ======-->
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <servlet-class>com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
    <!--====== /stockwatcher/stockPricesというURLに関連付け ======-->
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <url-pattern>/stockwatcher/stockPrices</url-pattern>
  </servlet-mapping>

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>StockWatcher.html</welcome-file>
  </welcome-file-list>

</web-app>









2. Invoking the service from the client



ここまでの操作でサーバー側で実行されるプログラムが作成できたことになります。

これから、クライアント側からサーバー側に通信するための
プログラムを作成します。

前の段落で作成した「StockPriceService」ですが、エラーが表示されて
いる状態だと思います。

ソースの左側にある黄色いアイコンをクリックすると、こんな感じで
修正候補が表示されるはずです。

10_008.png


一番先頭に表示される「Create asynchronous RemoteService interface ...」
を選択すると、ダイアログが表示されます。

そのまま完了を押下して、操作を進めます。

10_009.png


自動生成された「StockPriceServiceAsync」は以下のようになるはずです。



package com.google.gwt.sample.stockwatcher.client;

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

public interface StockPriceServiceAsync {

    void getPrices(String[] symbols, AsyncCallback<StockPrice[]> callback);

}






クライアント側では、このクラスを経由してサーバー側との通信を
実現する模様。

StockWatcherクラスに以下のimportを追加。

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




また、以下の変数も追加。


private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);




refreshWatchListメソッドの処理内容を
このように変更します。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // Initialize the service proxy.
        if (stockPriceSvc == null) {
            stockPriceSvc = GWT.create(StockPriceService.class);
        }
        
        // Set up the callback object.
        AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
            public void onFailure(Throwable caught) {
                // TODO: Do something with errors.
            }
            
            public void onSuccess(StockPrice[] result) {
                updateTable(result);
            }
        };
        
        // Make the call to the stock price service.
        stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
    }





ここまでできたら、開発用のサーバーを起動し、ブラウザでアクセスしてみます。
すると、こんな感じでエラーが表示されるはずです。

10_010.png



18:37:31.126 [ERROR] [stockwatcher]
subtype com.google.gwt.sample.stockwatcher.client.StockPrice is not
assignable to 'com.google.gwt.user.client.rpc.IsSerializable' or
'java.io.Serializable' nor does it have a custom field serializer
(reached via com.google.gwt.sample.stockwatcher.client.StockPrice[])




クライアントとサーバー間のデータのやり取りに使用している
「StockPrice」をシリアライズできなかったよ。というエラーですね。






3. Serializing Java objects



というわけで、データの通信に使用している「StockPrice」を
シリアライズ可能とするため、「Serializable」をimplementsするよう
変更してやります。

StockPriceで変更した箇所の抜粋はこちら。


package com.google.gwt.sample.stockwatcher.client;

// シリアライズするため、importを追加
import java.io.Serializable;

public class StockPrice implements Serializable {

    private static final long serialVersionUID = 1L;
    private String symbol;
    private double price;
    private double change;







4. Handling Exceptions



最後にエラー処理を追加します。

Eclipseのツールバー[ファイル]-[新規]-[クラス]を選択。

名前「DelistedException」
スーパークラス「java.lang.Exception」
インターフェース「java.io.Serializable」
を入力し、完了を押下。

10_011.png


DelistedExceptionを以下の内容に変更します。


package com.google.gwt.sample.stockwatcher.client;

import java.io.Serializable;

public class DelistedException extends Exception implements Serializable {

    private static final long serialVersionUID = 1L;
    private String symbol;

    public DelistedException() {
    }

    public DelistedException(String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return this.symbol;
    }
}






StockPriceServiceのgetPricesメソッドが作成した例外を
投げられるように修正します。


package com.google.gwt.sample.stockwatcher.client;

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

@RemoteServiceRelativePath("stockPrices")
public interface StockPriceService extends RemoteService {
    StockPrice[] getPrices(String[] symbols) throws DelistedException;;
}







エラー発生のサンプルとして、StockPriceServiceImplの株価を取得する処理で、
株式銘柄コードに「ERR」と入力された場合は、DelistedExceptionを
発生するように修正しました。

修正後の「StockPriceServiceImpl」はこんな感じになります。


package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.Random;
import com.google.gwt.sample.stockwatcher.client.DelistedException;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {
    
    private static final long serialVersionUID = 1L;
    private static final double MAX_PRICE = 100.0; // $100.00
    private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
    
    @Override
    public StockPrice[] getPrices(String[] symbols) throws DelistedException {
        Random rnd = new Random();
        
        StockPrice[] prices = new StockPrice[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            
            // 動作サンプルとして、銘柄コードに「ERR」と入力されている場合は
            // 例外を発生させる
            if (symbols[i].equals("ERR")) {
                throw new DelistedException("ERR");
            }
            
            double price = rnd.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f);
            
            prices[i] = new StockPrice(symbols[i], price, change);
        }
        return prices;
    }

}









エラーの表示は赤い文字で行いたいと思いますので、
StockWatcher.cssに以下の記載を追加。


/* エラー色を指定 */
.errorMessage {
  color: red;
}





StockWatcher.javaにエラー表示用のラベルを追加します。


    private Label errorMsgLabel = new Label();




作成したラベルオブジェクトをパネルに配置する処理も追加。


        // 部品が横に並ぶパネルに、銘柄入力テキストとボタンを追加
        addPanel.add(newSymbolTextBox);
        addPanel.add(addStockButton);
        // スタイルの指定を追加
        addPanel.addStyleName("addPanel");
        
        // エラーメッセージ表示領域
        errorMsgLabel.setStyleName("errorMessage");
        errorMsgLabel.setVisible(false);
        
        // 部品が縦に並ぶパネルに、エラー表示ラベル、テーブルと上記パネル、
        // 最終更新日ラベルを追加
        mainPanel.add(errorMsgLabel);
        mainPanel.add(stocksFlexTable);
        mainPanel.add(addPanel);
        mainPanel.add(lastUpdatedLabel);






refreshWatchListメソッドでTODOとして残していた、onFailureの処理を追記。
サーバー側で発生したエラーをラベルに出力します。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // Initialize the service proxy.
        if (stockPriceSvc == null) {
            stockPriceSvc = GWT.create(StockPriceService.class);
        }
        
        // Set up the callback object.
        AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
            public void onFailure(Throwable caught) {
                String details = caught.getMessage();
                if (caught instanceof DelistedException) {
                    details = "Company '" + ((DelistedException)caught).getSymbol() + "' was delisted";
                }
                errorMsgLabel.setText("Error: " + details);
                errorMsgLabel.setVisible(true);
            }
            
            public void onSuccess(StockPrice[] result) {
                updateTable(result);
            }
        };
        
        // Make the call to the stock price service.
        stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
    }






このままだと、一度エラーが発生した後はずっとエラーの表示が
残ってしまうため、エラー表示を消去する処理を追加します。


    private void updateTable(StockPrice[] prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
        }
        
        DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
        
        // 最終更新時間を表示
        lastUpdatedLabel.setText("最終更新時間 : "
            + timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
        
        // ここまで処理がくれば、エラーは発生していない
        // エラーメッセージ領域を非表示にする
        errorMsgLabel.setVisible(false);
    }






開発用のサーバーを起動し、ブラウザで動作を確認してみると、
狙い通りの動きになっているかと思います。

10_012.png




関連記事



Google Web Toolkit に関する記事の一覧









関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/07(日) 19:19:02|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Google Web Toolkit 2.1 JSON形式のデータの利用 | ホーム | Google Web Toolkit に関する記事の一覧>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
https://symfoware.blog.fc2.com/tb.php/640-570e3985
この記事にトラックバックする(FC2ブログユーザー)