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:
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)
}
Ć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.