GWTアプリケーションのバックエンドにSpring Frameworkを利用する

Java

GWTはブラウザ上で動作する Ajax系のUIツールキットだが、サーバ側のメソッドを呼び出す(=RPC)ための仕組みも含まれている。GWTアプリケーションから呼び出すことの出来るサーブレットを RemoteServiceServletという。典型的に、RemoteServiceServletはサーバ上のデータベースにアクセスしてビジネスロジックを実行するための役割を与えられる。

この記事では、RemoteServiceServletを実装するにあたって Spring Frameworkを使用するためのもっともシンプルな方法を解説する。Spring Frameworkはソフトウェア部品の動的な組み立て(DI)を支援するほか、Hibernateや各種JPA実装などといったORMと、それらにまつわるトランザクション管理の統合などを提供するフレームワークである。

GWT-Springの利点

EJB3.0が登場するまでの間、Struts-Spring-Hibernateの組み合わせは一世をを風靡したため、特にオフショアでJava人材の確保を行う際にも最も経験者を集めやすい組み合わせとなっているし、既存の資産も多い。この組み合わせのうち Struts の部分をGWT に置き換えることで古典的なサーバサイド Webアプリケーションの資産とノウハウを再利用しつつ RIAを構築することが出来るのではないだろうか。

GAEを利用する予定の人へ

Spring Frameworkの周辺技術はおおむね典型的なリレーショナル・データベース(RDB)を利用する目的で発展してきている。アプリケーションを動作させる環境が一般的な Java EEサーバとRDBの組み合わせであれば良いが、RDBを利用することができない Google App Engine(=GAE、Googleのクラウド)をアプリケーションの動作環境として想定しているのであれば Spring Frameworkが役に立つ場面は(少なくとも現状では)ほとんどないため、この先を読まずに他の記事へ飛んで頂いたほうがよいだろう。

Spring Frameworkの jarファイルをプロジェクトに追加する

プロジェクトの war/WEB-INF/lib フォルダに下記の jarファイルを追加する(本記事執筆時の最新バージョンにおけるファイル名)。これは Spring Frameworkの最低限の機能を利用するためのセットなので、必要に応じて他の jarファイルも追加すること。

org.springframework.asm-3.0.3.RELEASE.jar
org.springframework.beans-3.0.3.RELEASE.jar
org.springframework.context-3.0.3.RELEASE.jar
org.springframework.context.support-3.0.3.RELEASE.jar
org.springframework.core-3.0.3.RELEASE.jar
org.springframework.expression-3.0.3.RELEASE.jar
org.springframework.web-3.0.3.RELEASE.jar

上記のうち beans, context, core, web はソースのコンパイル に必要なため、右クリックメニューから Build path -> Add to build path してビルドパスに追加しておく。

applicationContext.xmlの追加

Webアプリケーションで Spring Frameworkを使用する場合、通常は applicationContext.xml という名前の設定ファイルを設置することになっている。このファイルを war/WEB-INF に作成する。

下記に空の applicationContext.xmlを例示しておく。(そう、これで空なのだ!)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:util="http://www.springframework.org/schema/util"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:jee="http://www.springframework.org/schema/jee"
  xmlns:lang="http://www.springframework.org/schema/lang"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
    <!-- この1行が入っているとアノテーションによるオートワイヤリングが有効になる -->
    <context:annotation-config/>
 
    <!-- ここに各種設定が挿入される -->
</beans>

web.xmlにSpring Frameworkのリスナーを登録する

Webアプリケーションの起動時に Spring Frameworkが自動的にさきほどの applicationContext.xmlを発見して初期化出来るようにするため、web.xmlに ContextLoaderListener というリスナーを登録する必要がある。また、ついでに RequestContextListener というリスナーも登録しておくことでHTTPセッションスコープの Beanなどが使えるようになる。これが何の役に立つか今知らなくてもとりあえず入れておいて損はない。

上記のリスナーを登録するには、war/WEB-INF/web.xml に下記の 要素を挿入する。

<listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>
<listener>
    <listener-class>
      org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

サーブレットを Spring Frameworkとリンクする

RemoteServiceServletは GWTアプリケーションからのリモート呼び出しに特化されてはいるものの、普通の HttpServlet を継承しているため、初期化処理を init() メソッドに書くことが出来る。ここに、Spring Frameworkが保持するコンポーネント(Bean)群と自分自身とのリンク(ワイヤリング)処理を呼び出すコードを記述する。

public void init() throws ServletException
{
    try { 
        // applicationContext.xmlに定義されているBean群とこのサーブレットを
        // アノテーションに従って Autowireする
        WebApplicationContextUtils
            .getRequiredWebApplicationContext(getServletContext())
                .getAutowireCapableBeanFactory().autowireBean(this);
    }
    catch (IllegalStateException ex) {
        // この例外が起こる場合web.xmlに設定が足りない
        throw new ServletException(
            "Couldn't get Spring's WebApplicationContext. " +
            "Please check whether ContextLoaderListener exists " +
            "in your web.xml.");
    }
}

ここまでで、Spring Frameworkと RemoteServiceServletの接続ができた。Webアプリケーションを Development Modeで実行してみて、Spring関係の起動ログが出力されなおかつ例外が起こっていなければうまく行っている。

以降、実際に Spring管理下のコンポーネントを RemoteServiceServletから利用する例の説明に移ることにする。

Spring Frameworkを使ったもっとも単純なデータベースアクセス

ここではシンプルさを優先して、O/Rマッパーを使わずに SQLでデータベースへアクセスする例を示す。

MySQLやPostgreSQLなどのデータベースサーバーを前提にしてしまうと設定が複雑になってしまい本記事のフォーカスがブレてしまうので、サーバーの必要ないデータベースである H2 Database Engine を使って説明する。(実は Java 6には JavaDB(Derby)という組み込みデータベースが含まれているのでそれを使うのが一番簡単かと思ったのだが、標準でクラスパスが通っているわけではないことから却って説明が面倒になる恐れがあり避けた)

Springのデータアクセス用コンポーネントとH2 Databaseの準備

データベースアクセスの機能を用いるにあたり、Spring Frameworkの配布物に含まれる jar ファイルのうち、下記がさらに必要になるためプロジェクトの war/WEB-INF/lib にコピーする。また、いずれもコンパイル時に必要なため右クリックメニューから Build path -> Add to build path してビルドパスに追加する。

org.springframework.jdbc-3.0.3.RELEASE.jar
org.springframework.transaction-3.0.3.RELEASE.jar

H2のサイトから "All platforms" 用のzipファイルをダウンロードし、その中から h2-バージョン番号.jar を探し出してプロジェクトの war/WEB-INF/lib にコピーする。こちらはコピーだけで良い。

データベース接続設定(データソース)の定義

applicationContext.xml に、H2用のデータソースを定義する。下記の例では、メモリ内データベースを利用するためのデータソースを "dataSource" という名前で定義している。メモリ内データベースとは、プログラムが起動されると生成され、終了と当時に消えてしまうデータベースであり、実運用には適さないがテスト用には大変役に立つ。

<!-- H2のメモリ内データベースを使用するためのデータソース -->
<bean name="dataSource" class="org.h2.jdbcx.JdbcDataSource">
    <property name="URL" value="jdbc:h2:mem:h2;DB_CLOSE_DELAY=-1"/>
</bean>

もし MySQLがどこかアクセス可能な場所で動いていて、設定にも自信があるなら代わりに下記のような設定をすることで MySQLデータベースに接続できるだろう。ただしこれを動作させるためには MySQLの JDBCドライバが必要になる。


<bean id="dataSource"
        class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
    <property name="url" value="jdbc:mysql://ホスト名/データベース名"/>
    <property name="user" value="ユーザー名"/>
    <property name="password" value="パスワード"/>
    <property name="zeroDateTimeBehavior" value="convertToNull"/>
    <property name="useUnicode" value="true"/>
    <property name="characterEncoding" value="UTF8"/>
</bean>

デプロイ先のアプリケーションサーバがデータソースを管理しておりそれを JNDIで提供している(アプリケーション側で接続設定を持つ必要がない)場合は下記のようにすることで JNDIからデータソースの参照をもらってくることが出来る。

<!-- 自分で設定せずアプリケーションサーバからもらってくるデータソース -->
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/データソース名"/>

データソースをサーブレットにワイヤリングする

先ほど init() メソッドに記述したオートワイヤリング処理の呼び出しにより、@Autowiredのついたフィールドには自動的に Springで定義した Beanがセット(インジェクション)されるので、サーブレットに下記のようなプライベートフィールドを追加するだけで、データソースを参照できるようになる。なお@Autowiredアノテーションは setterメソッドに付けても良い。

// サーブレットにこの行を追加
@Autowired private DataSource dataSource;

SQLを実行する

データソースを取得さえできれば、それを使ってSQLを実行することができる。GWTアプリケーションを新規作成した際に生成されるGreetingServiceImplを使って SQLを呼び出す例を説明する。

GreetingServiceImplサーブレットの greetServerメソッドは、サンプルのGWTアプリケーションでテキストを入力してボタンを押すと呼び出され、適当な文字列を返すようになっている。

String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
return "Hello, " + input + "!

I am running " + serverInfo + ".

It looks like you are using:
" + userAgent;

この文字列の末尾に、適当なSQLの実行結果を付け足してやることにした。いかんせんメモリ内データベースは毎回テーブルがなにも無い状態から始まるため、テーブルが無くても実行できるSQLということで、ただの足し算を実行させる。

SimpleJdbcTemplate jt = new SimpleJdbcTemplate(dataSource);
 
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
return "Hello, " + input + "!

I am running " + serverInfo + ".

It looks like you are using:
" + userAgent + ".

By the way, 1+1=" + jt.queryForInt("select 1+1");

ブラウザから実行した結果は右図のようになる。

SimpleJdbcTemplateは、Spring Frameworkの提供する SQL API群である。データソースをコンストラクタに与えて new してやることで、様々な SQL呼び出しのメソッドを提供してくれる。

実は SimpleJdbcTemplateはスレッドセーフなので都度 newしなくても良い。使い回しをする場合は setDataSource メソッドを作ってその中で newして取っておくように処理すれば良いだろう。

Springの SQL APIについて詳しくは公式ドキュメント(英語)を参考のこと。

関連記事

同じカテゴリの記事

ScalikeJDBCを Springで使う 2013年12月25日
Commons Codecを使わずにBase64エンコードをする 2010年8月9日
JRubyをJava組み込みで使うときの日本語文字化けについて 2010年7月14日
GWT DevMode doesn't find jndi.properties 2010年7月10日

お勧めカテゴリ

英語でアニメ観ようず
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
ScalaのようでJavaだけど少しScalaなJSON API
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
ドクジリアン柔術少女 すから☆ぱいそん
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図