Groovy/Grails (12-14) 2008/2009

Z JASR Wiki

Spis treści

Wprowadzenie

Groovy jest to dynamiczny język programowania, działający na JVM i bardzo dobrze integrujący się z Javą (dzięki temu, że jest kompilowany do postaci bytecode). Grails jest to framework wzorowany na Ruby on Rails, służący do budowy aplikacji webowych opartych na modelu MVC (model-widok-kontroler), wykorzystujący m.in. Groovy, Spring Framework, Hibernate.

Groovy

Jest to obiektowy język dla platformy Javy, który może stanowić alternatywę dla języka Java. Jest to język dynamiczny, posiadający wiele cech podobnych do tych z Pythona, Ruby, Perla i Smalltalka. Może być wykorzystywany jako język skryptowy dla platformy Javy.

Groovy wykorzystuje składnię podobną do Javy, kod jest kompilowany do bytecodu Wirtualnej Maszyny Javy. Groovy może współpracować z kodem oraz bibliotekami pisanymi w Javie. Większość kodu napisanego w Javie jest składniowo zgodna z Groovym.

Język jest w trakcie standaryzacji w ramach Java Community Process, w ramach zgłoszenia JSR 241.

Środowisko

Groovy wymaga Javy, więc na początku należy pobrać dystrybucję ze strony SUN. Wymagana jest wersja 1.4 lub nowsza, dla Groovy 1.1 wymagana jest wersja 1.5 lub nowsza. Konieczne jest ręczne ustawienie zmiennej środowiskowej JAVA_HOME. Nasępnie należy pobrać instalator lub źródła ze strony Groovy'ego i postępować zgodnie ze wskazówkami. (Uwaga: pod Windowsem ścieżka instalacyjna nie może zawierać spacji!).

Istnieje wsparcie dla Groovy'ego w najpopularniejszych środowiskach deweloperskich dla Javy:

  • Eclipse, poprzez plugin
  • IntelliJ IDEA, poprzez plugin
  • NetBeans, od wersji 6.5

Cechy

Groovy jest nadzbiorem języka Java. Można po prostu zmienić rozszerzenie pliku z .java na .groovy i to zazwyczaj będzie działać. Jednak Groovy ma szereg nowych cech, których standardowa Java nie posiada. Nowe cechy, które wprowadza Groovy, to w szczególności:

  • statyczne i dynamiczne typowanie
  • closures
  • przeciążanie operatorów
  • ułatwienia w obsłudze kolekcji (tablice, listy, mapy) i wyrażeń regularnych
  • wyrażenia zagnieżdżone w napisach
  • operator bezpiecznego odwołania ?. (automatyczna sprawdzanie braku null)

Składnia może być dużo bardziej zwięzła, niż w Javie. Na przykład, deklaracja w Javie:

public class StdJava
{
  public static void main(String argv[])
  {
    for (String it : new String [] {"Rod", "Carlos", "Chris"})
      if (it.length() <= 4)
        System.out.println(it);
  }
}

może w Groovym wyglądać tak

["Rod", "Carlos", "Chris"].findAll{it.size() <= 4}.each{println it}

Ponadto Groovy posiada wsparcie dla języków znaczników, takich jak XML, HTML, SAX, W3C DOM, Ant tasks, Swing user interfaces itp.

Osiągamy to zawsze poprzez tę samą składnię:

def someBuilder = new NodeBuilder()

someBuilder.people(kind:'folks', groovy:true) {
  person(x:123,  name:'James', cheese:'edam') {
    project(name:'groovy')
    project(name:'geronimo')
  }
  person(x:234,  name:'bob', cheese:'cheddar') {
    project(name:'groovy')
    project(name:'drools')
  }
}

zmieniając jedynie builder.

Z Groovym otrzymujemy:

  • NodeBuilder - tworzenie drzew
  • DOMBuilder - tworzenie dokumentów W3C DOM
  • MarkupBuilder - tworzenie XML/HTML
  • AntBuilder - tworzenie zadań Anta
  • SwingBuilder - tworzenie interfejsów w Swingu

Składnia

Przykład 1 - HelloWorld

Wersja 1

def name='World'; println "Hello $name!"

Wersja 2

class Greet {
  def name
  Greet(who) { name = who[0].toUpperCase() +
                      who[1..-1] }
  def salute() { println "Hello $name!" }
}

g = new Greet('world')
g.salute()

Wersja 3

import static org.apache.commons.lang.WordUtils.*

class Greeter extends Greet {
  Greeter(who) { name = capitalize(who) }
}

new Greeter('world').salute()

Wersja 4

groovy -e "println 'Hello ' + args[0]" World

Różnice w porównaniu z Javą

Groovy stara się być jak najbardziej naturalnym dla programistów Javy. Tu znajdują się główne różnice pomiędzy Javą i Groovym:

  • następujące pakiety są domyślnie importowane w Groovy:
java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*
  • == oznacza equals() dla obiektów każdego typu
  • średniki są opcjonalne (konieczne, gdy kilka instrukcji znajduje się w jednej linii)
  • słowo return jest opcjonalne
  • można używać słowa "this" wewnątrz metod statycznych (wskazuje klasę)
  • wszytko jest domyślnie publiczne
  • nie ma klas zagnieżdżonych
  • klauzula throws w sygnaturze metody nie jest kontrolowana
  • nie otrzymujemy błędów kompilacji, gdy chcemy wołać niezdefiniowaną metodę lub typy argumentów są niepoprawne (szczegóły)

Closures

Są to pewnego rodzaju bloki lub funkcje bez nazwy.

def clos = { println "hello!" }

println "Executing the closure:"
clos()                          //wypisze "hello!"

Mogą przyjmować parametry:

def clos = { a, b -> print a+b }
clos( 5, 7 )                       //wypisze "12"

Gdy ma być tylko jeden parametr, możemy go pominąć i użyć słówka it

def clos = { print it }
clos( "hi there" )              //wypisze "hi there"

Wiele metod jako ostatni parametr pobiera closure. Na jego przekazanie są dwa sposoby:

def list = ['a','b','c','d']
def newList = []

list.collect( newList ) {
  it.toUpperCase()
}
println newList           //  ["A", "B", "C", "D"]
def list = ['a','b','c','d']
def newList = []

def clos = { it.toUpperCase() }
list.collect( newList, clos )

assert newList == ["A", "B", "C", "D"]

Więcej informacji:

Kolekcje

Groovy ma naturalne wsparcie dla kolekcji, list, map i tablic.

def list = [5, 6, 7, 8]
assert list.get(2) == 7
assert list[2] == 7
assert list instanceof java.util.List

def emptyList = []
assert emptyList.size() == 0
emptyList.add(5)
assert emptyList.size() == 1
Przedziały

Przedziały są szczególnym przypadkiem kolekcji (rozszerzają java.util.List). Są zaimplementowane efektywnie, bowiem pamiętają jedynie końce przedziału.

// przedział domknięty
def range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)

// przedział jednostronnie otwarty
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert ! range.contains(8)

// punkty końcowe
def range = 1..10
assert range.from = 1
assert range.to = 10

// przedział dokmnięty
def range = 'a'..'d'
assert range.size() == 4
assert range.get(2) == 'c'
assert range[2] == 'c'
assert range instanceof java.util.List
assert range.contains('a')
assert range.contains('d')
assert ! range.contains('e')
Mapy

Domyślnie kluczem jest String, wartością dowolny obiekt.

def map = [name:"Gromit", likes:"cheese", id:1234]
assert map.get("name") == "Gromit"
assert map.get("id") == 1234
assert map["name"] == "Gromit"
assert map['id'] == 1234
assert map instanceof java.util.Map

def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.put("foo", 5)
assert emptyMap.size() == 1
assert emptyMap.get("foo") == 5

Zachowują się także podobnie do beanów, dlatego mamy także taki dostęp:

assert map.name == "Gromit"
assert map.id == 1234

def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.foo = 5
assert emptyMap.size() == 1
assert emptyMap.foo == 5
Przydatne metody
def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert words.findAll{ w -> w.size() > 4 } == ['buffalo', 'dinosaur']

def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert words.collect{ it[0] } == ['a', 'b', 'c', 'd']

Wyrażenia regularne

Groovy wspiera wyrażenia regularne, wprowadzając operatory =~ i ==~ sprawdzające, czy tekst pasuje do wzorca (więcej).

Pętle

Oprócz standardowych pętli Javy dostajemy kilka nowych możliwości.

// iteracja po przedziale
def x = 0
for ( i in 0..9 ) {
    x += i
}
assert x == 45

// iteracja po liście
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
    x += i
}
assert x == 10

// iteracja po tablicy
array = (0..4).toArray()
x = 0
for ( i in array ) {
    x += i
}
assert x == 10

// iteracja po mapie
def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
    x += e.value
}
assert x == 6

// iteracja po wartościach w mapie
x = 0
for ( v in map.values() ) {
    x += v
}
assert x == 6

// iteracja po znakach w napisie
def text = "abc"
def list = []
for (c in text) {
    list.add(c)
}
assert list == ["a", "b", "c"]

// iteracja poprzez closures
def stringList = [ "java", "perl", "python", "ruby", "c#", "cobol",
                   "groovy", "jython", "smalltalk", "prolog", "m", "yacc" ];

def stringMap = [ "Su" : "Sunday", "Mo" : "Monday", "Tu" : "Tuesday",
                  "We" : "Wednesday", "Th" : "Thursday", "Fr" : "Friday",
                  "Sa" : "Saturday" ];

stringList.each() { print " ${it}" }; println "";
// java perl python ruby c# cobol groovy jython smalltalk prolog m yacc

stringMap.each() { key, value -> println "${key} == ${value}" };
// Su == Sunday
// We == Wednesday
// Mo == Monday
// Sa == Saturday
// Th == Thursday
// Tu == Tuesday
// Fr == Friday

switch

Switch jest znacznie bardziej rozbudowany niż w Javie. Możemy sprawdzać równość, przynależność do przedziału, klasy, itp.

def x = 1.23
def result = ""

switch ( x ) {
    case "foo":
        result = "found foo"

    case "bar":
        result += "bar"

    case [4, 5, 6, 'inList']:
        result = "list"
        break

    case 12..30:
        result = "range"
        break

    case Integer:
        result = "integer"
        break

    case Number:
        result = "number"
        break

    default:
        result = "default"
}

assert result == "number"

Napisy

Napisy w Groovym są implementowane przez klasy String i GString. Można używać zarówno ", jak i '. Klasa GString pozwala na umieszczenie wewnątrz napisu wyliczanego wyrażenia (więcej).

Inne

  • Komentarze jedno- i wielolinijkowe jak w Javie.
  • Przypisanie jednoczesne
def a = 1, b = 2

(a, b) = [b, a]

assert a == 2
assert b == 1
  • Przy wołaniu metod nawiasy są opcjonalne. Ponadto istnieje składnia dla argumentów nazwanych
compare fund: "SuperInvestment", withBench: "NIKEI"
monster.move from: [3,4], to: [4,5]

Historia

Groovy został zaproponowany przez Jamesa Strachana na jego blogu w kwietniu 2003 roku. Kolejne wersje pojawiały się pomiędzy 2004 a 2006 rokiem. Po rozpoczęciu procesu standaryzacji w ramach JCP, zmieniło się numerowanie wersji. Wersja 1.0 pojawiła się 2 stycznia 2007. Po różnych betach i RC wersja 1.1 została wydana 7 grudnia 2007. Następnie jej numer zmieniono na 1.5, by podkreślić istotność wprowadzonych zmian.

Grails

Grails ma na celu wykorzystanie w Groovym strategii "Convention Over Configuration". Jest to open-source'owy framework wykorzystujący język Groovy uzupełniając i ułatwiając tworzenie Javowych aplikacji webowych. Grails można używać jako wolnostojącego środowiska deweloperskiego, które ukrywa wszystkie szczegóły konfiguracyjne, można go także integrować z logiką biznesową napisaną w Javie. Grails stara się, aby tworzenie aplikacji było tak proste, jak to tylko możliwe, dlatego powinien spodobać się nie tylko deweloperom korzystającym dotychczas z Javy.

Grails jest frameworkiem wzorowanym na Ruby on Rails, jako bazę wykorzystującym język Groovy. Po swoim poprzedniku przejął podstawowe zasady:

  • DRY (Don’t Repeat Yourself - Nie powtarzaj się)
  • Convention Over Configuration (Konwencja ponad konfigurację)

Co stanowi o dodatkowej sile Grailsa to wsparcie dla popularnych i mających bardzo dobrą opinię bibliotek Javowych: Spring Framework, Hibernate, Sitemesh. Dla użytkownika końcowego mogą być one niewidoczne, ukryte za interfejsami napisanymi w Groovy, ułatwiającymi ich używanie i redukującymi ilość potrzebnej konfiguracji (to jest właśnie siła wspomnianej wcześniej zasady!). W każdym momencie możliwe jest jednak pełne ich wykorzystanie oraz samodzielna konfiguracja.

W skrócie więc Grails = elastyczność Groovy + moc bibliotek Javy.

Co dodatkowo stanowi o sile Grails to system pluginów. Sprawnie działająca społeczność skupiona wokół projektu tworzy coraz to nowe pluginy (aktualnie około 40) pozwalające na współpracę m.in. z Flexem, JSF, GWT, Acegi, OpenLaszlo, Yahoo UI i wieloma innymi technologiami.

Z punktu widzenia programisty szybkość tworzenie aplikacji wzrasta więc niesamowicie a dodatkowo to wciąż jest Java (no i trochę Groovy).

Środowisko

Podobnie jak Groovy, Grails wymaga Javy, więc na początku należy pobrać dystrybucję ze strony SUN. Konieczne jest ręczne ustawienie zmiennej środowiskowej JAVA_HOME. Następnie:

  • pobieramy najnowszą dystrybucję Grails i rozpakowujemy archiwum (dostępna również tu);
  • ustawiamy zmienną środowiskową GRAILS_HOME na katalog, do którego rozpakowaliśmy archiwum;
  • do zmiennej środowiskowej PATH dodajemy ścieżkę do katalogu bin w folderze Grails;

Na students może to wyglądać tak (linijki dodane do pliku .bash_profile):

export GRAILS_HOME=~/grails-1.1
export PATH=$PATH:$GRAILS_HOME/bin
  • wpisujemy 'grails' w konsoli, jeśli pojawiają się komunikaty pomocy, to wszystko jest zainstalowane;
  • w przeciwnym przypadku można spróbować 'chmod +x grails' w katalogu bin z zainstalowanym Grailsem.

Do korzystania z Grails wystarczy nam edytor tekstowy i konsola. Istnieje oczywiście wsparcie dla Grails w najpopularniejszych środowiskach deweloperskich Javy:

Pierwsza aplikacja

Stworzymy prostą aplikację, służącą do dodawania ogłoszeń. Na początku wpisujemy komendę:

grails create-app

Podajemy nazwę naszego projektu, gdy jesteśmy o to poproszeni (np. ogloszenia). Powstaje następująca struktura katalogów:

    + grails-app
       + conf                 ---> pliki konfiguracyjne
           + hibernate              ---> opcjonalne pliki konfiguracyjne hibernate'a
           + spring                 ---> opcjonalne pliki konfiguracyjne springa
       + controllers          ---> kontrolery
       + domain               ---> encje
       + i18n                 ---> komunikaty dla internacjonalizacji
       + services             ---> serwisy
       + taglib               ---> biblioteki znaczników
       + util                 ---> klasy z dodatkowymi usługami
       + views                ---> widoki
           + layouts              ---> layouty
   + lib
   + scripts                  ---> skrypty
   + src
       + groovy               ---> opcjonalny; pliki źródłowe Groovy'ego
       + java                 ---> opcjonalny; pliki źródłowe Javy
   + test                     ---> wygenerowane klasy testowe
   + web-app
       + WEB-INF

Źródło danych

'create-app' stworzyło plik konfiguracyjny dla źródła danych DataSource.groovy w katalogu /grails-app/conf. Domyślnie źródłem danych jest baza danych HSQLDB utrzymywana w pamięci. Świetnie nadaje się do testowania (dlatego wykorzystamy ją w niniejszym tutorialu), jednak w poważnych zastosowaniach nie będzie użyteczna. Aby skonfigurować źródło danych wystarczy zmienić wartości źródła danych i sterowników we wspomnianym pliku oraz umieścić odpowiednie biblioteki w katalogu /lib.

Domyślnie plik wygląda tak:

dataSource {
  pooled = false
  driverClassName = "org.hsqldb.jdbcDriver"
  username = "sa"
  password = ""
}
// environment specific settings
environments {
  development {
    dataSource {
      dbCreate = "create-drop" // one of 'create', 'create-drop','update'
      url = "jdbc:hsqldb:mem:devDB"
    }
  }
  test {
    dataSource {
      dbCreate = "update"
      url = "jdbc:hsqldb:mem:testDb"
    }
  }
  production {
    dataSource {
      dbCreate = "update"
      url = "jdbc:hsqldb:file:prodDb;shutdown=true"
    }
  }
}

Encje

Nową encję tworzymy wpisując:

grails create-domain-class

w katalogu utworzonego projektu. Podajemy nazwę encji, gdy zostaniemy o to poproszeni. Wpisanie np. "lokal" (z małej litery) spowoduje utworzenie pliku Lokal.groovy w podkatalogu /grails-app/domain w ramach naszego projektu. Plik edytujemy używając dowolnego edytora tekstowego lub IDE. Utworzona klasa będzie automatycznie mapowana do bazy danych.

Możemy teraz dodać do niej kilka pól, np.:

class Lokal {
  String typ
  String miasto
  Long liczbaPomieszczen
  Boolean garaz
}

Dane testowe

Aby na początku dodać do bazy pewne dane, należy wyedytować plik /grails-app/conf/BootStrap.groovy (w niektórych wersjach ApplicationBootStrap.groovy), dodając dane do bloku init

class BootStrap {

    def init = { servletContext ->
        new Lokal(typ:"Dom",miasto:"Warszawa",liczbaPomieszczen:3,garaz:false).save()
    }
    def destroy = {
    }
}

Więzy spójności

Do klasy encji możemy dodać pewne więzy spójności, które oczywiście będą kontrolowane także w interfejsie, np.:

  static constraints = {
        typ(inList:["Dom", "Mieszkanie", "Pokój"])
        liczbaPomieszczen(range:1..6)
  }

więcej

Ćwiczenie 1: W analogiczny sposób dodaj do aplikacji encję Uzytkownik, zawierającą pola 'email' i 'haslo'. Dodaj także do systemu jakiegoś użytkownika.

Wskazówka

Rozwiązanie

Kontroler

Kontoler odpowiada za przejęcie żądania i wykonanie odpowiedniego closure. W katalogu aplikacji wpisujemy

grails create-controller

aby utworzyć nowy kontroler. Jako nazwę wpisujemy "Lokal" (tym razem z wielkiej litery). Powstaje plik /grails-app/controllers/LokalController.groovy. Edytujemy go, dodając linię

def scaffold = true

oraz usuwamy linijkę

def index = { }

Dzięki temu uzyskamy dynamiczny Scaffolding, czyli automatycznie zostaną wygenerowane widoki oraz operacje CRUD.

Ćwiczenie 2: Stwórz kontroler dla encji Uzytkownik i dodaj do niej pusty closure o nazwie login (przyda się później).

Rozwiązanie

Interceptor

Do kontrolera możemy dodawać interceptory, znane z programowania aspektowego. Dodając

      def beforeInterceptor = [action:this.&mojaFunkcja,except:['index']]

      def mojaFunkcja() {
          println "Witam w mojej funkcji"
      }

do pliku kontrolera mamy pewność, że przed wywyłaniem dowolnej metody kontrolera oprócz index zostanie wywołana mojaFunkcja.

Ćwiczenie 3: Spraw, aby przy wywołaniu dowolnej metody kontrolera encji Lokal, za wyjątkiem index, list i show, zostało wykonane przekierowanie do zdefiniowanej w ćwiczeniu 2 akcji "login" kontrolera encji Uzytkownik, o ile żaden użytkownik nie jest w tym momencie zalogowany. Przekierowanie wykonujemy metodą redirect.

Wskazówka

Rozwiązanie

Uruchamiamy aplikację

W tym momencie możemy już uruchomić naszą aplikację. W tym celu wystarczy wpisać

grails run-app

oraz wpisać w przeglądarce adres http://localhost:8080/ogloszenia.

Wybieramy link LokalController. Pojawia się lista dodanych lokali. Gdy spróbujemy wybrać "New Lokal", pojawi się strona

HTTP ERROR: 404
/WEB-INF/grails-app/views/uzytkownik/login.jsp
RequestURI=/ogloszenia/uzytkownik/login
Powered by Jetty://

Oznacza to, że nie jesteśmy zalogowanym użytkownikiem, zostaliśmy przekierowani do strony logowania, ale ona jeszcze nie istnieje.

GSP

GroovyServer Pages jest to język znaczników, służący do tworzenia widoków stron internetowych. W składni bardzo przypomina JSP. Polega na wstawianiu specjalnych znaczników GSP pomiędzy kod HTML (więcej).

W naszym przypadku do logowania wystarczy użycie znacznika <g:form>.

<html>
    <head>
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
                       <meta name="layout" content="main" />
         <title>Logowanie</title>
    </head>
    <body>
        <div class="body">
            <g:form action="zaloguj" method="post">
                <div class="dialog">
                                <p>Podaj e-mail i hasło:</p>
                 <table class="userForm">
                   <tr class='prop'>
                       <td valign='top' style='text-align:left;' width='20%'>
                           <label for='email'>E-mail:</label>
                       </td>
                       <td valign='top' style='text-align:left;' width='80%'>
                           <input id="email" type='text' name='email' value='${uzytkownik?.email}' />
                      </td>
                  </tr>
                  <tr class='prop'>
                      <td valign='top' style='text-align:left;' width='20%'>
                          <label for='haslo'>Hasło:</label>
                      </td>
                      <td valign='top' style='text-align:left;' width='80%'>
                          <input id="haslo" type='password' name='haslo'
                                 value='${uzytkownik?.haslo}' />
                      </td>
                  </tr>
               </table>
               </div>
               <div class="buttons">
                     <span class="formButton">
                        <input type="submit" value="Zaloguj"></input>
                     </span>
               </div>
            </g:form>
        </div>
    </body>
</html>

Powyższy kod zapisujemy do pliku /grails-app/views/uzytkownik/login.gsp <g:form action="zaloguj"> oznacza, że bo wybraniu przycisku "Zaloguj" w kontrolerze użytkownika zostanie wywołany closure "zaloguj"

Teraz możemy zrestartować aplikację. Gdy ponownie wybierzemy "New Lokal", powinna pojawić się strona logowania.

Ćwiczenie 4: Spraw, aby logowanie działało. Wyszukiwanie użytkownika w bazie można wykonać metodą findWhere. Parametry ze strony pobieramy z mapy params.

Rozwiązanie

Ćwiczenie 5: Dodaj wylogowywanie.

Wskazówka

Rozwiązanie

Ćwiczenie 6: Dodaj możliwość rejestracji oraz przypominanie hasła przez e-mail.

Linki

Groovy

Grails

Osobiste