G*workshop sendai 20100424(v2)

Preview:

DESCRIPTION

 

Citation preview

はじめての Groovy@G* ワークショップ in 仙台

2010/4/24日本アイ・ビー・エム ( 株 )  須江 信洋

nsue@e-mail.ne.jphttp://twitter.com/nobusue

※資料の内容は個人としての意見・見解を述べたものであり、所属する企業・組織が内容を保証するものではありません。

自己紹介 須江 信洋(すえ のぶひろ) 1970 年生まれの 40 才 ずっと JavaEE 関連の仕事をしています

職場は何回か変わってます。。。 G* との関わり

Groovy を組み込んだ製品 (WebSphere sMash) を売ってます

JGGUG 平会員 「 Groovy イン・アクション」翻訳メンバーの一味 「 Groovy 入門(仮称)」執筆中です。。。

2

Groovy とは?

3

4

Groovy とは

Who/When2003 年 8 月 James Strachan, Bob McWhirter

What JVM 上で稼動するオブジェクト指向の動的スクリプト言語Ruby, Python, Dylan および Smalltalk からの影響

Why Groovy? Java とほぼ同じ文法、 Java との親和性の高さ

Jython(Python) / (J)Ruby のように新規の API を学ぶ必要がないJava 開発者が入りやすい動的スクリプト言語

公式ページ:  http://groovy.codehaus.org/API :  http://groovy.codehaus.org/gapi/GDK : http://groovy.codehaus.org/groovy-jdk/

"Groovy is designed to help you get things done on the Java platform in a quicker, more concise and fun way - bringing the power of Python and Ruby inside the Java Platform."

Feature Rich

Java

Frie

ndly

otherother

otherother

Groovy

5

Javaフレンドリーな Groovy

Java とのシームレスな統合 JVM 上で動作し、 JRE を利用するスクリプトから Java クラスの生成が可能Groovy = Java + 便利な拡張機能

Java とほぼ同じ文法Groovy のクラスはそのまま Java のクラス Java の上位互換、冗長性を排除した文法

Groovy による Java の拡張言語仕様Groovy 独自のライブラリー既存 Java クラスへの機能追加 (GDK)

Groovy コード Java コード

JRE

JVM

GDK

ライブラリー

言語仕様

簡単・コンパクト・読みやすいコード!

Java 屋におすすめ、 Groovy Java からの段階的な移行が可能

Java のクラスライブラリがそのまま使える Java の文法がほぼそのまま使える Java のノウハウ (Ant,JUnit, ・・ ) がほぼそのまま使える

硬軟どちらにも対応できる オプショナルタイピング(必要なとこだけ型を指定する) Java とのシームレスな統合 動的プログラミング(クロージャ、 MOP 、・・) 動的クラス生成(コンパイルするタイミングを選択でき

る) ⇒ 日々の作業からアプリ開発まで、幅広く使えます ⇒ Java の API を対話的に確認するのにも有用

6

Groovy はじめの一歩

7

まずはインストール (Windows の場合 ) groovy.codehaus.org から取得したアーカイブ

(groovy-binary-1.7.x.zip) を展開 zip ファイル内の groovy-1.7.x ディレクトリを、適当なディレクトリ以下

(c:\groovy など ) に展開する 環境変数 GROOVY_HOME の設定

「スタート」 - 「コントロールパネル」 - 「システム」 「詳細設定」タブを選択し、「環境設定」をクリック “ システム環境変数”の「新規」をクリックし、以下を設定

変数名: GROOVY_HOME 変数値: c:\groovy\groovy-1.7.x ※各自の環境に合わせて

パスの設定 “ システム環境変数”から変数名” PATH” を選択し、「編集」をクリック

変数値の先頭に、「 %GROOVY_HOME%\bin; 」 を追加する

コマンドプロンプトから「 groovy –v 」を実行し、正常に起動することを確認8

当然ですが、 JDK(1.5 以上 ) も入れてお

いてね

Groovy スクリプトを実行する コマンド

groovy Hoge[.groovy] ( 拡張子が .groovy なら省略可 )

単発で Groovy スクリプトを実行する Groovy シェル

groovysh コマンドライン版対話型シェル

Groovy コンソール groovyConsole GUI版シェル

( SwingBuilder で構築されている)

9

まずは "Hello world"

class HelloWorld { public static void main(String[] args){ System.out.println "Hello world"; }}

10

[Java]

println 'Hello world'

[Groovy]定型的なコード (boilerplate code) を排除して、本質的な処理のみを記述すればよい

groovysh 使用上の注意 シェルの中で変数を定義するとき

× def x=‘hogehoge’

◎ x=‘hogehoge’

※x がスクリプトの binding に格納されるため、期待通りに動作する。 興味のある人は GINA の 11.2.2節を参照のこと。

11

その他 IDE を使う

Eclipse Groovy plug-in Groovy のコードアシスト+デバッグ、 V2.0 になってかなりまとも

に NetBeans

Grails をけっこう手厚く支援 Groovy はそれなり・・・

IntelliJ IDEA Groovy/Grailsサポートの老舗 通好みらしいです ⇒詳しくは、いまいさんに・・・

Windows の人は、 Installer版を使うと楽です .groovy をダブルクリックするだけで実行できるようになる Gant のビルドファイルを gant コマンドに関連付けてくれる、

など12

Object Browser 最後に評価したオブジェクトのプロパティ /メソッ

ドを参照できる groovysh: inspect コマンド groovyConsole: Ctrl+I or“Script - Inspect Last”

13

困った時は・・・ オブジェクトインスタンスのクラス名

obj.class.name オブジェクトのプロパティ /メソッド /フィールド

一覧 obj.properties obj.class.methods obj.class.fields

オブジェクトの文字列表現 obj.dump()

14

Groovy 基本の 'き '

15

Groovy :これだけおさえといて! コードとコメントの表記

基本的には Java と同じ 一行コメントは // 、複数行コメントは /* .. */ 行末の ; (セミコロン ) は不要だが、あっても良い メソッドからの戻り値指定 (return) はオプション(省略時は最後の式が戻り値になる)

構文 以下は、 Java とほぼ同等のものが使える

package 機構、文 クラスとメソッド定義(ただし、インナークラスは Groovy-1.7 でサポート) 制御構造( for ループは拡張されている ) 演算子、式、代入、例外処理 オブジェクトのインスタンス生成、参照、メソッド呼び出し

以下は Groovy が拡張しているもの オブジェクトアクセスの簡素化(添字演算子、 GPath など) リテラルの拡張(リスト、マップ、名前付きパラメータ) 新しいデータ型(範囲、クロージャなど) すべてをオブジェクトとして扱う

16

Java と Groovy の違い return キーワードはオプション

省略時には最後に評価した式が戻り値になる クラス /メソッド /フィールドはデフォルトで

public プロパティアクセス時、 private は無視される

インナークラスは Groovy-1.7 以降でサポート Java のインナークラスと完全互換ではないので注意 クロージャで代替できる場合が多い

チェック例外はない すべて非チェック例外として処理

「 a==b 」は同値性「 a.eqauls(b) 」の意味 オブジェクトの同一性を判定したい場合は、「 a.is(b) 」

17

クラス宣言 Groovy のクラス宣言は、 Java とほぼ同等

ただし、 Java のパッケージ可視性 ( 修飾子なし ) はサポートされない

以下のパッケージは自動的に import される groovy.lang.*, groovy.util.*, java.lang.*, java.util.*,

java.net.*, java.io.*, java.math.BigInteger, java.math.BigDecimal

class Book {private String titlevoid setTitle(String theTitle) {

title = theTitle}String getTitle(){

return title} }

定型的なコード(boilerplate code)

18

GroovyBean GroovyBean の特徴

可視性が設定されていないフィールドはプロパティとみなされ、 setter/getter が自動生成される

bean.prop および bean[prop] は、 setter/getter に変換される

class Book {String title

}

def aBook = new Book()aBook.setTitle('Groovy Recipes')assert aBook.getTitle() == 'Groovy Recipes'aBook.title = 'Groovy in Action'assert aBook.title == 'Groovy in Action'aBook['title'] = 'Groovy in Action'assert aBook['title'] == 'Groovy in Action'

前頁と同じクラスの定義

スクリプト

19

拡張された文字列リテラル 文字列リテラルの表記

def name = 'Bob'def greeting = "Hello, ${name}"def pattern = /\d.\d/

def multiLineString = '''Hello, worldMy name is Groovy.'''

def multiLineGString = """Hello, ${name}My name is Groovy."""

String

GString

GString(\ のエスケープ不要 )

複数行 String

複数行 GString

20

Java のプリミティブ型(非オブジェクト) Java では、以下の型はオブジェクトではない

boolean, byte, short, int, long, float, double, char プリミティブ型の特徴

Java の演算子 (+,-,*,/ など ) による演算ができる コレクションフレームワーク (List, Map など ) へ格納できない

ラッパークラスを利用 オブジェクト型の特徴

Java の演算子 (+,-,*,/ など ) による演算ができない 演算はメソッドとして実装する必要がある (cf. BigInteger) 例外: String(Java では特別扱い )

コレクションフレームワーク (List, Map など ) へ格納できる Autoboxing (Java 5~ )

必要なコンテキストでは暗黙の変換が行われるようになった Ease of Development の実現、 .NET などの影響

Groovy では、プリミティブ型はすべてラッパークラス(オブジェクト)として処理される21

数値とオブジェクト Groovy では、数値もすべてオブジェクトとして扱われる 演算はメソッド呼び出しに変換される

メソッドをオーバーライドすることで、独自の演算が定義可能

def x = 1def y = 2assert x + y == 3assert x.plus(y) == 3assert x instanceof Integer

22

Groovy の数値リテラル

データ型 リテラルの接尾辞java.lang.Integer (int範囲内の無印整数 )

例) 15, 0x1234ffff

java.lang.Long L,l

java.lang.Float F,f

java.lang.Double D,d

java.math.BigInteger G,g,(int範囲外の無印整数 )

java.math.BigDecimal

G,g,(無印小数 ),( 指数表記 )例) 1.23, 1.4E4, 2.8e4, 1.23g

23

Groovyツアー

24

•オブジェクトとコレクション操作•制御構造•GDK•演算子オーバーライドとCategory•XML操作

オブジェクトとコレクション操作 オブジェクトナビゲーション

GPath Safe dereference

拡張されたリテラルのサポート List Map 範囲 (Range 型、新規導入 )⇒ 利用頻度が高い割に、 Java ではサポートが弱い

クロージャ Expando Metaclass

25

GPath GPath

XPath のような簡易な表記で、オブジェクトツリー階層を辿れる obj.a.b.c => obj.getA().getB().getC()

途中にコレクションが含まれても、そのまま扱える 例)カレントオブジェクトの持つ全ての getter メソッドをソートして出力 this.class.methods.name.grep(~/get.*/).sort()

類似の技術 XPath

/document/order/lineItem[1] <document> 要素の下の、 <order> 要素の下の、 1 番目の <lineItem>

要素 JavaScript のオブジェクト参照

document.form.text1.value 現在のブラウザウィンドウの、フォームの、 text1 要素の内容

26

GPath と safe dereferencing operator

safe dereferencing operator(安全な参照演算子 ) : 「 ?. 」 GPath を辿る途中で null が含まれる可能性がある場合に利用

Java に比べ、シンプルな表記で NPE を回避することができる 直前のオブジェクトが null なら評価を中断し、結果として null を返

す obj?.a?.b?.c => if(obj!=null) obj.getA() else null ....

27

List List 型を言語レベルでサポート

リテラル表記が可能 添字演算子 ([ ]) によるアクセスが可能

動的に拡張可能

def roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII']assert roman[4] == 'IV'roman[8] = 'VIII‘roman << ‘IX’assert roman.size() == 10

28

def list1 = []def list2 = [] as LinkedList

空の List(java.util.ArrayList)

空の List(実装を指定 )

List の便利メソッド

29

assert [1,[2,3]].flatten() == [1,2,3]assert [1,2,3].intersect([4,3,1]) == [3,1]

assert [3,1,2].sort() == [1,2,3]

def list = [ [1,0], [0,1,2], [2,1,0] ]list = list.sort { a,b -> a[0] <=> b[0] }assert list = [ [0,1,2], [1,0], [2,1,0] ]

def doubled = [1,2,3].collect { it*2 }assert doubled == [2,4,6]

assert [1,2,3].join(‘-’) == ‘1-2-3’

Map Map 型を言語レベルでサポート

リテラル表記が可能 添字演算子 ([ ]) によるアクセスが可能

def http = [100 : 'CONTINUE',200 : 'OK',400 : 'BAD REQUEST' ]

assert http[200] == 'OK'

http[500] = 'INTERNAL SERVER ERROR'assert http.size() == 4

30

def map1 = [:]def list2 = [:] as TreeMap ← new TreeMap() と等価

空の Map(java.util.HashMap)

範囲 範囲 (Range 型 ) を言語レベルでサポート

対応する概念は Java に存在しない リテラル表記が可能 クロージャとの組合せでよく使われる

def x = 1..10assert x.contains(5)assert x.contains(15) == falseassert x.from == 1assert x.to == 10assert x.reverse() == 10..1

def y = 1..<10assert y.contains(10) == false

31

境界値を含む

境界値を含まない

オブジェクトとしてのコード:クロージャ コードフラグメントを First Class Object として扱える

Ruby のブロック、 JavaScript の関数オブジェクトに相当 ロジックの再利用や動的なディスパッチに有効 宣言時の環境( Lexical Scope) を保持している

log = ''(1..10).each{ log += it }assert log == '12345678910'

map = ['a':1, 'b':2]def doubler = {key, value -> map[key] = value * 2}map.each(doubler) assert map == ['a':2, 'b':4]

クロージャの引数が 1つのときは、 it で省略可能

クロージャを変数に代入可能32

クロージャの例

p [1,2,3,4,5].select {|i| i%2==0 }Ruby

println ([1,2,3,4,5].findAll {it%2==0 } )Groovy

「 1,2,3,4,5 」という数字の配列から、偶数を抜き出せ

“動詞 (find)+目的語 (All)” のJava のメソッド表記ルールに準拠

it : クロージャー内イテレータのデフォルト定義

33

Expando – 動的に拡張可能なオブジェクト

def obj = new Expando()obj.hello = {println "Hello"}obj.hello()    // Hello

obj.param = "world"println obj.param // world

インスタンス生成後にメソッドやフィールドの追加が可能

⇒ Ruby のオープンクラスや、JavaScript のプロトタイプベース・オブジェクト指向に近い機能

34

制御構造 Groovy の真偽判定正規表現 拡張された switch case

35

Groovy の真偽判定 拡張された真偽判定

Boolean 式による判定 (Java と同じ ) Matcher はマッチしなければ false 、マッチすれば true コレクション /マップは空なら false 、それ以外は true 文字列は空文字列なら false 、それ以外は true 数値はゼロなら false 、それ以外は true オブジェクトは null なら false 、それ以外は true

利用可能な制御構文 if ( 式 ) then ... else while ( 式 ) { ... } for (elem in 範囲 ) { ... }, for (elem in リスト ) { ... } コレクション .each() { ... } switch( 式 ) { case c1: ... ; break; case c2: ... ; break;

deafult: ... }36

制御構造の例

if (null) {assert false

} else {assert true

}

def i = 0while (i < 10) {

i++}assert i == 10

def clinks = 0for (remainingGuests in 0..9) {

clinks += remainingGuests}assert clinks == (10*9)/2

def list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]for (j in list) {

assert j == list[j]}list.each() { item ->

assert item == list[item]}

37

正規表現 正規表現を手軽に使うために、専用の演算子を導入

=~ : 正規表現検索演算子 正規表現に対する部分一致の判定 式として評価すると、 java.util.regex.Matcher を返す

==~ : 正規表現マッチ演算子 正規表現に対する全体一致の判定 式として評価すると、 Boolean を返す

~ 文字列 : 正規表現パターン 文字列をパターンとして扱う( ~/pattern/ の書式を推奨 ) 式として評価すると、 java.util.regex.Pattern を返す

38

正規表現の例

39

def twister = 'she sells sea shells at the sea shore of Seychelles'

assert twister =~ /s.a/assert (twister ==~ /s.a/) == falseassert twister ==~ /\w+( \w+)*/

def words = twister.split(/ /)assert words.size() == 10

switch('bear'){case ~/..../ : assert truecase ~/b.*/ : assert true; breakdefault : assert false

}

def animals = ['cat', 'dog', 'bear', 'wolf', 'tiger', 'elephant']assert animals.grep(~/.../) == ['cat', 'dog']

実用的に拡張された switch ~ case case に多様な条件式を表記できる

40

def x = 1.23def result = ''

switch (x) {case "foo" : result = "found foo"case "bar" : result += "bar"; breakcase [4,5,6,'inList'] : result = "list"; breakcase 12..30 : result = "range"; breakcase Integer : result = "integer"; breakcase Number : result = "number"; breakdefault: result = "default"

}println result

GDK: JDK のクラスを勝手に拡張 Groovy のメタプログラミング機能を利用して、

JDK のクラスが本来持っていないメソッドを追加

41

new File('sample.txt').eachLine{ line ->if( line.size() > 4 ) println line

}

ファイルを 1 行づつ読み込んで、

文字数が4以上の行を表示

java.io.File にメソッドeachLine(Closure) を追加

演算子オーバーライドと Category Groovy では、すべての演算子はメソッド

メソッドをオーバーライドすることで、挙動を変えられる http://groovy.codehaus.org/Operator+Overloading

通常はデフォルトの挙動で、特定の箇所のみ演算子の挙動を変えたい場合には Category を使う Category (≒mixin)

任意個の static メソッドを持つ(=カテゴリメソッド) use メソッドで、指定した範囲内のみカテゴリメソッドを有効にすることができる カンマ区切りで複数個の Category を差し込むこともできる

既存のメソッドのオーバーライドも可能42

Category の例:日付操作 org.codehaus.groovy.runtime.TimeCategory

Date の plus()/minus() をオーバーライド Integer に getHours()/getMinutes()等を追加

例:現在時刻の 9 時間 5 分後を求める

43

def date = new Date()use(org.codehaus.groovy.runtime.TimeCategory){

date = date + 9.hours + 5.minutes}

⇒ Java では???

XML 操作 XML の読み込みには、以下の方法が使える

Groovy の XmlParser XML全体をメモリー上にオブジェクト化

ノードの内部表現は groovy.util.Node

Groovy の XmlSlurper XML をオンデマンドでオブジェクト化( SAX よりもメモリ消費は多い) ノードの内部表現は GPathResult

Java の DOM Groovy 独自のヘルパーを提供

その他、 Java の SAX/StAX や JDOM などもそのまま使える

44

def plan = new XmlParser().parse(new File('plan.xml'))println plan.name()                 // planprintln plan.week[0].name()           // weekprintln plan.week[0].task[0].name() // taskprintln plan.week[0].task[0].‘@title‘ // ProjectZero をダウンロードする

XmlParser

<?xml version="1.0" encoding="Shift_JIS" ?> <plan>  <week capacity="8">    <task done="1" cost="2" title="ProjectZero をダウンロードする " />    <task done="1" cost="3" title="Groovy プロジェクトを作成する " /> </week> <week capacity="8">    <task done=“0” cost=“1” title=“テストケース作成 " />    <task done=“0” cost=“3” title=“テスト実施する " /> </week> </plan>

plan.xml

Groovy (GPath による Xpath の様なパス式 )

45

<tasks project=‘2'> <title>Install ProjectZero</title> <title>Make Groovy project</title> <title>Code it</title></tasks>groovy.xml.MarkupBuilder@120cc56

XmlBuilder

def builder = new groovy.xml.MarkupBuilder();builder.tasks([project:2]) {

title("Install ProjectZero")title("Make Groovy project")title("Code it")

}println builder

Groovy

実行結果

46

Groovy お得情報

47

•手軽に Groovy を試す•ライブラリの配布を楽にする•ビルドを便利にする•テストを便利にする•Google AppEngine をサクッと使う•Groovy の起動を早くする•Groovy のパフォーマンスを改善する

手軽に Groovy を試す Groovy をカジュアルに使いたい!!

面白そうだけど、いちいちインストールするのは面倒だな・・・

知り合いに紹介したいんだけど、手軽にデモするには?

そんなあなたにお勧め: Groovy web console ブラウザだけで Groovy のコードが実行できます!!

実体は Google AppEngine 上のアプリケーション 本家: http://groovyconsole.appspot.com/ JGGUG版 : http://jggugcode.appspot.com/

48

Groovy web console(JGGUG ver.)

49

ライブラリの配布を楽にする :Grape Grape(@Grab) で、 maven リポジトリから jar を自動

取得 Groovy-1.6 の新機能( 1.7 でさらに改善) 内部では Apache Ivy を利用

50

@Grab(group = 'net.homeip.yusuke', module='twitter4j', version='*')

import twitter4j.Twitter

Twitter twitter = new Twitter("<user>","<pass>")twitter.update(args[0])

Twitter4j を使った Twitter クライアントこのファイルだけで動きます!!( 要 Groovy-1.7 以上 )

ビルドを便利にする AntBuilder

Groovy のディストリビューションに含まれている Groovy プログラムの中から、 Ant タスクを呼べる ビルドの手続きをクロージャで書ける

Gant Ant のビルド定義を Groovy で置き換え

(build.xml⇒ build.groovy) 別途インストールが必要

Gradle / GMaven Maven のビルド定義を Groovy で置き換え 別途インストールが必要

51

テストを楽にする : Power Assert 要するに、「超親切な Assert 」

Groovy-1.7 の新機能 Spock(BDD フレームワーク ) から取り込まれた機能

52

def a = 1def b = 2def c = 3

assert (a+b)*c == 5

途中結果や、どこで fail したかまで教えてくれる。assertEquals() とかを組み合わせる必要ナシ。

GAEJ をサクッと使う : Gaelyk Google AppEngine に手っ取り早くアプリを置き

たい人に Groovy でロジックを書いて Groovy Template で View を書いて appcfg update war 、で OK

GAEJ の API をラップして、簡単に使えるようになってます 詳細はこちらを:

http://dl.dropbox.com/u/132573/Gaelyk-Tutorial.html

53

Groovy の起動を早くする : GroovyServ Groovy が便利なのは分かったけど、 JavaVM の起

動に時間がかかるから、気軽に使えないよね? という声にお応えして!!

kobo project(JGGUG 上原さん・中野さん ) が開発 Groovy を実行するための JavaVM を常駐し、

クライアント・サーバーモデルでスクリプトを実行 JavaVM起動時のオーバーヘッドを解消し、圧倒的なサクサク感を提供

Groovy コミッタの Paul King さんも使ってるとか http://kobo.github.com/groovyserv/

54

論より証拠。デモをご覧ください。

Groovy のパフォーマンスを改善する Groovy が便利なのは分かったけど、動的型を扱う

からパフォーマンスは Java に負けるよね? パフォーマンスが要求されるところは Java で書けばよ

い!Groovy は Java のクラスをシームレスに呼び出せる。

とは言っても、全部 Groovy で書けたら楽だよね。。。。

という声にお応えして!! Groovy++(Groovy Booster) が登場

http://code.google.com/p/groovypptest/ @Typed を付けるだけで、 Java相当の静的型に変身

http://d.hatena.ne.jp/uehaj/20100225/1267106256 今のところ Groovy本体とは別に開発されているが、将来的には取り込まれるかもです。

55

まとめ

56

Groovy 、面白いのでとにかく使ってみて! 「はじめての Groovy 」にしては、ちょっと濃すぎた? いえいえ、今日の内容ではまだまだ上っ面をなめただ

けです もっと濃い話が聞きたい方は、

http://d.hatena.ne.jp/uehaj を購読すべし が、 Groovy の面白さは

一通りお伝えしたつもりです

Java に限界を感じているエンジニア諸兄に 近頃話題の彼女( Scala たんや Ruby たん)も

いいけど・・・ 近くにいる娘( Groovy たん)とも付き合ってみると、

意外な発見があるかも・・・57

さて、ここで問題です。 [ 問題 1] Groovy で def a=1 と書いた場合、a の型はなんでしょう? (a) int (b) java.lang.Integer (c) java.lang.BigInteger

[ 問題 2] Groovy で a,b が BigInteger 型の場合、a - b と書くと何が起こるでしょう? (a) コンパイルエラー (b) 実行時エラー (c) a.minus(b) が実行される

58

Groovy の情報源

日本語書籍「 Groovy イン・アクション」 毎日コミュニケーションズより発売中 Groovy の基礎から活用まで、幅広い話題を網羅

Groovy本既刊(英語版) "Groovy in Action" (Manning)

Groovy のバイブル本。 "Programming Groovy" (Pragmatic Bookshelf)初心者向けの入門書。 Groovy-1.5ベース

"Groovy Recipe" (Pragmatic Bookshelf)具体的なコードサンプル集。 Groovy-1.5ベース

コミュニティ 日本 Grails/Groovyユーザーグループ

http://www.jggug.org/ G*ワークショップやってます。気軽にご参加ください。

59

Let’s get groovy!

60

Recommended