GWTサンプルの分析1

2019年6月6日

最新Eclipse(2018/1 Oxygen)でGWTサンプルを動かすまでにて、EclipseのGWTプラグインの機能を使用して、サンプルソースコード入りのGWTプロジェクトを作り、それを実行した。

ブラウザの表示とプロジェクトの構造

ブラウザの表示は以下のようになる。単に入力した文字列をダイアログボックスに表示するだけ。

このサンプルの構造を見ていく。パッケージエクスプローラは以下の表示になる。

  • sampleパッケージの下にclient, server, sharedというパッケージがあるが、ここにそれぞれクライアント用、サーバ用、両者共通のコードが含まれている。
  • sampleパッケージ直下にあるSample.gwt.xmlは、クライアントのみで使われ、上記のclient,sharedパッケージがクライアント用であることを示す。また、他のパッケージが動作に必要な場合には、ここに記述される。
  • warフォルダ以下は、一般的なwarファイルの構成になっており、この中のSample.htmlがメインとして表示するページのソース。

gwt.xmlファイル

この内容は以下のようになっている。重要な点は、inherits, entry-point, sourceである。

<?xml version="1.0" encoding="UTF-8"?>
<!--
  When updating your version of GWT, you should also update this DTD reference,
  so that your app can take advantage of the latest GWT module capabilities.
-->
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.7.0//EN"
  "http://gwtproject.org/doctype/2.7.0/gwt-module.dtd">
<module rename-to='sample'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.clean.Clean'/>
  <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

  <!-- Other module inherits                                      -->

  <!-- Specify the app entry point class.                         -->
  <entry-point class='sample.client.Sample'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

  <!-- allow Super Dev Mode -->
  <add-linker name="xsiframe"/>
</module>

inherits

inheritsは、このクライアントが他のクライアント用ライブラリを使用することを意味する。例えて言えば、importにあたる。あるクライアントプログラムを作成するには、様々な外部ライブラリを利用することが考えられるが、それらはすべてinheritsが必要。

それらのライブラリもすべてJavaコードなので、クライアントコード内でimportすればEclipse-IDE上ではエラーにならないのだが、inheritsがなければ実行もコンパイルもできない。

上記の例では、com.google.gwt.user.User, com.google.gwt.user.theme.clean.Cleanがinheritsされているが、これはもともとのGWTに含まれる基本的なライブラリを示している。

このライブラリがサンプルのソースコード上では、例えば以下のようにimportされている。

import sample.shared.FieldVerifier;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;

entry-point

entry-pointは、クライアントプログラムの開始点を示す。生のJavaであれば、static void main(String[]args)にあたる。

ブラウザ側はSample.htmlを表示しているだけだが、そのhtmlの中にクライアントコードを呼び出す記述があり、その結果クライアントコードが実行されるのだが、そのクライアントコードの開始点をここで指定する。

この例では、sample.client.Sampleというクラスが指定されており、このクラスは以下のようにEntryPointが実装されている。


public class Sample implements EntryPoint { .... public void onModuleLoad() { final Button sendButton = new Button("Send"); final TextBox nameField = new TextBox(); ....

EntryPointの定義は以下の通り

package com.google.gwt.core.client;
public interface EntryPoint {
  void onModuleLoad();
}

つまり、GWTクライアントプログラムは、EntryPointを実装したクラスがgwt.xml中にてentry-pointとして指定され、そのメソッドonModuleLoad()から開始する。

source

sourceはクライアント用のソースを示す。この例では以下のようになっているが、もちろんこれ以上のパッケージを指定してもよい。

  <source path='client'/>
  <source path='shared'/>

クライアント用のソースがclientとsharedに分かれている理由は、clientはクライアントのみで使用され、sharedはサーバでも使用されることである。つまり、shared下にクライアント・サーバで共通に使うもの、もしくは、クライアント・サーバ間でやりとりするオブジェクトを定義する。

※この例では、sharedに定義されているものはFieldVerifierというクラスのみだが、あまり有意義なものとは言えない。

ソースを分けねばならない理由

ソースをclient, shared, serverに分ける理由なのだが、第一には、すべてをJavaで記述できるとは言っても、何も考えずに同じコードを書くことはできない。様々な制限がある。

使用可能な言語仕様が異なる

現在の最新のGWTではJava8の言語使用をクライアント側でも使用できるが、過去のGWTでは使用できなかった。だから、

  • クライアント側ではJava6レベルの言語仕様で記述せねばならず
  • サーバ側ではJava8のストリーム等が使用できる

ということが起こっていた。当然sharedに入れるものもクライアント側の言語仕様に合わせなければならない。

使用可能なライブラリが異なる

client, sharedのコードはJavaScriptに変換されるのだが、もともとのJavaに備わるライブラリもすべてJavaScript側で用意されているかといえばそうではない。例えば、StringBuilder等の簡単なものは使えるのだが、複雑な仕様のものや、もちろんjava.awt等は用意されていない。つまり、サーバ側と同じライブラリがクライアント側でも使えるとは限らない。

逆に、クライアント側では、各種のGWT用のライブラリを使用する。例えば、com.google.gwt.core.client.GWTというパッケージがそれだが、名前の通りこれはクライアントでのみ使用でき、サーバ側では使えない。

特にsharedに入れるものに注意

上記のようにクライアント側とサーバ側のコードは同じJavaとはいっても様々な面で異なってくる。だから、両方で共通に使用するshared内のコードが両者で通用するようなコードにしなければならない。

そしてまた、この例では現れていないのだが、「クライアントとサーバ間でやりとりするオブジェクトの定義」については、特に注意が必要である。これはおいおい説明していく。

モジュール

*.gwt.xmlは「モジュール」と呼ばれるもので、この例ではその名前はsampleである。以下で指定されている。

<module rename-to='sample'>

基本的にモジュールは、一つのページに張り付いた(変換の結果の)JavaScriptである。GWTでは、簡単に複数ページを扱うことはできず、ページ毎にモジュールを作成する必要がある。

モジュールのコンパイルには時間を要する

そして、複数のモジュール(複数ページ)の作成は非現実的である。なぜなら、一つのモジュールのコンパイルにそれぞれかなりの時間がかかるからである。これは、そのモジュールに使用するライブラリの規模にもよるのだが、大きな規模のライブラリを使うと、そのモジュールのコンパイルに長時間を要する。

GWTのコンパイルは、単に参照するJavaライブラリをくっつけるだけではなく、それらのJavaライブラリ中の必要なものもすべてJavaScriptに変換しなければならない。あらかじめJavaScriptに変換しておくことができないのだ。毎回すべてをJavaScriptに変換する必要がある。

※ただし、Eclipse上でのデバッグ中は別で、これはキャッシュが作られる。上はあくまでもデプロイ用のwarファイル生成の際の話である。

GWTが表示画面を切り替える方法

GWTが、その表示画面を切り替えるやり方としては、基本的には単一のページ(単一のモジュール)のみを作成し、そのDOMツリーを実行中にすり替えてしまう、あるいは新たなDOMツリー要素をその場で作成するなどの方法が一般的である。

例えば、上部にある何らかのボタンが押されたら下部すべてを別の画面にしてしまうといったことは、DOMツリーの操作によって行うのであり、HTMLのフレームの機能ではない。