Neue Features in Spring Boot 2.1.1

Der Actuator Endpoint besitzt jetzt health check für Elesticsearch REST Clients. Das Logging für Serlet und Filter Registration wurde verbessert und den restlichen Loggingeinträgen angepasst. Die Konfiguration des LoggingApplicationListeners ignoriert jetzt zusätzliche Leerzeichen (trim) in der Angabe des LoggingLevels. SAP HANA gehört nun zu den bekannten Datenbanken. Hibernate 5.2 kann auch wieder mit Spring Boot 2.1 verwendet werden, wenn dieses als expliziete Abhängigkeit angegeben wird. Zuvor gab es eine ClassNotFoundException. Spring Boot verwendet standardmäßig Hibernate 5.3.

Caused by: java.lang.ClassNotFoundException: org.hibernate.resource.beans.container.spi.BeanContainer
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 45 common frames omitted

Es kann die SSL Verschlüsselung auf dem Management Port 8081. Der Spring Boot Classloader kann jetzt mit den in Java 9 eingeführten MultiRelease Jars umgehen und diese laden wenn sie in einem ausführbaren Jar sind.

Bug Fixes

Natürlich wurden auch in diesem Release jede Menge Fehler behoben. Unter anderem kann WebFlux, die reaktiven Implementation von Web MVC, nun Fehlerseiten für HTTP Status Codes rendern. Anwendungen die als Abhängigkeiten spring-boot-starter-web und spring-boot-startet-jersey hatten, konnten nicht gestartet werden, weil der Startvorgang mit einer einer Fehlermeldung quittiert wurde.

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'requestContextFilter', defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

Mit Sprng Boot 2.1 wurde das Verhalten von Bean definition overriding geändert, sodass es jetzt zu der Fehlermeldung kommt.

Ein Stacktrace auf der Whitelabel Fehlerrseite wird nun korrekt dargestellt. Das DIV Elemnent wird nun mit style=’white-space:pre-wrap;’ versehen.

Auch das Logging für das Endpoint Mapping wurde verbessert.

Dokumentation von Spring Boot

Wie immer wurde kräftig an der Dokumentation gearbeitet und somit entfällt auch diesmal ein Großteil der gelösten Tickets auf die Java Dokumentation.

Dependencies Upgrades

Hier einige der wichtiges Updates…
Upgrade to Flyway 5.2.3
Upgrade to Jetty Reactive Httpclient 1.0.2
Upgrade to Byte Buddy 1.9.5
Upgrade to Undertow 2.0.16.Final
Upgrade to Rxjava2 2.2.4
Upgrade to Netty Tcnative 2.0.20.Final
Upgrade to Micrometer 1.1.1
Upgrade to Junit Jupiter 5.3.2
Upgrade to Spring Session Bean-SR1
Upgrade to Spring Security 5.1.2.RELEASE
Upgrade to Spring Kafka 2.2.2.RELEASE
Upgrade to Spring Integration 5.1.1.RELEASE
Upgrade to Lombok 1.18.4
Upgrade to Mockito 2.23.4
Upgrade to Jooq 3.11.7
Upgrade to Tomcat 9.0.13
Upgrade to Kafka 2.0.1
Upgrade to Reactor Californium-SR3
Upgrade to Spring Data Lovelace-SR3
Upgrade to Spring Framework 5.1.3

Am 30.11.2018 wurde das Update von der Spring Tools Suite 4.0.2 veröffentlicht. Es enthält neben dem Bugfix für fehlende Context-Path Unterstützung (siehe Liver Hover fehlerhaft) Verbesserungen für die Navigation in application.properties und application.yaml Dateien, Vereinheitlichung der Autovervollständigung in Property und YAML, Autovervollständigung im Config-Editor in der Boot Dash View, Verbesserte Anzeige der Live Hover bei functional configuration für WebFlux, Unterstützung für Logging Groups in Property und YAML Editoren.

Außerdem wurden eine Reihe von Fehlern beseitigt, die die Stabilität des Language Server beeinträchtigten. So wurden hänger und zähflüssige Antwortzeiten beseitigt. Es wurde QuickFix mit CTRL + F1 in Editoren die das LSP (Language Server Protocol) verwenden behoben.

Alles passt zusammen

Nun fügen sich alle Puzzelteile zusammen und es läßt sich unter dem aktuellen LTS von Java, dem JDK 11, ein Beispiel mit Hibernate mit SQLite Datenbank, Jooq und Spring Boot 2.1 unter der Verwendung von Gradle als Buildsystem umsetzen. Bis hierhin gab es einige Baustellen, die im Zuge der Modularisierung (Projekt Jigsaw), noch zu beseitigen waren. Jetzt ist eine Umsetzung mit den aktuellsten Versionen möglich und dieses möchte ich hier vorstellen.

Hier die benötigten Komponenten im Einzelnen:

  • Spring Boot 2.1
  • Spring Data JPA (spring-boot-starter-data-jpa)
  • Jooq 3.11.4 (im Spring Boot Starter definiert)
  • Gradle 5.0-RC4
  • sqlite-dialect (Hibernate Dialekt)
  • sqlite-jdbc (Treiber)

Die Demo

Die Anwendung erstellt zwei Invocation Objekte und persistiert sie mit JPA (Hibernate) in der Datenbank. Im Anschluss wird mit Hilfe von Jooq die Tabelle aus SQLite Datenbank wieder gelesen und in Tabellenform auf der Konsole ausgegeben. Das Ganze passiert in der Klasse InitDatabase, die das CommandLineRunner Interface implemenmtiert und somit beim Start der Anwendung ausgeführt wird. Die SQLite Datenbank legt die Daten in der Datei application.db ab. Eine initiale Version ist im Git Repository vorhanden (s.u.).

Ausführen der Anwendung

Der Build wird wie immer mit dem Dreisatz git clone, cd, ./gradlew clean build bootRun angestoßen.

git clone https://mrpeacockgit.duckdns.org:443/Public/spring-hibernate-jooq-sqlite-demo.git
cd spring-hibernate-jooq-sqlite-demo/
./gradlew clean build bootRun

Hier sehen wir den schön formatierten Output von Jooq auf der Konsole.

ACHTUNG: Es muss der Gradle Wrapper unter JDK 11 ausgeführt werden, weil Java 11 als Source- und TargetCompatibility angegeben ist. Wird Gradle z.B. unter Java 8 ausgeführt, dann bricht Gradle den Buildvorgang mit > Could not target platform: ‘Java SE 11’ using tool chain: ‘JDK 8 (1.8)’ ab. Hat man keine JDK 11 zur Hand, dann kann Source und Target auf Java 8 reduziert werden, da keine Sprachfeatures von Java 11 genutzt werden.

Import in Eclipse

Damit in Eclipse die Anwendung fehlerfrei ist, müssen 2 Schritte ausgeführt werden.

  • Generierung des Java Codes. Es muss die Generierung der Java Quelltexte, aus dem gegebenen Datenbankschema mit Hilfe des Jooq Code Generators, durchgeführt werden. Der Task generateSpringhibernatesqliteJooqSchemaSource wird von dem Jooq Plugin bereitgestellt.

  • Damit die statischen Importe gefunden werden, müssen sie auch im Eclipse Buildpath vorhanden sein. Dazu muss einmalig der Task eclipse ausgeführt werden.

Jetzt sollte das Projekt fehlerfrei im Eclipse sein.

Die Entität

Die Entity die hier verwendet wird, ist der Demo bedingt, sehr einfach gehalten. Wie immer verwende ich das Project Lombok, um den Code sauber von Boilerplate Code zu halten.

@Entity
@Data
public class Invocation {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    private String name;
}

GenerationType

Der GenerationType muss hier auf AUTO stehen, ansonsten wird der Start der Anwendung mit einer Exception quittiert.

Caused by: org.sqlite.SQLiteException: [SQLITE_CONSTRAINT]  Abort due to constraint violation (NOT NULL constraint failed: invocation.id)

Hibernate Schema Erzeugung / Jooq Code Generation

Hier haben wir ein Henne-/Ei-Problem! Der Code Generator von Jooq benötigt eine Datenbank aus der er das Schema ausliest, um den Code zu erzeugen. Im Quelltext wird aber bereits per Import auf generierten Code zugegriffen, was zu einer Fehlermeldung beim Kompilieren während des Builds führen würde. Da die Code Generierung vor der Kompilierung erfolgt, habe ich eine leere Datenbank mit in das Git Repository aufgenommen. Somit kann die Anwendung über den Gradle Build gebaut und auch ausgeführt werden.

Problem der automatischen Schema Generierung

Anhand des oben beschriebenen Henne-/Ei-Problems, sollte eigentlich jedem schnell klar werden, dass das keine Lösung für große Anwendungen ist. Es sollte hier besser auf andere Lösungsansätze ausgewichen werden. Z.B. Flyway als Datenbankschemamigrationstool oder einfach die Verwendung von Spring Boot Bordmitteln (Spring liest schema.sql ein und verarbeitet die SQL Anweisungen).

Jooq Konfiguration

Für das Erstellen der Konfiguration Jooq Code Generator wird das Plugin von Etienne Struder verwendet. Hier in den Beispiel ist nur eine Grundkonfiguration vorgenommen worden. Alle Einstellungsmöglichkeiten die möglich sind, lassen sich über das XSD für Jooq Code Generator erfahren. Das lesen und verstehen des recht umfangreichen XSD ist nicht einfach. Man kann aber z.B. über Visual XSD sich das XSD visualisieren lassen und so den Aufbau schneller verstehen.

Autovervollständigung in Eclipse

Bei der Eingabe in Eclipse werden nicht automatisch die statischen Imports für die generierten Tabellen von Jooq angezeigt. Hier muss man in Eclipse erst den Umweg über eine nicht statischen Import nehmen, um ihn dann per STRG + 1 in einen statischen Import zu überführen.

Beispiel

In der Klasse InitDatabase die den CommadLineRunner implementiert, möchten wir alle Invocations aus der Datenbank listen.

private void listInvocations() {
        ctx.select()
            .from(INVOCATION)
            .fetch()
            .forEach(System.out::println);
    }

INVOCATION ist hier über den statischen Import

import static db.Tables.INVOCATION;

bekannt. Damit die Autovervollständigung funktioniert, muss der Import vorhanden sein. Das heißt erst nach dem man den Import eingefügt hat, lässt sich mit STRG + SPACE das Code-Fragment .from(INVOCATION) einfügen. Das ist nicht sehr effektiv, da man immer erst den Import einfügen muss.

Einstellung der Favoriten

Der Generator von Jooq ist so konfiguriert, dass die Tabelle aus der Datenbank im Java Package de.Tables.* gelistet werden. Die Quelltexte werden außerhalb von main in src/db gespeichert, so dass jederzeit durch löschen von dem Verzeichnis src/db die generierten Quelltext sauber neu erstellt werden können, ohne das alte Artefakte noch verhanden sind.

Man kann Eclipse anweisen, bestimmte static Members anzuzeigen, auch wenn das Import noch fehlt. Und genau das führt hier zum Ziel und erleichtert die Eingabe von Jooq Queries mit der hervorragenden DSL enorm.

In den Präferenzen von Eclipse wählen Sie java –> Editor –> Content Assist –> Favorites und erstellen einen neuen Typen. Hier ist es *db.Tables.**.

Jetzt kann die Eingabe vervollständigt werden, ohne das der Import zuvor vorhanden war

Hier in diesem Betrag gehe ich auf ein paar grundlegende Sicherheitsmaßnahmen ein. Ziel ist es einige wichtige Maßnahmen zum Schutz der Daten vorzustellen und wie diese mit Spring Security genutzt werden können.

CSRF

Cross Site Request Forgery kann man frei mit Seitenübergreifende Anfragenfälschung übersetzen. Um zu verhindern das ein Angreifer Anfragen an die Anwendung stellt, muss man sicherstellen dass der Link auf den der Benutzer geklickt hat nicht untergeschoben worden ist. Dieses kann man recht einfach erreichen, in dem man ein Geheimnis in der Webseite unterbringt. Dieses Geheimnis, es handelt sich hierbei um das sogenannte CSRF-Token, kennt nur die Anwendung (Server) und der Anwender (Client). Das Token hat eine kurz Lebensdauer und wird als hidden input field mit ausgegeben. Hier am Beispiel in Verwendung mit Thymeleaf Templating Engine.

<input
  type="hidden"
  th:name="${_csrf.parameterName}"
  th:value="${_csrf.token}" />

Es ist also nicht sehr kompliziert, aber dennoch eine Wirkungsvolle Waffe gegen mögliche Angreifer. Alle Anwendungen sollten also möglichst CSRF verhindern. Weitere Sicherungsmaßnahmen befinden sich gut beschrieben in der Referenzdokumentation von Spring Boot Security.

Hinweis: Ab Thymeleaf 2.1 und gesetzter @EnableWebSecurity fügt Thymeleaf dieses automatisch ein.

CSRF deaktivieren

Per default ist CSRF aktiviert. Das heißt das Seiten die von der Anwendung ausgeliefert werden, auch das Token beinhalten müssen, da sonst zu recht ein 403 Fehler angezeigt wird. In dem WebSecurityConfigurerAdapter kann man CSRF deaktivieren.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .disable() // TODO remove and secure template with CSRF token
    ...
}

Die Deaktivierung, auch wenn es nur in der Entwicklung ist, ist nicht zu empfehlen. Sicherheit in Anwendungen sollten von Anfang an integraler Bestandteil der Anwendung sein und nicht im Nachhinein übergestülpt werden. Daher rate ich von dieser Vorgehensweise hier ausdrücklich ab.

Dependency

Es muss nur der Starter spring-boot-starter-security eingebunden werden. Dieses ist die einzige Abhängigkeit, um die Anwendung mit Spring Security abzusichern.

dependencies {
    // Spring Boot
    ...    
    implementation('org.springframework.boot:spring-boot-starter-security')
    ...
}

Sicherung auf Methoden Level

Mit welchen Einstellungen lässt sich die Methodensicherung konfigurieren?

Grundsätzlich erfolgt die Steuerung über Annotationen. Welche Annotationen Wirkung haben, hängt davon ab welche Sicherungsmaßnahmen aktiviert sind. In der @EnableGlobalMethodeSecurity können als Paramter folgende Annotationen gesteuert werden:

  • securedEnabled

Methoden oder Klassen können nun mit @Secure() annotiert werden.

  • jsr250Enaabled Die Annotaion jsr250Enabled ist das Equivalent zur Spring Annotation Secured

  • order

  • prePostEnabled

Wenn prePostEnabled wahr ist, dann lassen sich Methoden vor Aufruf oder bevor die Rückgabewerte übergeben werden prüfen.

Beispiel

Wie sieht nun eine typische Konfiguration aus? Hier nachfolgend eine mit @Configuration annotierte Konfiguration, die secured ermöglicht und prePost aktiviert.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class EnableMethodSecurity extends GlobalMethodSecurityConfiguration {

}

Kurztipp für die Eingabe

Legt man die Klasse mit CTRL + n –> class neu an, dann kann man direkt GMSC + STRG + SPACE eingeben und Eclipse expandiert dann zur gewünschten Klasse GlobalMethodSecurityConfiguration. Man muss also nur die Anfangsbuchstaben wählen und dann mit STRG + SPACE die Autovervollständigung akitivieren.

Den Ausdruck hasPermission() implementieren

Neben dem Standard hasRole(“”) kann man in Spring auch eigene Ausdrücke implementieren. Hier zeige ich an dem Beispiel, wie man die hasPermissions() implementieren kann.

Dazu muss die Klasse das Interface PermissionEvaluator implementieren. Zunächst das Grundgerüst:

@Override
public boolean hasPermission(Authentication authentication,
                             Object targetDomainObject,
                             Object permissionObject) {
    if(authentication==null ||
            targetDomainObject==null ||
            !(permissionObject instanceof String)) {

        log.info("Permission denied");

        return false;
    }

    String targetType = targetDomainObject.getClass()
                            .getSimpleName()
                            .toUpperCase();
    String permission = permissionObject
                            .toString()
                            .toUpperCase();

    return hasPrivilege(authentication, targetType, permission);
}

@Override
public boolean hasPermission(Authentication authentication,
                             Serializable targetId,
                             String targetType,
                             Object permissionObject) {
    if ((authentication == null) || 
        (targetType == null) || 
        !(permissionObject instanceof String)) {
        return false;
    }

    String permission = permissionObject
                            .toString()
                            .toUpperCase();

    return hasPrivilege(authentication, targetType, permission);
}

/**
    * Check privilege on given constraints
    * 
    * @param authentication
    * @param targetType
    * @param permission
    * 
    * @return true if access is granted, otherwise false
    */
private boolean hasPrivilege(Authentication authentication,
                             String targetType,
                             String permission) {
    for (final GrantedAuthority grantedAuth : authentication.getAuthorities()) {
        if (grantedAuth.getAuthority().startsWith(targetType)) {
            if (grantedAuth.getAuthority().contains(permission)) {
                return true;
            }
        }
    }
    return false;
}

Methoden absichern

@Controller
public class CustomerController {
    /**
     * Show table with all Customers. This endpoint is protected
     * by hasPermission expression evaluation in PreAuthorize() annotation.
     */
    @GetMapping("/allcustomersHP")
    @PreAuthorize("hasPermission(#model, 'String', 'xxx')")
    public String allCustomersHP(Model model) {
        model.addAttribute("customers", customerService.findAll());
        return "customerListing";
    }

ACHUTNG: Damit @PreAuthorize ausgeführt und somit auch der Ausdruck hasPermission() evaluiert wird, muss in der Konfiguration prePostEnabled=true gesetzt werden. Siehe oben.

Benutzerdefinierte Ausdrücke für die Validierung

Es lassen sich nicht immer alle Umgebungen auf die hasPermission() abbilden, was dazu führen kann das die Funktion, die ja nur die Permission klären soll, auch andere Zwecke erfüllt. Diese Zweckentfremdung führt früher oder später zu Problemen.

Gegeben sei ein größerer Betrieb mit vielen Organisationseinheiten. Dann macht es Sinn zum Beispiel den Zugriff auf die Umsatzzahlen, einer bestimmten Gruppe von Mitarbeiten zu gewähren. Hier wäre es die Controllingabteilung.

Anstelle mit hasPermission auf isController oder ähnliches zu Prüfen, kann man auch gleich die Organisationeinheiten mit in die Benutzerdaten integrieren und z.B. mit einer Funktion isMemberOf(String organization) prüfen. Dazu muss eine Klasse den SecurityExpressionRoot erweitern. Dieses Thema werde ich in einem weiterem Beitrag ausführlich behandeln.

Sicherheitsmaßnahmen komplett deaktivieren

Wenn sich Spring Security im Classpath befindet, dann werden alle Endpoints (bis auf 2 Actuator /health, /info) gesichert und sind ohne weiteres Zutun nicht mehr erreichbar. Und es ist auch gut so, dass das Spring Team dieses konservative Standardverhalten gewählt hat.

Möchte man zum lokalen Testen nicht jedesmal die Dependencies anpassen, so kann man zum Beispiel auch eine 2. Konfiguration anlegen, die nur bei einem bestimmten Profil aktiviert wird. Überschreibt man die beiden Methodensignaturen configure(final AuthenticationManagerBuilder) und configure(final HttpSecurity), dann hat man den Effekt, dass Spring Security nicht im Classpath vorhanden sei.

@Profile("localdebug")
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(final AuthenticationManagerBuilder auth) throws Exception {}

  @Override
  protected void configure(final HttpSecurity http) throws Exception {}
}

Vorwort

In diesem Beitrag zeige ich wie man einen reactive RestController erstellt. Es wird die gesamte Anwendung reaktiv sein, d.h. wir verwenden hier den spring-boot-starter-data-mongodb-reactive Client mit seiner reaktiven Repository Schnittstelle.

Das Beispiel ist mit Absicht extrem einfach gehalten, um das Wesentliche besser darzustellen. In dem Beispiel soll ein RestController eine API mit den üblichen CRUD Methoden zur Verfügung stellen. Es soll in der Datenbank für Reservierungen geschaffen werden. Die Konfiguration wird nicht klassisch mit Annotationen durchgeführt, sondern ich zeige hier die Anwendung von functional configuration zum Setzen der Routen zu den entsprechenden Handlermethoden. Die Anwendung soll es ermöglichen eine Reservierung zu erstellen, lesen, löschen und zu ändern.

Die Dependencies

Um das Beispiel umzusetzen benötigen wir Abhängigkeiten für spring-boot-starter-data-mongodb-reactive und spring-boot-starter-webflux. Die weitere Abhängigkeiten (s.u.) sind optional, aber sollten dennoch mit eingebunden werden, da sie das Leben doch vereinfachen können.

dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
    compile('org.springframework.boot:spring-boot-starter-webflux')
    compile('org.springframework.boot:spring-boot-actuator')
    runtime('org.springframework.boot:spring-boot-devtools')

    // Project Lombok
    // Since Gradle warns if an AnnotationProcessor is found on classpath, put 
    // it into annotationProcessor directive
    compileOnly('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')

    // Testing
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('io.projectreactor:reactor-test')

    // flapdoodle runs a temporary embedded instance of mongodb
    testRuntime('de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.0.1')
}

Project Lombok

Ich mag keinen Boilerplate Code, da er gerade bei Entitäten den eigentlichen Verwendungszweck verdeckt. Daher bin ich ein großer Fan von dem Project Lombok und setze es hier in diesem Beispiel auch ein.

Flapdoodle

Zusätzlich für die Entwicklung und das Unittesting binde ich hier Flapdoodle ein. Flapdoodle kann eine temporäre embedded Version von MongoDB starten. Dazu lädt es ggf. MongoDB herunter und startet für jeden Test eine temporäre Instanz der MongoDB Datenbank. Es wird immer mit einer leeren Datenbank gestartet, so dass hier im Reinraum getestet werden kann, ohne das man zuvor aufräumen muss.

Actuator

Ich habe hier auch spring-boot-actuator mit aufgenommen, damit ich in der Spring Tool Suite 4 die Endpoints in den Properties der Anwendung angezeigt bekomme, bzw. die Live Hover Informationen nutzen kann (siehe Bug in Live Hover).

Die Klasse Reservation

Die Klasse Reservation bildet eine Reservierung ab. Da es hier nur um eine Demonstration handelt, ist hier natürlich genügend Spielraum vorhanden für Erweiterungen. Wie bereits beschrieben nutze ich hier Lombok Annotationen, um möglichst sauber von Boilerplate Code zu arbeiten

Die Klasse Reservation ist also kurz und knackig.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document
class Reservation {
    private String id;

    private String reservationName;
}

Die Annotation @Document beschreibt des sich um ein MongoDB Dokument handelt. Ein Dokument hält einen Datensatz. In MongoDB werden als JSON Objekte übergeben und dann intern als BSON abgespeichert.

Mono und Flux

Bei der reaktiven Programmierung gibt es die beiden Klassen Mono und Flux die das Publisher Interface implementieren. Beide Implementierungen liefern einen Stream von Elementen mit…

Mono genau 0 oder 1 Elemente
Flux 0 oder 1...n Elemente

Die Konfiguration der Routen

Die Klasse ReservationRouter übernimmt das Routing der Request zu den entsprechenden Handlermethoden in der Klasse RequestHandler.

Ein GET Aufruf auf /reservations gibt JSON mit allen Reservierungen aus der Datenbank zurück

Ein GET Aufruf auf /reservation/{id} lädt genau eine Reservierung mit der angegebenen Id

Ein DELETE Aufruf auf /reservation/{id} löscht den referenzierten Datensatz

Ein POST Aufruf auf /reservation erzeugt eine neue Reservierung

Ein PUT Aufruf auf /reservation/{id} modifiziert den referenzierten Datensatz

Die statische Methode i macht die Aufrufe CaseInsensitiv.

@Configuration
public class ReservationRouter {
    @Bean
    RouterFunction<ServerResponse> routes(RequestHandler handler) {
        return route(i(GET("/reservations")), handler::all)
            .andRoute(i(GET("/reservation/{id}")), handler::getById)
            .andRoute(i(DELETE("/reservation/{id}")), handler::deleteById)
            .andRoute(i(POST("/reservation")), handler::create)
            .andRoute(i(PUT("/reservation/{id}")), handler::updateById)
            ;
    }

    private static RequestPredicate i(RequestPredicate target) {
        return new CaseInsensitiveRequestPredicate(target);
    }
}

Die statische Methode route definiert nun mit den angegebenen RequestPredicates (GET, DELETE, POST und PUT) und einem Patternstring, welche Handlermethode den Request bearbeitet.

Warum muss ich schon wieder eine neue Methode zur Konfiguration lernen?

Sicherlich haben Sie sich auch schon gefragt, Moment Mal, das geht doch auch alles mit den bewährten Annotationen. Ja, das stimmt und daran gibt es auch nicht auszusetzen. Es gibt allerdings einen triftigen Grund warum man sich de functional configuration annehmen sollte. Es ist die Performance. Es lassen sich die Startupzeiten mit dem neuen funktionalen Ansatz drastisch, gegenüber der klassischen Methode mit Annotationen, verkürzen. Dieses ist insbesondere im Microservice Bereich, wo schnell mal ein paar zusätzliche Instanzen gespawnt werden müssen, wichtig.

Die Handler Klasse

Die Handlerklasse RequestHandler verarbeitet nun die ankommenden Requests und führt Aktionen auf dem Service durch. Die Methode getById und deleteById werden direkt durchgereicht an den Service. Es wird nur die Pathvariable id extrahiert und dem Service als Parameter übergeben.

Die Handlermethoden liefern ein Mono<ServerResponse> zurück. Spannend wird es der Aufruf von create hier wird der Übergebene ServerRequest in eine Reservation.class gemappt und der Service soll die Reservierung anlegen.

Zu bemerken ist, dass das reaktive Paradigma hier in beide Richtungen eingehalten wird. Das Anfragen die und auch Antworten als Mono oder Flux zwischen dem Server und dem Service ausgetauscht werden. Der hier gezeigte Handler ist vollkommen Asynchron und auch reaktiv, da hier keine blockierenden Operationen ausgeführt werden.

@Component
public class RequestHandler {
    private ReservationService service;

    public RequestHandler(ReservationService service) {
        this.service = service;
    }

    Mono<ServerResponse> getById(ServerRequest r) {
        return defaultReadResponse(service.getById(id(r)));
    }

    Mono<ServerResponse> deleteById(ServerRequest r) {
        return defaultReadResponse(service.deleteById(id(r)));
    }

    Mono<ServerResponse> create(ServerRequest r) {
        Flux<Reservation> flux = r
                .bodyToFlux(Reservation.class)
                .flatMap(toWrite -> service.create(toWrite.getReservationName()));
        return defaultWriteResponse(flux);
    }

...

    private static Mono<ServerResponse> defaultWriteResponse(Publisher<Reservation> reservations) {
        return Mono
                .from(reservations)
                .flatMap(r -> ServerResponse.created(URI.create("/reservation/" + r.getId()))
                .contentType(MediaType.APPLICATION_JSON_UTF8).build());
    }

    private static Mono<ServerResponse> defaultReadResponse(Publisher<Reservation> publisher) {
        return ServerResponse
                .ok()
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(publisher, Reservation.class);
    }

    private static String id(ServerRequest r) {
        return r.pathVariable("id");
    }
}

Der Service

Im vorherigen Abschnitt haben wir beleuchtet wie die Anfragen im Handler verarbeitet werden. Der Service bildet hier fast 1:1 das Repository ab.

@Service
public class ReservationService {
    private ReservationRepository repo;

    public ReservationService(ReservationRepository repo) {
        this.repo = repo;
    }

    public Mono<Reservation> getById(String id) {
        return repo.findById(id);
    }

    public Flux<Reservation> findAll() {
        return repo.findAll();
    }

    public Mono<Reservation> deleteById(String id) {
        return repo.findById(id)
                .flatMap(
                        r -> repo.deleteById(r.getId())
                        .thenReturn(r)
                );
    }

    public Mono<Reservation> create(String reservationName) {
        return repo.save(new Reservation(null, reservationName));
    }
}

Der Service greift auf das reaktive Repository ReservationRepository zu und es werden entsprechend Mono oder Flux<reservation> zurückgegeben. Bei der findAll() haben wir einen Stream mit allen Datensätzen aus der Datenbank. Deshalb kommt hier Flux als Implementierung des Publisher Interfaces zum Einsatz, weil es 0 bis n Datensätze sein können.

Das ReservationRepository

Das Repository ist von Typ ReactiveMongoRepository und stellt die CRUD Methoden zur Verfügung. Wir verwenden hier derived queries, um eine zusätzliche Anfrage für den Service bereitzustellen. Mit findBy+Property kann Spring Data aus dem Methodennamen den gewünschten Query erzeugen. Das heißt wir suchen mit findByReservationName(String name) nach einem Datensatz wo die Property ReservationName dem übergebenen Parameter entspricht.

Dieses ist aber ein Standard Feature von Spring Data und hat nichts mit der reaktiven Implementierung zu tun. Ansonsten gibt es nichts besonderes in dem Interface.

public interface ReservationRepository extends ReactiveMongoRepository<Reservation, String> {
    Flux<Reservation> findByReservationName(String name);
}

Mit curl die Rest API aufrufen

Das Testen der Anwendung möchte ich hier mit dem Kommandozeilenprogramm curl demonstrieren. Es sind standard HTTP Anfragen an den Server, so dass ich hier nur kurz insert und delete zeige.

Einfügen einer Reservierung

curl -i -X POST -H 'Content-Type: application/json' -d '{ "reservationName": "1. Test" }' http://localhost:8080/reservation

Es wird eine Antwort wie zum Beispiel…

HTTP/1.1 201 Created
Content-Type: application/json;charset=UTF-8
Location: /reservation/5bf68a11dbcbb837d33f4be6
content-length: 0

erzeugt, die das erfolgreiche Anlegen des Datensatze bestätigen.

Löschen einer Reservierung

curl -i -X DELETE http://localhost:8080/reservation/5bf3eda398947a5158e60200

Die Template Engine Thymeleaf kann über Fragments bestimmte Blöcke einer HTML Datei verarbeiten. Fragmente können z.B. eingefügt oder ersetzt werden.

Bootstrap CSS Beispiel

Damit Bootstrap CSS auf den Seiten funktioniert, müssen im Header JS und CSS Dateien eingebunden werden. Ansonsten funktioniert Bootstrp CSS nicht korrekt oder der Inhalt wird nicht wie gewünscht angezeigt. Da dieses immer wieder der selbe Block an Anweisungen ist, ist es ein idealer Kandidat, um ihn zu inkludieren.

Wo werden Fragmente abgelegt?

Thymeleaf Fragmente werden üblicherweise unter templates/fragments abgelegt.

Wie wird ein Fragment gekennzeichnet?

Ein Fragment wird im HTML Code mit th:fragment=”name” gekennzeichnet.

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <div th:fragment="header-css" th:remove="tag">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <!-- Bootstrap -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
    ...
    </div>
</head>

In unserem Header hat das Fragment den Namen header-css. Das th:remove Attribut hat hier eine besondere Funktion. Später mehr dazu.

Umsetzung mit Thymeleaf Fragments

Das HTML das an den Browser ausgeliefert werden soll, muss natürlich valide sein. So soll das fertige Ergebnis aussehen:

<!DOCTYPE HTML>
<html>
<head>
<title>Hallo Welt !!!</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- Bootstrap -->
<script src="/webjars/jquery/3.3.1-1/jquery.min.js"></script>
<script src="/webjars/popper.js/1.14.3/umd/popper.min.js"></script>
<link href="/webjars/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" />
<script src="/webjars/bootstrap/4.1.3/js/bootstrap.min.js"></script>
</head>

...

Inkludierung des Fragments

Das Fragment soll uns nun den oben gekennzeichneten Block einfügen. Das Thymeleaf Attribut th:replace benötigt 2 Angaben. Die erste Angabe ist der Dateiname die das Framgent enthält und nach dem :: den Namen des Fragments. Hier also wird in dem Verzeichnis fragments nach der Datei header.html gesucht, welche das Fragment header-css enthält.

Daraus ergibt sich folgender Code:

<head>
<title>Customers</title>
<title th:replace="fragments/header.html :: header-css"></title>
</head>

Hier muss man einen kleinen Trick anwenden, da nur wenige Elemente im HEAD erlaubt sind, damit wir in der IDE keine Warnungen angezeigt bekommen. Wir verwenden einfach einen zweiten TITLE-Tag und ersetzen den Inhalt mit dem Fragment header-css. Das zusätzliche Attribut th:remove=”tag” in dem Fragment sorgt nun dafür dass das umschließende Tag entfernt wird und nur der gewünschte Inhalt im gerenderten Template ausgeliefert wird.

Wer hat sich nicht schon Mal gefragt, was die Blackbox Hibernate im Hintergrund so alles macht?

Statistiken

Statistiken von Hibernate

Hibernate umfangreiche Statistiken für uns erstellen, um zum Beispiel besser zur verstehen warum es an der einen oder anderen Stelle klemmt. Die automatische Erstellung der Statistiken im Hintergrund nagt natürlich an der Performance der Anwendung und sollte daher nur in der Entwicklung verwendet werden. Zu Testzwecken kann das Feature gegebenenfalls auch auf einem PreProduktion System aktiviert werden, damit man eine realere Umgebung Simulieren kann.

Um das Feature zu aktivieren muss man generate_statistics auf true setzen. Dieses kann über die persistence.xml erledigt werden.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    ...
    <persistence-unit name="default" >
         <properties>
         ...
             <property name="hibernate.generate_statistics" value="true" />
         ...
         </properties>
    </persistence-unit> 
</persistence>

Um nicht die persistence.xml zu modifizierem, welche mit deployt wird, sollte man für die Testfälle besser die System Properties anpassen. Diese über das Kommandozeilenparameter -D

java -jar app.jar -Dhibernate.generate_statistics=true

oder in der IDE angegeben werden:

Spring Boot

Möchte man das Feature unter Spring Boot verwenden und trägt das so in die application.properties ein, dann wird das nicht funktionieren. Um die Properties an den JPA Provider (hier also Hibernate) zu übergeben, muss dieses in einen Prefix in der application.properties bekommen. In dem Fall von Hibernate ist das spring.jpa.properties.hibernate.

# Generate additional statistics. Do not use in production
spring.jpa.properties.hibernate.generate_statistics=true

Statistiken

Wenn man wie oben beschrieben die Generierung der Statistiken aktiviert hat, dann bekommt man eine nette Ausgabe auf der Konsole zu den abgesetzten Queries zur Datenbank.

40999 nanoseconds spent acquiring 2 JDBC connections;
25173 nanoseconds spent releasing 1 JDBC connections;
327100 nanoseconds spent preparing 3 JDBC statements;
1215257 nanoseconds spent executing 3 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
901269 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)

Queries

Queries formatieren

Normalerweise werden die Queries inline geloggt. D.h. je Zeile ein Query. Das macht insgesamt das Log gut lesbar, aber dafür sind die SQL Queries um so schlechter zu lesen. Auch hierfür bietet Hibernate eine Lösung an. Man kann die Ausgabe der Queries im Log formatieren lassen. Auch hier gilt natürlich der Grundsatz, dieses nicht in Produktion zu verwenden. Setzt man format_sql auf true, dann erhält man eine Mehrzeilige, formatierte Ausgabe der Queries in dem Log.

Hibernate: 
   create table customer (
       id bigint not null,
        lastname varchar(50),
        name varchar(50),
        primary key (id)
    ) engine=MyISAM

In der application.properties trägt man analog zu dem obigen Beispiel es mit dem Prefix spring.jpa.properties.hibernate ein.

# Prettify Hibernate SQL Queries in log
spring.jpa.properties.hibernate.format_sql=tru

Binding Parameter

Parameter der Queries

Mit den beiden oben genannten Methoden kann man schon sehr viel Informationen aus dem Log von Hibernate entnehmen. Eine wichtige Zutat um das Geheimnis der Blackbox Hibernate zu lüften fehlt allerdings noch. Man kann zwar jetzt die konkreten Queries im Log sehen, aber man kann nicht erkennen welche Parameterwerte gebunden sind.

Konfiguration von Spring Boot

Mit dem sogenannten Tracing kann man sich die Werte der Parameter mit in dem Log ausgeben lassen. Gesteuert wird das Ganze über den Hibernate Type. Dieser muss auf Trace gestellt werden:

# Show binding parameter values
logging.level.org.hibernate.type=trace

Die Ausgabe im Log erscheint dann wie folgt:

Hibernate: 
    insert 
    into
        customer
        (lastname, name, id) 
    values
        (?, ?, ?)
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Bernhard]
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Lisa]
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [11]

Link fehlerhaft

Aktuell besteht ein Bug in dem ansonsten so nützlichen Live Hover Informationen in Spring Tool Suite 4. Viele Anwendungen laufen ohne einen speziellen Application Context, so dass die Anwendung unter / erreichbar ist.

# Set application context path
server.servlet.context-path=/spring-mvc-thymeleaf-demo

Setzt man allerdings einen Application Context, dann ist der anzeigte Link fehlerhaft. Es fehlt der Path unter dem die Anwendung läuft, d.h. der Link funktioniert effektiv nicht (siehe Bild unten).

Bugfix

Mit dem nächsten Punktrelease ist Abhilfe in Sicht. Unter Issue 129 wird das Ticket bearbeitet. Dann sollte der Live Hover Bug behoben sein.

PasswordEncoder

Ohne weiteres Zutun legt Spring Security die Passwörter unverschlüssel ab. Das sollte heute aber nicht mehr der Fall sein, da zum Beispiel wenn die Passwörter der Benutzer in einer Datenbank gespeichert werden diese bei einem Einbruch als Plaintext vorliegen. Daher werden (sollten / es gibt immer wieder Beispiele die das Gegenteil beweisen) die Passwörter verschlüsselt abgelegt. Dieses kann zum Beispiel ein Hash des Passwortes sein.

Um Spring Security flexible zu gestalten, kann man zur Laufzeit einen PasswordEncoder setzen. Die Einstellungen die die Sicherheit einer Spring Anwendung betreffen, werden in einer WebSecurityConfigurerAdapter erweiterten Konfiguration vorgenommen. Hierfür muss die Einstellung für die Authentifikation geändert werden. Dafür die ist die Methode passwordEncoder() verantwortlich.

Als ein guter Kandidat für die verschlüsselte Ablage ist BCrypt. In Spring Security implementiert die Klasse BCryptPasswordEncoder den BCrypt Algorithmus.

Damit dieses per DI injeziert werden kann, benötigen wir eine Bean die uns den BCryptPasswordEncoder liefert.

@Bean PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Jetzt kann ein PasswordEncoder per @Autowired zur Laufzeit gesetzt werden und als Parameter für passwordEncoder() Funktion dienen.

@Autowired
private PasswordEncoder passwordEncoder;

Grundgerüst einer Anwendung

Dependency

Als Abhängigkeit ist natürlich spring-boot-starter-security einzufügen.

dependencies {
    // Spring Boot
    ...
    implementation('org.springframework.boot:spring-boot-starter-security')
    ... 

Konfiguration WebSecurityConfigurerAdapter

Hier ein sehr simples Beispiel mit einer in Memory Benutzerauthentifizierung. Ich glaube jeder hat schon einmal die verwendeten Benutzernamen und Passwörter gesehen… 🙂

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .passwordEncoder(passwordEncoder)
            .withUser("user")
            .password("$2a$10$9z954e6ETmwauiiBbwCNbOwU90UnUrh9hpRUWwNmWUx6aFic2nE46") // user
            .roles("USER")
                .and()
            .withUser("admin")
            .password("$2a$10$Tljz0HJByMV97Y92B65QBu0TckqbJDlT1kgmibMijhbMtpHPORanK") // admin
            .roles("ADMIN");
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/admin/**").hasAnyRole("ADMIN")
                .anyRequest().hasAnyRole("USER").and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/allcustomers")
                .permitAll();
    }
}

SQLite Dialect

Um Hibernate zur Zusammenarbeit mit SQLite zu bewegen, gibt es seit kurzem ein Release für https://github.com/gwenn/sqlite-dialect auf Maven. D.h. es kann direkt über Maven Central bezogen werden. Früher wurde das Programm nur über Jitpack.io verteilt, was zusätzliche Konfiguration im Buildskript erforderte.

Nun kann man die beötigten Abhängigkeiten mit 2 Zeilen hinzufügen:

// SQLite
compile('org.xerial:sqlite-jdbc:3.25.2')
compile('com.github.gwenn:sqlite-dialect:0.1.0')

Jetzt muss nur noch Konfiguration angepasst werden:

spring:
  datasource:
    url: jdbc:sqlite:empty.db

oder

spring.datasource.url=jdbc:sqlite:empty.db

Nun sollten in der Anwendung erstellte Repositories auf der SQLite Datenbank arbeiten.