ログ日記

作業ログと日記とメモ

GWTとSeasar2で共通処理

GWTで共通処理を書く場合はどうすればいいのかを調べていて、GWT-RPCを別のアプリに送信する方法を試した。

まず、CommonService.javaの@RemoteServiceRelativePathアノテーションでひとつ上の相対パスを指定します。

/**
 * The client side stub for the RPC service.
 */
@RemoteServiceRelativePath("../common")
public interface CommonService extends RemoteService {

次に、web.xmlの記述を変更し、/commonでこのサーバサイド処理を受け取るようにします。

GWTで共通処理を使いまわす方法 - 遅ればせながらGoogle App Engineでの開発記録日誌

WindowBuilderで生成したらpathは /appName/com.example.app になり、また、S2GWT的なもの( http://d.hatena.ne.jp/kaiseh/20100701/1277941404 )を利用して全てのRPCリクエストは/rpcに送っていたので、外部のRPCリクエストパスは@RemoteServiceRelativePath("../../appName/com.example.app/rpc")のように指定した。


これで完成…と思いきや、昨日書いたように使い勝手が悪い。ヘッダーやログイン情報などでこれを利用していると、EclipseGWTプラグインではテストできなくなる。サーバーの設定も込みで動かさないといけなくて、せっかくのプラグインの効果が半減だと思う。
なので、外部のRPC呼び出しは使わずにjarを使うことにした。





Javaを書き始めたのが最近なのでJarの作り方がよく分からなかったのだが、Eclipseのエクスポートでポチポチクリックしていくだけで出来た。
注意点が二つあった。

  • Jar用のAppLibrary.gwt.xmlを作る
  • ディレクトリー・エントリーの追加にチェックを入れる

後者は当たり前なのかもしれないが…Seasar2でクラスを静的には読み込めるのに「ルートパッケージ(XXXXX)に対応するリソースがクラスパスから見つかりませんでした」のエラーが出てハマった。
jar用のxmlは、アプリのgwt.xmlをコピーしてエントリーポイントを削除したものを別名で保存して利用する。そうしないとjarを読み込んだときに別アプリのエントリーポイントも起動してしまう。


あとは別のデータベースを使っているならそれ用の設定も必要。
jarアプリの方のdiconを編集する。
s2jdbc.dicon

<components>
  <include path="app-s2jdbc.dicon" />
</components>

app-s2jdbc.dicon

<components>
  <include path="app-jdbc.dicon">
  <include path="s2jdbc-internal.dicon">
  <component name="appJdbcManager" class="org.seasar...">
  ...
  </component>
</components>

このように複数DB接続用の片方を設定する感じ。
s2jdbc-genを使っているならそっちのjdbcmanagernameの設定も変更する。自動生成されたsetterは

    @Resource(name = "appJdbcManager")
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public void setJdbcManager(JdbcManager jdbcManager){
        this.jdbcManager = jdbcManager;
    }

となっていた。
TransactionAttributeTypeの説明は http://otndnld.oracle.co.jp/document/products/as10g/101310/doc_cd/web.1013/B31852-01/servtran.htm#382118 この辺とか。



カスタマイザは
http://d.hatena.ne.jp/newta/20091008/1254997110
こちらを使わせてもらった。
プロパティが無いときに例外が発生したのでtryを追加した。

/**
 * DIするプロパティ設定をカスタマイズします。
 * @author newta
 */
public class DIPropertyCustomizer extends AbstractCustomizer {

    private final String expression;
    private final String propertyName;

    public DIPropertyCustomizer(String propertyName,String expression){
        this.propertyName = propertyName;
        this.expression = expression;
    }

    @Override
    protected void doCustomize(ComponentDef componentDef) {

        try {
            PropertyDef propertyDef = componentDef.getPropertyDef(propertyName);

            if(propertyDef == null)
                return;
            propertyDef.setExpression(new OgnlExpression(expression));

        }catch (PropertyNotFoundRuntimeException e){
                return;
        }
    }
}

たぶんserviceパッケージにGWTのRPC受け取りクラスも入っているからだと思う。Seasar的にはserviceパッケージってDBを使うものも使わないものも全部ひっくるめていいんだよね…?
コメントのとおりすがりさんと同じく汎用的な実装方法が分からないので取り敢えずこれで。


パッケージ構成は以下のようになった。

- app
   +- client
   |     +- rpc                  GWT-RPC
   |     |   +- MyRpcService.java
   |     |   +- MyRpcServiceAsync.java
   |     +- App.java
   |     +- その他uiなど
   +- server
   |     +- app                  SeasarのaddRootPackageName指定パッケージ
   |     |   +- entity
   |     |   +- service
   |     |          +- impl
   |     |          |    +- MyRpcServiceImpl.java GWT-RPC
   |     |          +- AbstractService.java
   |     |          +- ....Service.java      s2jdbc-genによって生成されたコード
   |     |          +- MyServletService.java
   |     +- rpc
   |     |   +- S2RemoteServiceServlet.java
   |     +- servlet
   |           +- MyServletProxy.java
   |           +- MyServletInterface.java
   +- App.gwt.xml
   +- AppLibrary.gwt.xml
   +- app.dicon
   +- その他dicon

client.rpcはインターフェース格納用に追加した。ウィザードで生成したらclient.rpc以下とserver.rpc以下にファイルが生成されるので、server.rpc以下に生成されたファイルはs2containerで管理するためにserver.app.service.implに手動で移動する。
Servlet関連はテスト的に追加した。web.xmlにはProxyを指定しておき、Proxyからapp以下の実装クラスを呼び出すとサーブレットでSMART Deployができる。

package app.server.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.seasar.framework.container.SingletonS2Container;

public class MyServletProxy extends HttpServlet{
    private static final long serialVersionUID = 1L;

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
 {
        MyServletInterface service = 
          SingletonS2Container.getComponent("myServletService");
        service.doPost();
    }
}

MyServletInterfaceはs2container管理外で、MyServletServiceはs2container管理になる。
まぁサーブレットは使わないかな…使いたくなったときのためのメモ。


ここからjarを作るときはapp.diconなどを外して、diconファイルはapp-s2jdbc.diconとapp-jdbc.diconだけを含めるようにする。
そしてjarを利用するアプリのapp.diconからapp-s2jdbc.diconをインクルードする。
利用する方のアプリのgwt.xml

<inherits name="app.AppLibrary" />

を追加するとjarのMyRpcServiceAsyncインターフェースを通じてRPC呼び出しが出来るようになる。サーバー側のコードは普通にimportすればそのまま使える。


以上で完了。
S2RemoteServiceServletを使っているおかげで、jarにどんなRPCが定義されていようともweb.xmlを編集する必要がないというのが良いね。