実用的なGroovy: Groovyでサーバー側に対応する

GroovletsとGSPを使った即時対応のサーバー側プログラミング

GroovletとGSP(GroovyServer Pages)フレームワークは、Java® Servlet APIの上に構築されています。ただし、Groovyのサーバー側実装はStrutsやJSFとは異なり、全ての場合に対応するわけではありません。むしろサーバー側アプリケーションを素早く、容易に構築するための簡略手段と言えるものです。Groovy提唱者のAndrew Gloverが、このフレームワークと、その使い方を紹介します。

Andrew Glover, CTO, Stelligent Incorporated

Andrew GloverAndrew Gloverは合衆国ワシントン特別区にある、Vanward TechnologiesのCTO(最高技術責任者)です。Vanward Technologiesは自動化テスト・フレームワークの構築を専門としており、ソフトウェアのバグ発生数や統合時間やテスト時間の減少、また全体的なコード安定性改善に貢献しています。



2005年 3月 15日

Javaプラットフォームは、サーバー側アプリケーション開発におけるプラットフォームの選択肢として、その地位を確立しています。サーバー側のJava技術としてサーブレットが強固な足場を固めているため、サーブレットAPIに関する様々なフレームワークが構築されており、一部の名前を挙げるだけでも、StrutsやJSF(JavaServer Faces)、Tapestryなどがあります。皆さんが想像される通り、Groovyにも、サーブレットAPIに関連してフレームワークが構築されています。しかしこのフレームワークは、何よりも単純さを目的として作られているのです。

GroovletとGSP(GroovyServer Pages)フレームワークの目的は、ごく単純にWebアプリケーションを構築するための、優雅かつ単純なプラットフォームを提供することです。GroovySqlがデータベース開発における唯一の選択肢ではないのと同様に、Groovletフレームワークは、Strutsのように機能豊富なフレームワークの置き換えにはなりえません 。Groovletは、とにかく動作するコードを素早く作りたい、という開発者のための代替手段なのです。

例えば私は最近、xml-rpcに似たAPIのクライアント側をテストするためのスタブ・アプリケーションを、「すぐに」開発する必要に迫られました。サーブレットを使えば、すぐに必要な機能を作れることは明らかだったのですが、Strutsにまで入り込むことは全く考えませんでした。私はサーブレットとその関連ロジックを、ベースレベルの通常のJavaサーブレットAPIを使って書くことを考えたのですが、とにかくすぐに機能が必要だったので、Groovletを使って作ることにしました。

これから先を読んで頂ければ分かると思いますが、その選択は当然のものだったのです。

Groovletを使ったプログラミングの詳細に入る前に、後で示すサンプル・コードの中に使われる、Groovyの機能を簡単に復習しておこうと思います。最初に、私が以前、記事Feeling Groovy の中で書いた、defキーワードを復習しましょう。

このシリーズについて

どのようなツールであれ、開発作業の中に採り入れるためには、どういう場合に使うべきか、または使うべきではないかをよく知る必要があります。スクリプト言語は非常に強力なツールですが、その強力さは、適切なシナリオで適切な使い方をした場合にのみ発揮されます。実用的なGroovy シリーズはそうした点を念頭に置き、Groovyの実用的な使い方に焦点を絞って、どういう場合に、どのように使うのかを解説して行きます。

スクリプトの中でファンクションを定義する

通常のJavaプログラミングでは、メソッドはクラス・オブジェクトの中になければなりません。実際、全ての振る舞いは、クラス・コンテキストの中で定義する必要があります。しかしGroovyでの振る舞いは、クラス定義の外で定義される、ファンクション(functions) の中で定義されるのです。

こうしたファンクションは名前で直接参照され、Groovyスクリプトの中で定義されます。Groovyスクリプトの中で、本格的な再利用ができるのです。Groovyのファンクションにはdef キーワードが必要です。こうしたファンクションは、スクリプトのスコープ内で使用可能な、グローバルに静的なメソッドと考えることができます。Groovyは動的タイプ化言語なので、def にはパラメーターに対する型宣言が必要なく、またdefreturn ステートメントも必要としません。

例えばリスト1では、listmap といった、集合の内容を出力する単純なファンクションを定義しています。次にlist の定義に進み、それに中身を入れ、新たに定義したdef を呼びます。次にmap を作り、これにも同じことを繰り返します。

リスト1. これがdefです!
def logCollection(coll){
  counter = 0;
  coll.each{ x | println "${++counter} item: ${x}"
  }
}
lst = [12, 3, "Andy", 'c']
logCollection(lst)
mp = ["name" : "Groovy", "date" : new Date()]
logCollection(mp)

def にはreturn ステートメントは必要ないので、最後の行が何らかの値を生成すると、その値はdef によって返されます。例えば、リスト2のコードは、渡された変数のクラス名を返すdef を定義しています。return ステートメントを持つdef を書くことも、持たないdefを書くこともできますが、結果は同じものになります。

リスト2. defでは、returnステートメントはオプション
def getJavaType(val){
  val.class.getName()
}
tst = "Test"
println getJavaType(tst)

単純なスクリプトを書く場合には、defキーワードは非常に便利です。このすぐ後で分かると思いますが、defキーワードは、Groovletを開発する際にも便利なのです。


GroovletとGSP

GroovletとGSPを扱うために必要な条件は、非常に単純です。サーブレット・コンテナーと、最新版のGroovyが必要なだけです。GroovletやGSPフレームワークの利点は、選択したパターンの全URLを、web.xmlファイルを通して特定なサーブレットにマップできる点です。従ってGroovletとGSP実装を設定するための第一歩は、Webアプリケーション・コンテキストを定義し、それに関連したweb.xmlファイルを更新することです。このファイルには、特定なサーブレット・クラス定義と、それに対応したURLパターンが含まれます。

私はApache Jakarta Tomcatを使うことにし、groove というコンテキストを作りました。このディレクトリー・レイアウトを、リスト3に示します。

リスト3. Grooveコンテキストのディレクトリー・リスト
./groove:
drwxrwxrwx+   3 aglover  users        0 Jan 19 12:14 WEB-INF
./WEB-INF:
-rwxrwxrwx+   1 aglover  users      906 Jan 16 14:37 web.xml
drwxrwxrwx+   2 aglover  users        0 Jan 19 17:12 lib
./WEB-INF/lib:
-rwxrwxrwx+   1 aglover  users   832173 Jan 16 14:28 groovy-1.0-beta-9.jar
-rwxrwxrwx+   1 aglover  users    26337 Jan 16 14:29 asm-1.5.2.jar

WEB-INFディレクトリーには、少なくともリスト4に示す要素を持つweb.xmlファイルが必要です。

リスト4. 完全に構成されたweb.xmlファイル
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
    "http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
    <servlet>
      <servlet-name>GroovyServlet</servlet-name>
      <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>GroovyTemplate</servlet-name>
        <servlet-class>groovy.servlet.TemplateServlet</servlet-class>    
	</servlet>
    <servlet-mapping>
        <servlet-name>GroovyServlet</servlet-name>
        <url-pattern>*.groovy</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>GroovyTemplate</servlet-name>
        <url-pattern>*.gsp</url-pattern>
    </servlet-mapping>
</web-app>

上のweb.xmlファイルの中の定義で言っているのは、.groovy で終わるリクエスト(例えば、http://localhost:8080/groove/hello.groovy)はクラスgroovy.servlet.GroovyServlet に送られ、一方.gsp で終わるリクエストはクラスgroovy.servlet.TemplateServlet に行く、ということです。

次のステップは、libディレクトリーに2つのjar、つまりgroovyのディストリビューションjar(私の場合ではgroovy-1.0-beta-9.jar)と、それに対応するasm jar(groovy beta-9ではasm-1.5.2.jar)を置くことです。

そうです。それだけなのです。では次に進みましょう。


Groovletをお願いします

Groovyでは、クラス階層構造の継承に関する要求がほとんど無いため、Groovletを書くのは非常に簡単です。Groovletでは、javax.servlet.http.HttpServletjavax.servlet.GenericServlet 、あるいはGroovyServlet クラスのようなものから継承する必要がありません。実際、Groovletを作るのは、Groovyスクリプトを作るのと同じくらい単純なのです。クラスを作る必要すらありません。リスト5は私が書いた単純なGroovletですが、2つのこと、つまりHTMLを出力することと、次にこのGroovletが実行しているコンテナに関する幾つかの情報を提供する、という2つをします。

リスト5. Groovletを使い始める
println """
<html><head>
<title>Groovlets 101</title>
</head>
<body>
<p>
Welcome to Groovlets 101. As you can see
this Groovlet is fairly simple.
</p>
<p>
This course is being run on the following servlet container: </br>
${application.getServerInfo()}
</p>
</body>
</html>
"""

このGroovletをブラウザーで見ると、図1のようなものになるはずです。

図1. 単純なGroovletからの出力
図1. 単純なGroovletからの出力

リスト5のGroovletをよく見てみると、皆さんは最初にGroovyスクリプトを書き始めた頃を思い出すでのではないでしょうか。まず、main メソッドやクラス定義が何も無く、単純なコードが幾つかあるだけです。さらにGroovletフレームワークは、ServletRequestServletResponseServletContextHttpSessionなどのインスタンス変数を暗示的に提供しています。application 変数によって、どのようにServletContext のインスタンスを参照できたかが分かるでしょうか。HttpSession のインスタンスを捉えたいと思ったら、session 変数名を使います。同様に、ServletRequestServletResponse にはそれぞれ、requestresponse を使います。


診断Groovlet

Groovletを書くのはGroovyスクリプトを作るのと同じくらい単純なだけではなく、ファンクションをdef キーワードで定義し、Groovlet内で直接呼ぶこともできるのです。これを説明するために、Webアプリケーションの診断チェックを行う、ちょっと難しいGroovletを作ることにします。

皆さんが、世界中の様々な顧客が購入するWebアプリケーションを書いたと考えてみてください。巨大な顧客ベースがあり、このアプリケーションをリリースしてからしばらく時間が経っています。過去のサポート経験から分かっていることは、JVMバージョンやORM(object-relational mapping)が正しくないことに起因する問題によって、パニック状態に陥った顧客からの問い合わせが殺到することです。

皆さんは非常に忙しいので、私にその解決手段を求めます。私はGroovletを使うことによって、VMのバージョンを確認し、Hibernateセッション(参考文献)を作る単純な診断スクリプトを、素早く書けるのです。まず、2つのファンクションを作り、ブラウザーがこのスクリプトをヒットした時に、これらのファンクションを呼ぶようにします。この診断Groovletはリスト6のように定義します。

リスト6. 診断Groovlet
import com.vanward.resource.hibernate.factory.DefaultHibernateSessionFactory
/**
 * Tests VM version from environment- note, even 1.5 will
 * cause an assertion error.
 */
def testVMVersion(){
  println "<h3>JVM Version Check: </h3>"
  vers = System.getProperty("java.version")
  assert vers.startsWith("1.4"): "JVM must be at least 1.4"
  println "<p>JVM version: ${vers} </p>"
}
/**
 * Attempts to create an instance of a hibernate session. If this
 * works we have a connection to a database; additionally, we 
 * have a properly configured hibernate instance.
 */
def testHibernate(){
  println "<h3>Hibernate Configuration Check: </h3>"
  try{
    sessFactory = DefaultHibernateSessionFactory.getInstance()
    session = sessFactory.getHibernateSession()
    assert session != null: "Unable to create hibernate session. 
    Session was null"
    println "<p>Hibernate configuration check was successful</p>"
  }catch(Throwable tr){
    println """
    <p>Unable to create hibernate session. Exception type is: <br/>
    <i>${tr.toString()} </i><br/>		
    </p>
    """
  }   
}
println """
<html><head>
<title>Diagnostics Check</title></head>
<body>
"""
testVMVersion()
testHibernate()
println """
</body></html>
"""

このGroovletの検証ロジックは非常に単純なものですが、ここで必要なことには十分です。単純にこの診断スクリプトをWebアプリケーションに同梱しておき、皆さんのカスタマー・サポート窓口が電話を受けた時に、顧客に対してブラウザーでDiagnostics.groovy スクリプトを指すように指示し、その結果がどうかなるかを知らせるように顧客に伝えればよいのです。この結果は、図2に示すもののようになるはずです。

図2. 診断Groovletからの出力
図2. 診断Groovletからの出力

では、GSPはどうなのか

ここまでは、Groovletを書くことに焦点を当ててきました。しかし、これから先を読めば分かると思いますが、JSPがサーブレットAPIを補完するのと同じように、GroovyのGSPページはGroovletフレームワークをうまく補完するのです。

GSPは表面的にはJSPとよく似ていますが、GSPフレームワークは実際にはテンプレート・エンジンなので、両者は全くと言ってよいほど異なるのです。テンプレート・エンジンがよく分からない方は、前回の記事を読んでみてください。

GSPとJSPは基本的に異なる技術ですが、Webアプリケーションのビューを具体化する手段としてGSPは素晴らしい、という点では似ていると言うことができます。前回の記事を思い出してもらえば分かると思いますが、ビューを具体化する技術を使うと、アプリケーションのビジネス・ロジックにまつわる懸念事項と、そのロジックに対応したビューを分離できるのです。リスト6 の診断Groovletをちょっと見直してみれば、どの部分でGSPコードを使えば改善できるかが、皆さんにも分かるのではないでしょうか。

そうです。あのGroovletはちょっと見苦しいですよね。アプリケーション・ロジックと、HTMLを出力するためのprintln の負荷が混在していることが問題なのです。幸い、このGroovletを補完する単純なGSPを作ることで、この問題を解決できるのです。

GSPの例

GSPを作るのは、Groovletを作るのと同じくらい簡単です。GSP開発の鍵は、GSPが本質的にテンプレートであること、従ってロジックを限定した方が効果的だということを意識することです。まず、リスト7に示すような、単純なGSPを作ります。

リスト7. 単純なGSP
<html>
<head><title>index.gsp</title></head>
<body>
<b><% println "hello gsp" %></b>
<p>
<% wrd = "Groovy"
   for (i in wrd){ %>
 <h1> <%=i%> <br/>
   <%} %>
</p>
</body>
</html>

皆さんは上のGSPを見ると、標準的なGroovy開発を思い出すに違いありません。構文は、<% の使い方などはJSP的ですが、Groovletフレームワークと同じように、ServletRequestServletResponseServletContext 、それにHttpSession オブジェクトなど、一般的なサーブレット・オブジェクトにアクセスできるようになっているのです。


これをリファクターしてください・・・

プログラミング言語やプラットフォームに関して経験するにつれ、昔のコードをリファクターすることによって多くを学べるものです。ここでは、以前のコラム で取り上げた、単純なレポート・アプリケーションを再度取り上げることにします(この時は、単にGroovySqlについてのみ学びました)。

思い出して欲しいのですが、その記事の中では、組織の中で様々な使い方ができるようなレポート・アプリケーションを、手軽な、しかし美しくはない手法で構築しました。その後、このアプリケーションは、会社のデータベースに対する活動を調べるためによく使われるものとなりました。さて、技術者でない人達が、この巨大なレポートにアクセスしたいと思っているのですが、彼らはそのために自分たちのマシンにGroovyをインストールするというオーバーヘッドは望んでいません。

私はこうしたことが起きることをある程度予測しており、その解決方法は現実的に 明白でした。このレポート・アプリケーションを、Webで使用できるようにすればよいのです。私には幸いなことに、リファクタリングが一瞬で可能な、GroovletとGSPがあるのです。

レポート・アプリケーションをリファクターする

GroovySqlに関する記事のリスト12 の中身を相手にしましょう。これをリファクターするのは簡単です。全てのprintln を、setAttribute() メソッドを使ってHttpRequest オブジェクトの中にインスタンス変数を置くロジックで置き換えるだけです。

次のステップは、RequestDispatcher を使って、レポート・アプリケーションのビュー・コンポーネントを処理するGSPにrequest を転送することです。この新しいレポートGroovletは、リスト8のように定義されます。

リスト8. リファクターされた、データベースをレポートするアプリケーション
import groovy.sql.Sql
/**
 * forwards to passed in page
 */
def forward(page, req, res){
  dis = req.getRequestDispatcher(page);
  dis.forward(req, res);
}
sql = Sql.newInstance("jdbc:mysql://yourserver.anywhere/tiger", "scott",
        "tiger", "org.gjt.mm.mysql.Driver")
   
uptime = null
questions = null
insertnum = null
selectnum = null
updatenum = null
sql.eachRow("show status"){ status |
  if(status.variable_name == "Uptime"){
         uptime =  status[1]
	 request.setAttribute("uptime", uptime)
  }else if (status.variable_name == "Questions"){
         questions =  status[1]
	 request.setAttribute("questions", questions)
  }
}
request.setAttribute("qpm", Integer.valueOf(questions) / 
Integer.valueOf(uptime) )
sql.eachRow("show status like 'Com_%'"){ status |
    if(status.variable_name == "Com_insert"){
         insertnum =  Integer.valueOf(status[1])
    }else if (status.variable_name == "Com_select"){
         selectnum =  Integer.valueOf(status[1])
    }else if (status.variable_name == "Com_update"){
          updatenum =  Integer.valueOf(status[1])
    }
}
request.setAttribute("qinsert", 100 * (insertnum / Integer.valueOf(uptime)))
request.setAttribute("qselect", 100 * (selectnum / Integer.valueOf(uptime)))
request.setAttribute("qupdate", 100 * (updatenum / Integer.valueOf(uptime)))
forward("mysqlreport.gsp", request, response)

リスト8のコードは、皆さんにもおなじみのはずです。前のアプリケーションのprintln を全て置き換え、レポートのビュー部分を処理するforward ファンクションを追加しただけです。

ビュー・コンポーネントを追加する

次のステップは、レポート・アプリケーションのビューを処理するGSPを作ることです。私は芸術家ではなくエンジニアなので、私が作るビューは単純なものです。リスト9に示すように、テーブルを持った、ちょっとしたHTMLです。

リスト9. レポートのビュー・コンポーネント
<html><head>
<title>MySql Health Report</title>
</head>
<body>
<table>
<tr>
  <td>Database Uptime:</td><td><% println
  "${request.getAttribute("uptime")}" %></td>
</tr>
<tr>
  <td>Number of Queries:</td><td><% println
  "${request.getAttribute("questions")}" %></td>
</tr>
<tr>
  <td>Queries per Minute =</td><td><% println
  "${request.getAttribute("qpm")}" %></td>
</tr>
<tr>
  <td>% Queries Inserts =</td><td><% println
  "${request.getAttribute("qinsert")}" %></td>
</tr>
<tr>
  <td>% Queries Selects =</td><td><% println
  "${request.getAttribute("qselect")}" %></td>
</tr>
<tr>
  <td>% Queries Updates =</td><td><% println
  "${request.getAttribute("qupdate")}" %></td>
</tr>
</table>
</body>
</html>

この新しいレポートを実行すると、結果は図3に示すような出力となります。もちろん、数字は異なっている可能性があります。

図3. リファクターされたレポート・アプリケーションからの出力
図3. リファクターされたレポート・アプリケーションからの出力

まとめ

ご覧の通り、サーバー側開発で要求される機能が非常に単純であり、すぐに作れるようなものの場合には、明らかにGroovletとGSPを選ぶべきでしょう。どちらのフレームワークも非常に柔軟であり、コードを書いてから結果を見るまでの時間の短さでは、他に匹敵するものがありません。

ただし、GroovletはStrutsの置き換えではないことは強調しておきたいと思います。GSPフレームワークは、Velocityと直接 に競合するようなものではありません。GroovySqlはHibernateの置き換えではなく、GroovyはJava言語の置き換えではないのです。

どの場合においても、これらの技術は補完的なものであり、即時対応の開発用には多くの場合、Groovyを使った方が単純だということです。JDBCを直接使う代わりにGroovySqlが使えるのと同様に、GroovletとGSPは実際的な場面で、直接サーブレットAPIを使う代わりとなりうるのです。

次回は、GroovyMarkupの世界に入り込むことにします。

参考文献

  • 実用的なGroovy シリーズの他の記事もぜひ読んでください。シリーズの進行と共に、それぞれの記事がお互いに関係してきます。
  • HibernateとGroovysqlの関係は、VelocityとGSPフレームワーク、Java言語とGroovyの関係と同じです。要は役割に適したツールを選ぶということです。Hibernateについて詳しくは、developerWorksのチュートリアル、Hibernateで継承マッピングを単純化する(developerWorks, 2004年12月)を見てください。
  • Velocityについて学ぶために、Sing LiによるClient and server-side templating with Velocity (developerWorks, 2004年2月)を見てください。
  • Malcolm DavisによるStruts、オープン・ソースMVC実装 (developerWorks, 2001年2月)は、Strutsを紹介した記事として、今でも最高のものの一つです。
  • Groovyは、Groovy open source project page からダウンロードすることができます。このサイトでは、コンパイルやユニット・テスト、正規表現などについても学ぶことができます。
  • developerWorksのJava technologyゾーン には、Javaプログラミングのあらゆる面に関する記事が豊富に用意されています。
  • また、developerWorksJava technology zone tutorials には、Javaに焦点を当てた無料チュートリアルの完全なリストがありますので、ご覧ください。
  • The Developer Bookstore には、Java関連の書籍 を初め、広範な話題を網羅した技術書が豊富に取り揃えられていますので、ぜひご覧ください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218877
ArticleTitle=実用的なGroovy: Groovyでサーバー側に対応する
publish-date=03152005