Groovy als Web-Backend: Unterschied zwischen den Versionen

Aus Jonas Notizen Webseite
Zur Navigation springen Zur Suche springen
K (Visual Editor Test)
(→‎Einführung: Angefangen die gesamte Seite zu überbessern)
Zeile 1: Zeile 1:
 
=Einführung=
 
=Einführung=
 +
 +
== Quellen ==
 +
 +
* [https://grails.org/ Offizielle Grails Webseite]
 +
* [http://docs.grails.org/3.3.9/guide/single.html Offizielle Dokumentationsseite zu Grails 3.3.9] (Single-Page)
 +
* [http://guides.grails.org/ Homepage der offiziellen Grails-Guides]
 +
* [http://views.grails.org/latest/ Startseite des offiziellen Unterprojekts "Grails Views"]
 +
* [[wikipedia:Grails_(framework)|Englischer Wikipedia-Eintrag zum Thema "Grails (Framework)"]]
 +
 +
<br />
 
==Über Grails==
 
==Über Grails==
Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten,  
+
[[Datei:Grails logo.png|mini|Grails Logo]]
und folgen meist nicht dem '''[https://de.wikipedia.org/wiki/Don’t_repeat_yourself DRY-Prinzip]''' ("'''D'''on't '''R'''epeat '''y'''ourself").<br>
+
Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten, und folgen meist nicht dem '''[https://de.wikipedia.org/wiki/Don’t_repeat_yourself DRY-Prinzip]''' ("'''D'''on't '''R'''epeat '''y'''ourself").  
Dynamische Frameworks wie [https://www.djangoproject.com/ Django (Python)] und [https://rubyonrails.org/ Rails (Ruby)]  
+
 
haben neue Einblicke gesetzt, wie man über Web-Applikationen denken sollte.  
+
<br>Dynamische Frameworks wie [https://www.djangoproject.com/ Django (Python)] und [https://rubyonrails.org/ Rails (Ruby)] haben neue moderne Einblicke gesetzt, wie man über Web-Applikationen denken sollte.  
  
'''Grails baut auf diesen Konzepten auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich.'''
+
'''Grails baut auf diesen Konzepten auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich.''' ''Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.''
''Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.''
 
  
 
Zu den hervor-zu-hebenden Features zählen:
 
Zu den hervor-zu-hebenden Features zählen:
Zeile 13: Zeile 22:
 
*[http://gorm.grails.org/ GORM] - Eine einfach zu verwendende [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung Objekt-Mapping]-Bibliothek für [http://gorm.grails.org/latest/hibernate SQL], [http://gorm.grails.org/latest/mongodb MongoDB], [http://gorm.grails.org/latest/neo4j/ Neo4j] und [http://gorm.grails.org/ andere].
 
*[http://gorm.grails.org/ GORM] - Eine einfach zu verwendende [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung Objekt-Mapping]-Bibliothek für [http://gorm.grails.org/latest/hibernate SQL], [http://gorm.grails.org/latest/mongodb MongoDB], [http://gorm.grails.org/latest/neo4j/ Neo4j] und [http://gorm.grails.org/ andere].
 
*View-Templating zum [https://gsp.grails.org/ rendern von HTML] sowie auch [http://views.grails.org/ JSON]
 
*View-Templating zum [https://gsp.grails.org/ rendern von HTML] sowie auch [http://views.grails.org/ JSON]
*Eine Controller-Logik, aufgebaut auf [http://www.spring.io/ Spring Boot]
+
*Eine auf [http://www.spring.io/ Spring Boot] aufbauende Controller-Logik
 
*Ein Plugin-System mit [http://plugins.grails.org/ hunderten von Plugins]
 
*Ein Plugin-System mit [http://plugins.grails.org/ hunderten von Plugins]
 
*Flexible "App-Profile" zur [http://start.grails.org/#/index einfachen Erstellung von Apps mit AngularJS/React/...-Integration] ''(soz. Starthilfen)''
 
*Flexible "App-Profile" zur [http://start.grails.org/#/index einfachen Erstellung von Apps mit AngularJS/React/...-Integration] ''(soz. Starthilfen)''
*Eine interaktive Kommandozeilen-Umgebung (CLI) sowie einem Build-System auf [http://gradle.org/ Gradle]-Basis
+
*Eine interaktive Kommandozeilen-Anwendung (CLI) sowie einem Build-System auf [http://gradle.org/ Gradle]-Basis
 
*Einen eingebauten [http://tomcat.apache.org/ Apache Tomcat]-Container, der sogar während des laufenden Betriebes neugeladen werden kann
 
*Einen eingebauten [http://tomcat.apache.org/ Apache Tomcat]-Container, der sogar während des laufenden Betriebes neugeladen werden kann
  
All dies wird durch die Leistungsfähigkeit der [http://groovy-lang.org/ Groovy]-Sprache und den umfassenden Einsatz  
+
All dies wird durch die Leistungsfähigkeit der [http://groovy-lang.org/ Groovy]-Sprache und den umfassenden Einsatz [https://de.wikipedia.org/wiki/Domänenspezifische_Sprache domänenspezifischer  Sprachen] (DSLs) wesentlich vereinfacht.
[https://de.wikipedia.org/wiki/Domänenspezifische_Sprache Domänen-spezifischer Sprachen] (DSLs) wesentlich vereinfacht.
 
  
==Grails installieren mithilfe des SDK-Mans==
+
<br />
''(Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [https://docs.grails.org/3.3.10/guide/single.html#requirements].)''
 
  
Mithilfe vom [https://sdkman.io/ SDKMAN] kann man Grails ganz einfach auf sein System mit folgendem Befehl installieren:<br>
+
==Installation von Grails 3.3==
<code>$ sdk install grails 3.3.9</code>
 
  
==App erstellen==
+
=== Installations-Anforderungen ===
 +
''Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [https://docs.grails.org/3.3.10/guide/single.html#requirements].''
 +
 
 +
* Wenn man JDK 1.7 verwenden möchte, müsste man Gradle mit Java 1.7.0_131-b31 oder höher ausführen, um die [https://blog.gradle.org/unable-to-download-maven-central-bintray Auflösung von Gradle-Abhängigkeiten zu beheben, wenn die Unterstützung von TLS v1.1 und v1.0 eingestellt wird].
 +
 
 +
<br />
 +
 
 +
=== Automatische Grails-Installation mithilfe des SDKMAN's-Projekts ===
 +
<blockquote>SDKMAN! ist ein Werkzeug zur Verwaltung paralleler Versionen mehrerer Software Development Kits auf den meisten Unix-basierten Systemen.
 +
 
 +
Es bietet eine bequeme Befehlszeilenschnittstelle (CLI) und API zum Installieren, Wechseln, Entfernen und Auflisten von Kandidaten. <ref>Startseite des SDKMAN! Projekts: https://sdkman.io/</ref></blockquote>
 +
 
 +
 
 +
Nachdem man [https://sdkman.io/install SDKMAN! mithilfe der offiziellen Anleitung erfolgreich installiert hat] kann man mit dem folgenden Befehl Grails auf sein System installieren:<syntaxhighlight lang="bash">
 +
$ sdk install grails 3.3.9
 +
</syntaxhighlight>Falls dies die erste Installation von Grails mithilfe des SDKMAN! ist, wird diese Version direkt als Standard in allen Umgebungen gesetzt und auch direkt angewendet.
 +
<br />
 +
==[http://docs.grails.org/3.3.9/guide/single.html#introduction#creatingAnApplication App erstellen]==
 
<code>$ grails create-app APP-NAME</code> erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.
 
<code>$ grails create-app APP-NAME</code> erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.
  
===Grails Befehle ausführen===
+
===Befehle ausführen===
 
Wenn man jetzt in den erstellten Projekt-Ordner wechselt ''(<code>$ cd APP-NAME</code>)'' gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:
 
Wenn man jetzt in den erstellten Projekt-Ordner wechselt ''(<code>$ cd APP-NAME</code>)'' gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:
  
Zeile 38: Zeile 61:
 
**Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
 
**Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
 
*Mithilfe von <code>$ grails</code> in die [https://docs.grails.org/3.3.9/guide/gettingStarted.html#usingInteractiveMode Interaktive-Konsole] wechseln, und dort seine Befehle ''(Ohne <code>grails</code> vorne dran)'' ausführen
 
*Mithilfe von <code>$ grails</code> in die [https://docs.grails.org/3.3.9/guide/gettingStarted.html#usingInteractiveMode Interaktive-Konsole] wechseln, und dort seine Befehle ''(Ohne <code>grails</code> vorne dran)'' ausführen
**Vorteil: Es wird nur einmal die JVM gestartet / benötigte Projekt/Grails-Daten geladen (= Weniger Ladezeit)
+
**Vorteil: Es wird nur einmal die JVM gestartet / benötigte Projekt und Grails-Daten geladen (= Weniger Ladezeit)
 
**Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste
 
**Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste
  
==[http://docs.grails.org/latest/guide/single.html#conventionOverConfiguration "Konventionen über Konfiguration"]==
+
 
"Konventionen über Konfiguration" bedeutet, '''dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird'''.<br>
+
D.h.: Wenn man in einer Dokumentation <code>grails <<command>></code> kann man:
'''Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden "injeziert"'''. (Beispiel: [https://docs.grails.org/latest/ref/Constraints/Usage.html constraints], [http://docs.grails.org/3.1.1/ref/Domain%20Classes/hasMany.html hasMany]).  
+
 
 +
* Direkt <code>grails <<command>></code> in seiner Shell eingeben
 +
* Zuerst mithilfe von <code>grails</code> in die Interaktive Grails-Shell eindringen und danach den <code><<command>></code> eingeben.
 +
 
 +
 
 +
== Grails-Integrationen für diverse IDE's ==
 +
 
 +
=== IntelliJ-IDEA ===
 +
IntelliJ IDEA ist eine '''ausgezeichnete''' IDE für die Entwicklung von Grails 3.0. Sie ist in 2 Editionen erhältlich:
 +
 
 +
* der kostenlosen Community-Edition und
 +
* der kostenpflichtigen "Ultimate" Edition. (Tipp: Es gibt auch kostenlose Studenten-Pakete)
 +
 
 +
Die Community Edition kann für die meisten Dinge verwendet werden, obwohl GSP-Syntax-Highlighting nur ein Teil der "Ultimate" Edition ist.
 +
<br />
 +
 
 +
=== TextMate, Sublime, VIM, ... ===
 +
Siehe [http://docs.grails.org/3.3.9/guide/single.html#_textmate_sublime_vim_etc offizielle Dokumentation]
 +
<br />
 +
==[http://docs.grails.org/3.3.9/guide/single.html#conventionOverConfiguration Konventionen über Konfiguration]==
 +
"Konventionen über Konfiguration" bedeutet, '''dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird'''.<br>'''Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden "injeziert"'''. (Beispiel: [https://docs.grails.org/latest/ref/Constraints/Usage.html constraints], [http://docs.grails.org/3.1.1/ref/Domain%20Classes/hasMany.html hasMany]).  
  
 
Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:
 
Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:
  
*<code>grails-app</code> - "Top-Level" Ordner für allerhand App-Ressourcen
+
*<code>grails-app</code> - "Top-Level" Ordner für allerhand Groovy-Ressourcen
**<code>'''assets'''</code> - Statische, unveränderbare Ressourcen wie CSS, Bilder und Javascript:
+
**<code>'''assets'''</code> - Statische, unveränderbare Ressourcen wie CSS, Bilder und Javascript (Siehe [https://grails.org/plugin/asset-pipeline asset-pipeline Plugin])
 
***<code>images</code>
 
***<code>images</code>
 
***<code>javascripts</code>
 
***<code>javascripts</code>
 
***<code>stylesheets</code>
 
***<code>stylesheets</code>
**<code>'''conf'''</code> - [https://docs.grails.org/latest/guide/conf.html Konfigurations-Dateien]
+
**<code>'''conf'''</code> - [https://docs.grails.org/3.3.9/guide/conf.html Konfigurations-Dateien]
***<code>'''application.yml'''</code> - Laufzeit Konfigurationen
+
***<code>'''application.yml'''</code> - Laufzeit Konfiguration
***<code>logback.groovy</code> - [https://logback.qos.ch/manual/groovy.html logback-Konfigurationen]
+
***<code>logback.groovy</code> - [https://logback.qos.ch/manual/groovy.html Groovy LOGBack-Konfiguration]
**<code>'''controllers'''</code> - [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Web-Controller-Klassen] - '''Das C''' im '''M'''odel'''V'''iew'''C'''ontrol
+
**<code>'''controllers'''</code> - [http://docs.grails.org/3.3.9/guide/theWebLayer.html#controllers Web-Controller-Klassen] - '''Das C''' im '''M'''odel'''V'''iew'''C'''ontrol
**<code>'''domain'''</code> - [http://docs.grails.org/latest/guide/GORM.html Domain-Klassen] - '''Das M''' im '''M'''odel'''V'''iew'''C'''ontrol
+
**<code>'''domain'''</code> - [http://docs.grails.org/3.3.9/guide/GORM.html Domain-Klassen] - '''Das M''' im '''M'''odel'''V'''iew'''C'''ontrol
**<code>'''i18n'''</code> - Nachrichten-Bündel für [http://docs.grails.org/latest/guide/i18n.html i18n (Internationalisierung)]
+
**<code>'''i18n'''</code> - Nachrichten-Bündel für [http://docs.grails.org/3.3.9/guide/i18n.html i18n (Internationalisierung)]  
 
**<code>init</code> - Eingangspunkt der App
 
**<code>init</code> - Eingangspunkt der App
 
***<code>Application.groovy</code> - Eingangspunkt zum grundsätzlichen starten von Grails (Enthält die <code>main</code>-Methode)
 
***<code>Application.groovy</code> - Eingangspunkt zum grundsätzlichen starten von Grails (Enthält die <code>main</code>-Methode)
 
***<code>'''Bootstrap.groovy'''</code> - Beinhält Methoden, die nach dem anfänglichen initialisieren von Grails (Siehe oben) aufgerufen werden. In dieser Klasse steht einem schon die so ziemlich gesamte Grails-Umgebung zur Verfügung (um zB. GORM-Objekte zu erstellen/validieren/löschen/... , E-Mails zu schicken, Konfigurationsdateien zu lesen, ...)
 
***<code>'''Bootstrap.groovy'''</code> - Beinhält Methoden, die nach dem anfänglichen initialisieren von Grails (Siehe oben) aufgerufen werden. In dieser Klasse steht einem schon die so ziemlich gesamte Grails-Umgebung zur Verfügung (um zB. GORM-Objekte zu erstellen/validieren/löschen/... , E-Mails zu schicken, Konfigurationsdateien zu lesen, ...)
**<code>jobs</code> - Klassen, die in einem selbst-bestimmten Zyklus aufgerufen werden ([https://grails.org/plugin/quartz?skipRedirect=true Siehe Quartz-Plugin])
+
**<code>jobs</code> - Klassen, die in einem selbst-bestimmten Zyklus aufgerufen werden (Siehe [https://grails.org/plugin/quartz?skipRedirect=true Quartz-Plugin])
 
**<code>services</code> - Die [http://docs.grails.org/latest/guide/services.html Service-Layer] für die eigentliche Applikations-Logik (Controller sollten nur die Request/Redirects behandeln)
 
**<code>services</code> - Die [http://docs.grails.org/latest/guide/services.html Service-Layer] für die eigentliche Applikations-Logik (Controller sollten nur die Request/Redirects behandeln)
 
**<code>taglib</code> - [https://gsp.grails.org/latest/guide/taglibs.html Tag-Libraries] zum erstellen eigener GSP-Tags
 
**<code>taglib</code> - [https://gsp.grails.org/latest/guide/taglibs.html Tag-Libraries] zum erstellen eigener GSP-Tags
Zeile 69: Zeile 112:
 
*<code>src/main/groovy</code> - (Anderweitig) Hilfsklassen
 
*<code>src/main/groovy</code> - (Anderweitig) Hilfsklassen
 
*<code>src/test/groovy</code> - [http://docs.grails.org/latest/guide/testing.html Unit und Integrations-Tests]
 
*<code>src/test/groovy</code> - [http://docs.grails.org/latest/guide/testing.html Unit und Integrations-Tests]
*<code>'''build.gradle'''</code> - (Gradle-)Build Konfigurationen (Für Plugins, Dependencies, ...)
+
*''<code>'''build.gradle'''</code> - (Gradle-)Build Konfigurationen (Für Plugins, Dependencies, ...)''
 +
 
  
 
==App starten==
 
==App starten==
Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl  
+
Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/run-app.html run-app]</code> "von Grund auf" gestartet werden.
<code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/run-app.html run-app]</code> ("von Grund auf") gestartet werden.
 
  
 
*Standard-Port dieses Web-Servers ist <code>8080</code>, kann aber durch den Parameter <code>-port=[PORT]</code> abgeändert werden.
 
*Standard-Port dieses Web-Servers ist <code>8080</code>, kann aber durch den Parameter <code>-port=[PORT]</code> abgeändert werden.
*Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. ''(Standardmäßig http://localhost:8080/APP-NAME/<nowiki/>)''
+
*Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. ''(Standardmäßig http://localhost:8080/APP-NAME/<nowiki/> - kann aber auch durch die Laufzeit-Konfigurationsvariable <code>server.contextPath</code> geändert werden.)''
  
 
==App testen==
 
==App testen==
Alle <code>$ grails create-*</code> Befehle erstellen eine dazugehörige
+
Alle <code>$ grails create-*</code> Befehle generieren eine [http://docs.grails.org/latest/guide/testing.html Unit oder Integrations-Testing] Klasse im <code>src/test/groovy</code> Ordner.
[http://docs.grails.org/latest/guide/testing.html Unit-Testing] Klasse unter <code>src/test/groovy</code>.
+
 
Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit  
+
Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/test-app.html test-app]</code> starten.
<code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/test-app.html test-app]</code> starten.
 
  
 
==[https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen]==
 
==[https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen]==
Grail-Apps können auf mehrere Weisen bereitgestellt werden. <br>
+
Grail-Apps können auf mehrere Weisen bereitgestellt werden. <br>Wenn man einen Traditionellen Container ''(Wie Apache's Tomcat, Jetty, etc.)'' benutzt, kann man ganz einfach ein sog. "Web Application Archive" (<code>.war</code>-Datei) erstellen, welches alle App-Daten in eine Archiv-ähnliche Datei zusammenfasst. ''(Vergleichbar mit einem <code>.jar</code>-Archiv)''<br>
Wenn man einen Traditionellen Container ''(Wie Apache's Tomcat, Jetty, etc.)'' benutzt, kann man ganz einfach ein sog. "Web Application Archive" (<code>.war</code>-Datei) erstellen, welches alle App-Daten in eine Archiv-ähnliche Datei zusammenfasst. ''(Vergleichbar mit einem <code>.jar</code>-Archiv)''<br>
 
  
 
===Grails und Tomcat===
 
===Grails und Tomcat===
Zeile 107: Zeile 148:
  
 
===Unterstützte Java EE-Container===
 
===Unterstützte Java EE-Container===
Grails läuft auf jeden Container der  die [https://de.wikipedia.org/wiki/Servlet Java Servlet API] in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:
+
Grails 3-3 läuft auf jeden Container der  die [https://de.wikipedia.org/wiki/Servlet Java Servlet API] in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:
  
 
*Tomcat 7
 
*Tomcat 7
Zeile 124: Zeile 165:
 
*Die dazugehörigen Unit-Tests
 
*Die dazugehörigen Unit-Tests
  
Alle oben genannten Skelette können mithilfe von <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/generate-all.html generate-all] (PACKET.)KLASSEN-NAME</code>
+
Alle oben genannten Skelette können mithilfe von <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/generate-all.html generate-all] (PACKET.)KLASSEN-NAME</code> ganz einfach erstellt werden.
ganz einfach erstellt werden.
+
 
  
 
=[https://docs.grails.org/3.3.10/guide/single.html#conf Konfiguration]=
 
=[https://docs.grails.org/3.3.10/guide/single.html#conf Konfiguration]=

Version vom 20. Mai 2020, 10:15 Uhr

Inhaltsverzeichnis

Einführung

Quellen


Über Grails

Grails Logo

Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten, und folgen meist nicht dem DRY-Prinzip ("Don't Repeat yourself").


Dynamische Frameworks wie Django (Python) und Rails (Ruby) haben neue moderne Einblicke gesetzt, wie man über Web-Applikationen denken sollte.

Grails baut auf diesen Konzepten auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich. Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.

Zu den hervor-zu-hebenden Features zählen:

All dies wird durch die Leistungsfähigkeit der Groovy-Sprache und den umfassenden Einsatz domänenspezifischer Sprachen (DSLs) wesentlich vereinfacht.


Installation von Grails 3.3

Installations-Anforderungen

Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [1].


Automatische Grails-Installation mithilfe des SDKMAN's-Projekts

SDKMAN! ist ein Werkzeug zur Verwaltung paralleler Versionen mehrerer Software Development Kits auf den meisten Unix-basierten Systemen. Es bietet eine bequeme Befehlszeilenschnittstelle (CLI) und API zum Installieren, Wechseln, Entfernen und Auflisten von Kandidaten. [1]


Nachdem man SDKMAN! mithilfe der offiziellen Anleitung erfolgreich installiert hat kann man mit dem folgenden Befehl Grails auf sein System installieren:

$ sdk install grails 3.3.9

Falls dies die erste Installation von Grails mithilfe des SDKMAN! ist, wird diese Version direkt als Standard in allen Umgebungen gesetzt und auch direkt angewendet.


App erstellen

$ grails create-app APP-NAME erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.

Befehle ausführen

Wenn man jetzt in den erstellten Projekt-Ordner wechselt ($ cd APP-NAME) gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:

  • $ grails BEFEHL
    • Nachteil: Für jeden Befehl muss eine neue JVM-Instanz erstellt werden (= Bei jedem Befehl muss man eine gewisse Zeit extra warten)
    • Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
  • Mithilfe von $ grails in die Interaktive-Konsole wechseln, und dort seine Befehle (Ohne grails vorne dran) ausführen
    • Vorteil: Es wird nur einmal die JVM gestartet / benötigte Projekt und Grails-Daten geladen (= Weniger Ladezeit)
    • Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste


D.h.: Wenn man in einer Dokumentation grails <<command>> kann man:

  • Direkt grails <<command>> in seiner Shell eingeben
  • Zuerst mithilfe von grails in die Interaktive Grails-Shell eindringen und danach den <<command>> eingeben.


Grails-Integrationen für diverse IDE's

IntelliJ-IDEA

IntelliJ IDEA ist eine ausgezeichnete IDE für die Entwicklung von Grails 3.0. Sie ist in 2 Editionen erhältlich:

  • der kostenlosen Community-Edition und
  • der kostenpflichtigen "Ultimate" Edition. (Tipp: Es gibt auch kostenlose Studenten-Pakete)

Die Community Edition kann für die meisten Dinge verwendet werden, obwohl GSP-Syntax-Highlighting nur ein Teil der "Ultimate" Edition ist.

TextMate, Sublime, VIM, ...

Siehe offizielle Dokumentation

Konventionen über Konfiguration

"Konventionen über Konfiguration" bedeutet, dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird.
Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden "injeziert". (Beispiel: constraints, hasMany).

Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:

  • grails-app - "Top-Level" Ordner für allerhand Groovy-Ressourcen
    • assets - Statische, unveränderbare Ressourcen wie CSS, Bilder und Javascript (Siehe asset-pipeline Plugin)
      • images
      • javascripts
      • stylesheets
    • conf - Konfigurations-Dateien
    • controllers - Web-Controller-Klassen - Das C im ModelViewControl
    • domain - Domain-Klassen - Das M im ModelViewControl
    • i18n - Nachrichten-Bündel für i18n (Internationalisierung)
    • init - Eingangspunkt der App
      • Application.groovy - Eingangspunkt zum grundsätzlichen starten von Grails (Enthält die main-Methode)
      • Bootstrap.groovy - Beinhält Methoden, die nach dem anfänglichen initialisieren von Grails (Siehe oben) aufgerufen werden. In dieser Klasse steht einem schon die so ziemlich gesamte Grails-Umgebung zur Verfügung (um zB. GORM-Objekte zu erstellen/validieren/löschen/... , E-Mails zu schicken, Konfigurationsdateien zu lesen, ...)
    • jobs - Klassen, die in einem selbst-bestimmten Zyklus aufgerufen werden (Siehe Quartz-Plugin)
    • services - Die Service-Layer für die eigentliche Applikations-Logik (Controller sollten nur die Request/Redirects behandeln)
    • taglib - Tag-Libraries zum erstellen eigener GSP-Tags
    • utils - Hilfsklassen die auf die Grails-Umgebung Zugriff haben?
    • views - Groovy Server Pages - Das V im ModelViewControl
  • scripts - Zum erstellen eigener Grails Befehle, zB. Code-Generierungs Skripts wie generate-all
  • src/main/groovy - (Anderweitig) Hilfsklassen
  • src/test/groovy - Unit und Integrations-Tests
  • build.gradle - (Gradle-)Build Konfigurationen (Für Plugins, Dependencies, ...)


App starten

Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl $ grails run-app "von Grund auf" gestartet werden.

  • Standard-Port dieses Web-Servers ist 8080, kann aber durch den Parameter -port=[PORT] abgeändert werden.
  • Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. (Standardmäßig http://localhost:8080/APP-NAME/ - kann aber auch durch die Laufzeit-Konfigurationsvariable server.contextPath geändert werden.)

App testen

Alle $ grails create-* Befehle generieren eine Unit oder Integrations-Testing Klasse im src/test/groovy Ordner.

Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit $ grails test-app starten.

App bereitstellen

Grail-Apps können auf mehrere Weisen bereitgestellt werden.
Wenn man einen Traditionellen Container (Wie Apache's Tomcat, Jetty, etc.) benutzt, kann man ganz einfach ein sog. "Web Application Archive" (.war-Datei) erstellen, welches alle App-Daten in eine Archiv-ähnliche Datei zusammenfasst. (Vergleichbar mit einem .jar-Archiv)

Grails und Tomcat

Grails bindet standardmäßig einen Tomcat-8 Container mit jeder WAR-Datei ein.
Da dies aber zu Problemen führen kann, wenn der Server bereits einen aktiven Tomcat-Container mit einer anderen Version besitzt, kann man diese Abhängigkeit (Dependency) innerhalb der build.gradle-Datei von compile auf provided umstellen:

provided "org.springframework.boot:spring-boot-starter-tomcat"

Die Tomcat-Version kann man ebenfalls unter build.gradle innerhalb der dependencies {}-Sektion wie folgt auf zB. Tomcat-7 ändern:

ext['tomcat.version'] = '7.0.59'

WAR-Datei erstellen und starten

Mit dem Grails-Befehl $ grails war baut Grails die ".war-Version" seiner App im Verzeichnis build/libs des Projekts auf.

Mit dem Java-Befehl java -Dgrails.env=prod -jar [DEINE-WAR-DATEI].war kann man nun ganz einfach seine erstellte .war-Datei ausführen.
Beispiel: java -Dgrails.env=prod -jar build/libs/mywar-0.1.war

Unterstützte Java EE-Container

Grails 3-3 läuft auf jeden Container der die Java Servlet API in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:

  • Tomcat 7
  • GlassFish 3
  • Resin 4
  • JBOSS 6
  • Jetty 8
  • Oracle Weblogic 12c
  • IBM WebSphere 8.0

Generierung eines Applikations-Bereichs (Scaffolding)

Mit Scaffolding kann man einige grundlegende CRUD (Create, Read, Update, Delete)-Schnittstellen für eine Domänenklasse generieren, darunter:

  • Die benötigten Views (.gsp)
  • Die dazugehörigen Controller-Klassen für CRUD-Operationen
  • Die dazugehörigen Unit-Tests

Alle oben genannten Skelette können mithilfe von $ grails generate-all (PACKET.)KLASSEN-NAME ganz einfach erstellt werden.


Konfiguration

Obwohl Grails dem Schema "Convention-over-Configuration" folgt, kommt man natürlich nicht ohne Konfigurations-Dateien aus.

Für einen schnelles Hello-World (o.Ä.) reichen die Standard-Konfigurationen komplett aus. Möchte man dann aber zB. seine eigene MySQL-Verbindung einrichten, kann man dies mithilfe simpler Konfigurationsdateien einstellen.

Grundlegende Konfiguration

Grails Konfigurations-Dateien sind in 2 Sektionen aufgeteilt:

  • Build-Konfiguration (build.gradle)
  • Laufzeit-Konfiguration (grails-app/conf/application.yml)

Standard-Variablen

Für die Konfigurations-Dateien stehen folgende Variablen zur Verfügung:

Variable Beschreibung
userHome Ordner-Pfad beim starten des Servers
grailsHome Ordner-Pfad indem Grails installiert ist. (WENN GRAILS_HOME gesetzt wurde, wird diese genutzt)
appName Der Applikations-Name, gelesen von build.gradle
appVersion Die Applikations-Version, gelesen von build.gradle

Beispiel-Benutzung:

my.tmp.dir = "${userHome}/.grails/tmp"

Konfigurationswerte abrufen

innerhalb von Controllern/Tag-Libraries

Um innerhalb von Controllern/Tag-Libraries auf die Laufzeit-Konfiguration zuzugreifen, gibt es eine spezielle öffentliche Variable namens grailsApplication vom Typ GrailsApplication.

Dessen config-Variable vom Typen Config bietet die benötigten Funktionen um Werte aus der Konfigurationsdatei zu erhalten.
Insbesondere Interessant ist die getProperty-Methode, bei der man auch einen Fallback-Wert angeben kann falls kein Wert in der Konfig angegeben ist:

class MyController {

    def hello(Recipient recipient) {
        // ...
        // Eigenschaft 'foo.bar.max.hellos' (Vom Typen "Integer") abrufen, falls nicht gesetzt "5" nehmen
        def max = grailsApplication.config.getProperty('foo.bar.max.hellos', Integer, 5)

        // Eigenschaft 'foo.bar.greeting' (Ohne definierten Typen, aka. "String"), falls nicht gesetzt "Hello"
        def greeting = grailsApplication.config.getProperty('foo.bar.greeting', "Hello")

        def message = (recipient.receivedHelloCount >= max) ? "Sorry, you've been greeted the max number of times" :  "${greeting}, ${recipient}"

        render message
    }
}

Konfigurationswerte mit Variablen verknüpfen

Zugehörige Konfigurations-Werte kann man zudem mit Springs @Value-Annotation mit einer Variable verknüpfen.

import org.springframework.beans.factory.annotation.*

class MyController {
    // Beim starten wird die Variable auf den Wert von der Konfig gesetzt
    @Value('${foo.bar.hello}')
    String recipient

    def hello() {
        render "Hello ${recipient}"
    }
}

Logging

Standardmäßig wird die Protokollierung in Grails 3.0 mithilfe des Logback-Frameworks verarbeitet und kann mit der Datei unter grails-app/conf/logback.groovy konfiguriert werden.

externe Konfigurationsdateien

Um zu bestimmen, von welcher Datei Logback seine Konfigurationswerte lesen soll, gibt es 3 Möglichkeiten:

  • Innerhalb der Runtime-Konfiguration:
logging:
    config: /Users/me/config/logback.groovy
  • Mithilfe einer Umgebungsvariable:
$ export LOGGING_CONFIG=/Users/me/config/logback.groovy
$ ./gradlew bootRun
  • Mithilfe einer System-Eigenschaft:
$ ./gradlew -Dlogging.config=/Users/me/config/logback.groovy bootRun

Anfrage-Parameter im Stacktrace verstecken

Wenn Grails eine Stack-Trace protokolliert, enthält die Protokollnachricht möglicherweise die Namen und Werte aller Anforderungsparameter für die aktuelle Anfrage. Parameter, die in dem Protokoll nicht aufgeführt werden sollen, können innerhalb der Konfigurationsvariable grails.exceptionresolver.params.exclude hinterlegt werden:

grails:
    exceptionresolver:
        params:
            exclude:
                - password
                - creditCard


Umgebungs-Abhängige Konfigurationswerte

Wenn man die Konfigurationen direkt in die Datei einfügt (Am Anfang der Zeile, aka. als Parent-Node) gilt diese für alle gestarteten Umgebungen.
In Grails kann man, je nachdem in welcher "Umgebung/Phase" die App gestartet wurde, andere Werte festlegen:

environments {
    production {
        dataSource {
            url = "jdbc:mysql://liveip.com/liveDb"
            //...
        }
    }
    test {
        dataSource {
            url = "jdbc:mysql://localhost.com/testDb"
            //...
        }
    }
}

Hiermit hat man seine eigene Abschottung zum Testen und muss sich zB. nicht fürchten die Produktion-Datenbank zu vernichten.

DataSource

Weil Grails auf Java aufbaut sollte man ein bisschen Verständnis von JDBC (Java Database Connectivity, die Datenbankschnittstelle der Java-Plattform) besitzen.

Treiber einfügen

Wenn man eine andere Datenbank als H2 nimmt benötigt man noch einen JDBC-Treiber. Für MySQL wäre dies Connector/J.
Solche Treiber kommen normalerweise in Form eines Java-Archivs. Am besten fügt man diese Treiber mithilfe eines Maven-Repos in seine dependencies ein: (Gefunden in build.gradle)

dependencies {
    ...
    runtime 'mysql:mysql-connector-java:5.1.29'
}

Wenn man die JAR erfolgreich eingebunden hat kann man sich jetzt mit der Weise bekannt machen wie Grails Datenbanken-Aktionen verwaltet.

Datasource Einrichten

In grails-app/conf/DataSource.groovy wird eingestellt, wie Grails die Domain-Klassen-Objekte speichert.

Für dataSource stehen folgende Konfigurations-Möglichkeiten zur Verfügung:

  • driverClassName - Klassenname des JDBC-Treibers (zB. "com.mysql.jdbc.Driver")
  • username, password - Login-Daten, um die JDBC-Verbindung aufzubauen
  • url - Die JDBC-URL der Datenbank
  • dbCreate - Gibt an, ob die Datenbank automatisch aus dem Domänen-Modell generiert werden soll: "create-drop", "create", "update" oder "validate".
    • create - Falls sich das Datenbank-Schema verändert hat, wird die Tabelle entleert und mithilfe des neuen Schemas erstellt
    • create-drop - Gleich wie create, leert die Datenbank (!) hingegen bei jedem neustart (Egal ob sich das Schema geändert hat oder nicht, nützlich zum Testen)
    • update - Aktualisiert das Model der Tabelle, falls es sich verändert hat (Die Daten bleiben erhalten, Die Domainklassen-Tabelle muss existieren) Notiz: Solch eine Weise veträgt natürlich nicht viele Änderung des Schemas. (zB. Beim ändern des Namens einer Spalte bleibt die alte Spalte mit den existierenden Daten und die neue wird einfach hinzugefügt)
    • validate - Warnt vor Änderungen, aber verändert das Datenbank-Schema bei Veränderung nicht
  • pooled - Ob man einen Pool von Verbindungen benutzen sollte (Standard: true)
  • type - Die Verbindungspoolklasse, wenn Sie Grails zwingen möchten, sie zu verwenden, wenn mehr als eine verfügbar ist.
  • logSql - Leitet SQL-Logs zum stdout um
  • formatSql - Ob SQL-Logs formatiert werden sollen
  • dialect - Eine Zeichenfolge oder Klasse, die den Ruhezustand-Dialekt darstellt, der für die Kommunikation mit der Datenbank verwendet wird. Informationen zu verfügbaren Dialekten findet man im Paket org.hibernate.dialect.
  • readOnly - Wenn true, ist die DataSource schreibgeschützt, was dazu führt, dass der Verbindungspool für jede Verbindung setReadOnly(true) aufruft
  • transactional - Wenn false, wird die DataSource-Transaktionsmanager-Bean außerhalb der verketteten BE1PC-Transaktionsmanager-Implementierung belassen. Dies gilt nur für zusätzliche Datenquellen.
  • persistenceInterceptor - Die Standarddatenquelle wird automatisch mit dem Persistenz-Interceptor verbunden, andere Datenquellen werden nicht automatisch verbunden, es sei denn, dies ist auf true gesetzt
  • properties - Extra Einstellungen für den DatenSource-Bean. (Siehe Tomcat-Pool Dokumentation sowie die dazugehörige Javadoc)
  • jmxExport - Wenn false, wird die Registrierung von JMX-MBeans für alle DataSources deaktiviert. Standardmäßig werden JMX-MBeans für DataSources mit jmxEnabled = true in den Eigenschaften hinzugefügt.

NOTIZ

Anscheinend wird in neueren Versionen von Grails die dataSource in der Datei grails-app/conf/application.yml im YAML-Format eingestellt. Die Konfigurationsmöglichkeiten bleiben aber anscheinend die gleichen, Beispiel:

dataSource:
    pooled: true
    jmxExport: true
    driverClassName: com.mysql.jdbc.Driver
    url: "jdbc:mysql://localhost:3306/db_mahlzeit"
    dbCreate: "create-drop"
    dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    username: usr_mahlzeit
    password: nesev0_JP

Typische MySQL Konfiguration

Eine Typische MySQL-Konfiguration wäre:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    properties {
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
    }
}

Komplexere MySQL Konfiguration

Oder ein Beispiel einer eher komplexeren Konfiguration:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    properties {
       // Documentation for Tomcat JDBC Pool
       // http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
       // https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       ignoreExceptionOnPreLoad = true
       // http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED // safe default
       // controls for leaked connections
       abandonWhenPercentageFull = 100 // settings are active only when pool is full
       removeAbandonedTimeout = 120
       removeAbandoned = true
       // use JMX console to change this setting at runtime
       logAbandoned = false // causes stacktrace recording overhead, use only for debugging
       // JDBC driver properties
       // Mysql as example
       dbProperties {
           // Mysql specific driver properties
           // http://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html
           // let Tomcat JDBC Pool handle reconnecting
           autoReconnect=false
           // truncation behaviour
           jdbcCompliantTruncation=false
           // mysql 0-date conversion
           zeroDateTimeBehavior='convertToNull'
           // Tomcat JDBC Pool's StatementCache is used instead, so disable mysql driver's cache
           cachePrepStmts=false
           cacheCallableStmts=false
           // Tomcat JDBC Pool's StatementFinalizer keeps track
           dontTrackOpenResources=true
           // performance optimization: reduce number of SQLExceptions thrown in mysql driver code
           holdResultsOpenOverStatementClose=true
           // enable MySQL query cache - using server prep stmts will disable query caching
           useServerPrepStmts=false
           // metadata caching
           cacheServerConfiguration=true
           cacheResultSetMetadata=true
           metadataCacheSize=100
           // timeouts for TCP/IP
           connectTimeout=15000
           socketTimeout=120000
           // timer tuning (disable)
           maintainTimeStats=false
           enableQueryTimeouts=false
           // misc tuning
           noDatetimeStringSync=true
       }
    }
}


Eigene Scripts

Mit dem Befehl $ grails create-script NAME kann man sein eigenes Skript erstellen, welches standardmäßig in src/main/scripts/ gespeichert wird.

description()

Die description()-Methode wird für die Ausgabe vom grails help Befehl verwendet, um den Nutzern zu helfen.
Beispiel vom generate-all Befehl:

description( "Generates a controller that performs CRUD operations and the associated views" ) {
  usage "grails generate-all <<DOMAIN CLASS>>"
  flag name:'force', description:"Whether to overwrite existing files"
  argument name:'Domain Class', description:'The name of the domain class'
}

Beispiel-Befehl

Ein Beispiel anhand des create-script-Befehls:

description( "Creates a Grails script" ) {
  usage "grails create-script <<SCRIPT NAME>>"
  argument name:'Script Name', description:"The name of the script to create"
  flag name:'force', description:"Whether to overwrite existing files"
}

def scriptName = args[0]
def model = model(scriptName)
def overwrite = flag('force') ? true : false

render  template: template('artifacts/Script.groovy'),
        destination: file("src/main/scripts/${model.lowerCaseName}.groovy"),
        model: model,
        overwrite: overwrite

model()

Wenn man model() mit einem Class/String/File/Resource-Parameter aufruft erhält man eine neue Instanz von der Klasse Model, welche hiflreiche Methoden zur Quellcode-Generierung besitzt.

Scripts innerhalb eines Scripts aufrufen

Grails hat schon von Anfang an eine große Anzahl an Skripts, welche man ganz einfach aufrufen kann.
Der folgende Code ruft den Befehl testApp mit dem Parametern --debug-jvm auf.

testApp('--debug-jvm')










Die Web-Ebene

Controller

Ein Controller verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor. Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, .gsp) delegieren.

Controller erstellen

Mit dem Befehl $ grails create-controller (PACKET.)KLASSEN-NAME erstellt man das Skelett eines Controllers, welches in grails-app/controllers/APP-NAME/DEIN/PACKET/KLASSEN-NAME.groovy gespeichert wird. (Dieser Befehl ist nur zur vereinfachten Erstellung, man kann es auch manuell oder mit einer IDE machen)

Aktionen

Die Standard URL-Mapping-Konfiguration versichert dass der Name des Controllers sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.
Das folgende Beispiel ist hiermit unter ".../mahlzeit/index" erreichbar:

package myapp

class MahlzeitController {
    def index() { }
}

Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.

Standard-Aktion

Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat (Also nur den Namen des Controllers, wie "/mahlzeit/" anstatt zB. "/mahlzeit/login") versucht Grails eine Standard-Aktion ausfindig zu machen, welche diese Anfrage annimmt.

  • Wenn es nur eine Aktion (aka. Methode) gibt, wird diese als Standard-Aktion anerkannt
  • Wenn es eine Aktion namens "index" gibt, wird diese als Standard-Aktion anerkannt

Alternativ kann man auch eine eigenen Standard setzten, indem man static defaultAction = "DEINE-METHODE" in den Quellcode des Controllers einfügt.

Scope-Variablen

"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:

servletContext: Statisch, für alle gleich

Dieser Scope lässt uns Daten speichern, welche über die gesamte Web-App gleich statisch erreichbar sind.
Das servletContext-Objekt ist eine Instanz vom Standardmäßigen Java(EE)-Objekt ServletContext.

Es ist nützlich um:

  • Applikations-Eigenschaften zu speichern
  • lokale Server-Ressourcen zu laden und
  • Informationen vom Servlet-Container zu erhalten.

session: Für jede Sitzung anders

Das session-Objekt ist eine Instanz von [2] der Java(EE) Servlet-API.

Es ist nützlich um Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).

request: Mitgesendete Informationen

Das request-Objekt ist eine Instanz von HttpServletRequest der Java(EE) Servlet API.

Es ist nützlich um:

  • Mitgesendete Request-Header-Felderdaten zu bekommen
  • Anfragen-bezogene Attribute zwischen-zu-speichern und
  • Informationen des aktuellen Klienten zu erhalten

Grails fügt einige zusätzliche Funktionen zum request-Objekt hinzu, die das standardmäßige HttpServletRequest-Objekt nicht hat, hinzu:

  • XML - Eine Instanz der GPathResult welches erlaubt einkommende XML-Anfragen zu verarbeiten (Parsen) - Nützlich für REST
  • JSON - Eine Instanz des JSONObjects welches erlaubt einkommente JSON-Anfragen zu verarbeiten (Parsen) - Nützlich für JSON und REST
  • isRedirected() - Gibt true zurück wenn diese Anfrage nach einem Redirect fordert.
  • get - Gibt true zurück wenn die Anfrage ein GET-Request ist
  • post - Gibt true zurück wenn die Anfrage ein POST-Request ist
  • each - Implementation von Groovys each-Methode um über gewisse Request-Objekte zu iterieren.
  • find - Implementation von Groovys find-Methode um ein gewisses Request-Objekt zu finden (Ohne komplett richtigen Hashkey-Namen)
  • findAll - Implementation von Groovys findAll-Methode um gewisse Request-Objekte zu finden (Ohne komplett richtigen Hashkey-Namen)
  • xhr - Gibt true zurück wenn die Anfrage ein AJAX-Request ist.

Würde der Body der Anfrage folgenden XML-Code besitzen:

<book>
   <title>The Stand</title>
</book>

Können wir dies ganz einfach mithilfe des request-Objekts serialisieren:

def title = request.XML?.book.title // Mit dem "?" versichern wir uns, dass wenn die Variable "XML" "null" ist es zu keiner NullPointerException kommt und "title" einfach auch auf "null" gesetzt wird (Groovy feature :D)
render "The Title is $title"

(params): (Veränderbare,) Mitgesendete CGI Informationen

Eine veränderbare, mehrdimensionale HashMap von Anforderungsparametern (CGI).
Obwohl das request-Objekt auch Methoden zum lesen der Anforderungsparametern enthält, ist diese manchmal nützlich für Daten-Bindung an Objekte.

Nehmen wir an wir haben folgende Anfrage: hello?foo=bar. In unserer Controll-Action "hello" können wir den Wert von foo nun ganz einfach ausgeben lassen:

println params.foo

flash: Speicher zwischen 2 Anfragen

Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung und nur für die nächste Anforderung gespeichert werden. Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.

Mit diesem Muster jk Sie HTTP-Weiterleitungen verwenden (Nützlich für Redirect after Post) und Werte beibehalten, die vom Flash-Objekt abgerufen werden können. Beispiel:

class BookController {

    def index() {
        flash.message = "Welcome!"
        redirect(action: 'home')
    }

    def home() {}
}

Scope eines Controllers

Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:

  • prototype (Standard) - Für jeden Request wird ein neuer Controller erstellt
  • session - Für jede Sitzung wird nur ein Controller-Objekt erstellt
  • singleton - Für die gesamte Zeit gibt es nur eine Instanz des Controllers (Wird geraten für Aktionen mit Methoden)

Dieser Standard-Wert kann man unter grails-app/conf/application.yml wie folgt abändern:

grails:
    controllers:
        defaultScope: singleton

Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man static scope = "DEIN-SCOPE" in den Quellcode des Controllers einfügt.

Model und Ansicht (Model and View)

Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.

  • Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie von zB. einer .gsp-Datei erreichbar sind.

Es gibt einige Wege um ein Model zu übergeben:

  • Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: [Var1: Data1, Var2: Data2, ...]
def VIEW_NAME() {
   [book: Book.get(params.id)] // Gleich wie render(view: "VIEW_NAME", model: [book: Book.get(params.id)])
}

Eine erweiterte/bessere Weiße wäre es:

import org.springframework.web.servlet.ModelAndView

def index() {
    // get some books just for the index page, perhaps your favorites
    def favoriteBooks = ...

    // forward to the list view to show them
    return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
}

zu benutzen.

Zu beachten ist, dass bestimmte Variablennamen in Ihrem Modell nicht verwendet werden können:

  • attributes
  • application

Views selber selektieren mit der render()-Methode

In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche view die Map-Daten übergeben werden soll.
Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter grails-app/views/book/show.gsp nachsuchen: (Nach dem Schema grails-app/views/CONTROLLER/AKTION.gsp)

class BookController {
    def show() {
         [book: Book.get(params.id)]
    }
}

Falls wir aber nicht wollen, das Grails sich den Namen der View-Datei mithilfe des Methoden-Namens sucht, können wir die vielfältige render()-Methode einsetzen:

def show() {
    def map = [book: Book.get(params.id)]
    render(view: "display", model: map)
}

(Anmerkung: Wenn Grails keine .gsp-Datei finden kann, sucht es auch nach .jsp-Dateien.)

Views für Namespaced-Controller selektieren

Wenn ein Controller einen eigenen namespace gesetzt hat (In diesem Beispiel 'business'), schaut Grails

  • zuerst nach ob die View-Datei unter grails-app/views/business/... zu finden ist
  • und falls nicht sucht es unter dem normalen Namespace grails-app/views/... nach.

Beispiel:

class ReportingController {
    static namespace = 'business'

    def humanResources() {
        // This will render grails-app/views/business/reporting/humanResources.gsp
        // if it exists.

        // If grails-app/views/business/reporting/humanResources.gsp does not
        // exist the fallback will be grails-app/views/reporting/humanResources.gsp.

        // The namespaced GSP will take precedence over the non-namespaced GSP.

        [numberOfEmployees: 9]
    }


    def accountsReceivable() {
        // This will render grails-app/views/business/reporting/numberCrunch.gsp
        // if it exists.

        // If grails-app/views/business/reporting/numberCrunch.gsp does not
        // exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.

        // The namespaced GSP will take precedence over the non-namespaced GSP.

        render view: 'numberCrunch', model: [numberOfEmployees: 13]
    }
}

Redirects

Aktionen können mithilfe der Controller-Methode redirect() umgeleitet werden. Beispiel:

class OverviewController {

    def login() {}

    def find() {
        if (!session.user)
            redirect(action: 'login')
            return
        }
        ...
    }
}

Auf welche Seite redirect() umleitet, kann man Grails auf mehrere Weisen mitteilen:

  • Der Name der Aktion (Sowie den namens des Controllers, falls sich die Aktion in einem anderen Controller befindet)
// Redirects to the "index()"-Action in the "home"-Controller
redirect(controller: 'home', action: 'index')
  • URI zu einer Resource relativ vom Kontext-Pfad:
// Redirect to an explicit URI
redirect(uri: "/login.html")
  • Eine Komplette URL:
// Redirect to a URL
redirect(url: "http://grails.org")

Parameter können von einer zu der anderen Aktion mithilfe des params-Arguments übergeben werden:

redirect(action: 'myaction', params: [myparam: "myvalue"])

Diese werden dann in den Controller-Scope params (Siehe oben) gespeichert.

Aktionen aneinander-reihen

chain() fungiert ähnlich wie redirect(), indem es auf eine andere Aktion springt und diese ausführt.
Chaining hingegen erlaubt es uns das Model beizubehalten und Model-Daten zusätzlich zu übergeben.

Beispiel:

class ExampleChainController {

    def first() {
        chain(action: second, model: [one: 1])
    }

    def second () {
        chain(action: third, model: [two: 2])
    }

    def third() {
        [three: 3]
    }
}

Resultiert beim Aufrufen von "/examplechain/first" in das Model

[one: 1, two: 2, three: 3]

Wie beim redirect kann man hier auch weitere Daten in das params-Scope einspeisen:

chain(action: "action1", model: [one: 1], params: [myparam: "param1"])

Die render()-Methode

Die render()-Methode ist sehr flexibel und kann viele Argumente hingegennehmen (Vom anzeigen eines einfachen Textes bis hin zur render eines Views/Templates.).

// renders text to response
render "some text"

// renders text for a specified content-type/encoding
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")

// render a template to the response for the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", model: [book: theShining])

// render each item in the collection using the specified template
render(template: "book", collection: [b1, b2, b3])

// render a template to the response for the specified bean
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", bean: theShining)

//! render the view with the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(view: "viewName", model: [book: theShining])

// render the view with the controller as the model
render(view: "viewName")

// render some markup to the response
render {
    div(id: "myDiv", "some text inside the div")
}

// render some XML markup to the response
render(contentType: "text/xml") {
    books {
         for (b in books) {
             book(title: b.title, author: b.author)
         }
    }
}

//! render a JSON ( http://www.json.org ) response with the builder attribute:
render(contentType: "application/json") {
    book(title: b.title, author: b.author)
}

//! render with status code
render(status: 503, text: 'Failed to update book ${b.id}')

//! render a file
render(file: new File(absolutePath), fileName: "book.pdf")


Data Binding

"Data-Binding" ist das Verfahren, einkommende Request-Parameter an ein Objekt zu binden.

  • "Data-Binding" sollte auch die Konversion von String auf den jeweiligen Typen übernehmen können, weil alle Request-Parameter ja nur Strings sind aber ein Objekt ja auch Integers (etc) haben kann.
// Klasse 
class Person {
    String firstName
    String lastName
    Integer age
}

// Bindung
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]

def person = new Person(bindingMap)

assert person.firstName == 'Peter'
assert person.lastName == 'Gabriel'
assert person.age == 63

Mit Unterklassen

Dieser Binder kann auch unterklassen verwalten:

// Klassen
class Person {
    String firstName
    String lastName
    Integer age
    Address homeAddress
}

class Address {
    String county
    String country
}

// Bindung
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63, homeAddress: [county: 'Surrey', country: 'England'] ]

def person = new Person(bindingMap)

assert person.firstName == 'Peter'
assert person.lastName == 'Gabriel'
assert person.age == 63
assert person.homeAddress.county == 'Surrey'
assert person.homeAddress.country == 'England'

Mit Arrays

Auch Arrays sind möglich:

// Klassen
class Band {
    String name
    static hasMany = [albums: Album] // One-To-Many Relation von Band-zu-Album (Für Datenbank-Relation wichtige Angabe!)
    List albums 
}

class Album {
    String title
    Integer numberOfTracks
}

// Bindung
def bindingMap = [name: 'Genesis',
                  'albums[0]': [title: 'Foxtrot', numberOfTracks: 6],
                  'albums[1]': [title: 'Nursery Cryme', numberOfTracks: 7]]

def band = new Band(bindingMap)

assert band.name == 'Genesis'
assert band.albums.size() == 2
assert band.albums[0].title == 'Foxtrot'
assert band.albums[0].numberOfTracks == 6
assert band.albums[1].title == 'Nursery Cryme'
assert band.albums[1].numberOfTracks == 7

Mit Maps

Sowie auch mit Maps:

// Klassen
class Album {
    String title
    static hasMany = [players: Player] // One-To-Many Relation von Album-zu-Player (Für Datenbank-Relation wichtige Angabe!)
    Map players 
}

class Player {
    String name
}

// Bindung
def bindingMap = [title: 'The Lamb Lies Down On Broadway',
                  'players[guitar]': [name: 'Steve Hackett'],
                  'players[vocals]': [name: 'Peter Gabriel'],
                  'players[keyboards]': [name: 'Tony Banks']]

def album = new Album(bindingMap)

assert album.title == 'The Lamb Lies Down On Broadway'
assert album.players.size() == 3
assert album.players.guitar.name == 'Steve Hackett'
assert album.players.vocals.name == 'Peter Gabriel'
assert album.players.keyboards.name == 'Tony Banks'

File-Uploads

.gsp-Code:

Upload Form: <br />
<g:uploadForm action="upload">
   <input type="file" name="myFile" />
   <input type="submit" />
</g:uploadForm>
  • g:uploadForm fügt automatisch enctype="multipart/form-data" zum form-Tag hinzu.

Nun gibt es viele Möglichkeiten mit dem File-Upload umzugehen. Eine davon ist mithilfe von Springs MultipartFile.
(Diese Weise ist gut um Dateien zu transferieren und manipulieren weil man direkt die InputStream-Instanz bekommen kann.)

def upload() {
    def f = request.getFile('myFile')
    if (f.empty) {
        flash.message = 'file cannot be empty'
        render(view: 'uploadForm')
        return
    }

    f.transferTo(new File('/some/local/dir/myfile.txt'))
    response.sendError(200, 'Done')
}

Upload-Größen-Limit von Grails

Grails Standard-Wert für die maximale Dateigröße liegt bei 128KB. Wenn dieses Limit überschritten wird taucht folgender fehler auf:

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException

Dieses Limit kann man in seiner application.yml folgendermaßen anpassen:

grails:
    controllers:
        upload:
            maxFileSize: 2000000
            maxRequestSize: 2100000
  • maxFileSize: Maximale größe einer Datei bei einem Request
  • maxRequestSize: Maximale Größe eines gesamten Requests

URL-Mappings

In der Dokumentation ist die Konvention des URL-Mappings (standartmäßig) wie folgt eingestellt: /controller/action/id.
Dies kann man aber auch sehr leicht für spezielle Seiten in der Datei grails-app/conf/UrlMappings.groovy umändern. Beispiel (URL "/product" auf die Aktion "list()" vom Controller "Product" leiten):

"/product"(controller: "product", action: "list")

Man kann die URL-Mappings auch verschachteln:

group "/store", {
    group "/product", {
        "/$id"(controller:"product")
    }
}


(Explizites) REST-Mapping

"/books"(resources:'book')

ist gleich wie

get "/books"(controller:"book", action:"index")
get "/books/create"(controller:"book", action:"create")
post "/books"(controller:"book", action:"save")
get "/books/$id"(controller:"book", action:"show")
get "/books/$id/edit"(controller:"book", action:"edit")
put "/books/$id"(controller:"book", action:"update")
delete "/books/$id"(controller:"book", action:"delete")


// Results in /books/1/authors/2
<g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>

Redirects

"/viewBooks"(redirect: '/books/list')
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])


Integrierte Variablen

"/product/$id"(controller: "product")

Im obrigen Fall wird Grails automatisch beim Besuchen von "/product/2" die Zahl 2 in das params-Scope einsetzen, damit es vom Controller einfach genutzt werden kann:

class ProductController {
     def index() { render params.id }
}

Natürlich kann man auch mehrere Variablen verwenden, wie bei diesem Beispiel eines Blogs:

"/$blog/$year/$month/$day/$id"(controller: "blog", action: "show")

Optionale Variablen

Indem man ein Fragezeichen am ende des Variabl-Namens anhängt schlägt das URL-Mapping auch zu, wenn der Nutzer diese nicht angegeben hat. Beispiel-Mapping:

"/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")

Das obrige Beispiel-Mapping schlägt bei allen folgenden URLs zu:

/graemerocher/2007/01/10/my_funky_blog_entry
/graemerocher/2007/01/10
/graemerocher/2007/01
/graemerocher/2007
/graemerocher

Optionale File-Extension

Wenn man die Datei-Erweiterung auch ganz einfach in einer params-Scope Variable haben möchte, kann man dies durch das anhängen von der Optionalen Variable (.$format)?:

// Mapping
"/$controller/$action?/$id?(.$format)?"()

// Controller
def index() {
    render "extension is ${response.format}"
}

Selbst "eingepflanzte" Variablen

Man kann auch selber mithilfe von URL-Mapping Variablen in das params-Scope reinschreiben.

"/holiday/win" {
     id = "Marrakech"
     year = 2007
}


Dynamisch aufgelöste Variablen

Die oben-genannten, selbst setzbaren statischen Variablen sind sehr nützlich.
Aber manchmal soll sich der Wert der Variable je nach Anfrage anders verhalten können. Dies kann man erzielen, indem man einen eigenen Block zur Variable hinzufügt.

"/holiday/win" {
     id = { params.id }
     isEligible = { session.user != null } // must be logged in
}

Im obrigen Fall wird der Code innerhalb der Blöcke erst dann evaluiert, wenn das URL-Matching zugeschlagen wurde, und kann dadurch "mit Logik versehen werden".

Constraints (Einschränkungen des Typens einer Variable)

Nehmen wir folgendes Beispiel:

static mappings = {
   "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

Dieses Mapping funktioniert für:

/graemerocher/2007/01/10/my_funky_blog_entry

Aber auch für Werte, die wir vllt. nicht vorgesehen haben:

/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry

Dies wäre sehr problematisch und man müsste eine (komplexe) eigene Logik reincoden, damit bestimmte Variablen nur einen bestimmten Wert haben dürfen.
Zum Glück bieten uns URL-Mappings einen einfachen Weg für diese Einschränkung/Validation:

"/$blog/$year?/$month?/$day?/$id?" {
     controller = "blog"
     action = "show"
     constraints {
          year(matches:/\\\d{4}/)
          month(matches:/\\\d{2}/)
          day(matches:/\\\d{2}/)
     }
}

Error-Codes

Grails lässt uns zudem jeglichen HTTP-Fehlercode auf eine eigene Aktion (Seite) mappen:

"403"(controller: "errors", action: "forbidden")
"404"(controller: "errors", action: "notFound")
"500"(controller: "errors", action: "serverError")

Man kann auch auftauchende Exceptions auf eine eigene Seite mappen:

"500"(controller: "errors", action: "illegalArgument", exception: IllegalArgumentException)
  1. Startseite des SDKMAN! Projekts: https://sdkman.io/