Custom Starter

Spring Boot beinhaltet eigene Starter. Ein Starter ist eine Sammlung von Dependencys die aufeinander abgestimmt sind. Diese werden über das Dependency Management von dem Parent POM bzw. über ein Gradle Plugin mit den passenden Versionen verwaltet. So kann man sehr einfach und fehlerfrei Dependencys in eine Spring Boot Anwendung einbringen.

Nun bietet Spring Boot aber nicht für alle 3rd Party Libraries einen Spring Boot Starter. Es werden aber (nach Pull Request) auf der GitHub Seite Custom Starters gelistet.

Spring-Boot-CLI Tools

Spring Boot bietet auch ein Kommandozeilewerkzeug. Die Installation unter Manjaro erfolgt mit

yaourt --noconfirm -S spring-boot-cli

sehr schnell.

Password encoden

Nun kann mit dem spring Kommando einen Hash für ein Passwort schnell auf der Kommandozeile erstellen:

$ spring encodepassword meinGeheimesPasswort
{bcrypt}$2a$10$ip/Y7fBiC4Eh7vyvltUuruJnUpZeDD44JU8rbrvFfV94QIIzZkFKm

Spring Tools 4.4.1

Am 24.10.2019 ist die neue Version der Spring Tools 4.4.1 erschienen. Neben Bugs die behoben worden sind, gibt es Änderungen bei den Live Informationen (s.u.).

Spring Boot

  • In Eclipse funktioniert nun das Goto Symbol auch in XML Bean Dateien
  • Der Fehler das in der Autovervollständigung in der application.properties manchmal Zeichenmüll hinterlässt wurde behoben
  • Die Performance für das Scannen der XML Symbole wurde verbessert
  • CVE-2019-18212 und CVE-2019-18213 wurden behoben

Live Data anzeigen

Mit Spring Boot 2.2 wurde das JMX per default abgeschaltet. D.h. möchte man weiterhin die Liveinformationen der Anwendung angezeigt bekommen, so muss man JMX Actuator Endpoint wieder mit -Dspring.jmx.enabled=true aktivieren. Ab Spring Tools 4.2.0 wird der in Eclipse integrierte Spring Boot Lauch Configuration diese Einstellung automatisch setzen, so dass das alte Verhalten wiederhergestellt ist und die Live Informationen abgerufen werden können.

Das normale Verhalten von VLC ist das der Screensaver und somit auch das Hibernating verhindert wird, wenn man ein Video schaut. Dieses ist aber nicht der Fall, wenn man einen Internetradiostream hört. Dann ist voreingestellt das keine Visualisierung dargestellt wird und das Fenster von VLC nur das Icon von VLC anzeigt. Dieses hat zur Folge das VLC nicht den Screensaver blockiert. Den gewünschten Effekt kann man also erreichen in dem man eine Visualisierung aktiviert.

Core API revision

Java 8 basierend

Die gesamte Codebasis wird seit 5.0 auf Java 8 umgestellt. Dieses wurde nun weiter vorangetrieben. Teilweise jetzt erst wirklich auf Java 8 umgestellt.

Java 8 API Typen

Rückgabetypen wurden Stream, Duration, Instant, Executable und so weiter eingeführt.

Interfaces mit Defaultmethoden

Interfaces nutzen nun Default Methoden, um neue Funktionen bereitzustellen ohne dabei die Rückwärtskompatabilität zu brechen.

Nullability

In der gesamten Codebasis werden alle Methoden mit @NonNull oder @Nullable deklariert. Dadurch können die IDEs wie Eclipse und IntelliJ die Interaktion mit dem Spring Framework validieren. Viele der vorhandenen Null Checks konnten somit in dem Spring Framework entfernt werden.

Eigene APIs die auf Spring Framework Rückgabewerten basieren sollten nun auch von den Annotationen Gebrauch machen und somit im allgemeinen den Code von weiteren Null Checks befreien.

Performance Tuning

Component Scanning

Das Scannen des gesammten Classpath beim Programmstart kann viel Zeit in Anspruch nehmen. Dabei werden alle Klassen nach der Annotation @ComponentScan durchsucht. Im allgemeinen wird man die Startupzeit durch Angabe eines base packages verkürzen, da nicht mehr alle Klassen durchsucht werden müssen. Alternativ kann ein Scanning auch komplett verhindert werden, in dem man alle Komponentenklassen aufzählt.

Seit Spring Framework 5 kann man einen Annotationsprozessor zur Kompilierzeit verwenden (spring-context-indexer). Dieser erstellt eine META-INF/spring.components per JAR. Diese wird automatisch zur Laufzeit ausgewertet.

Component Model

Am effizientesten werden Komponenten in einer reinen Funktionalen Methode registriert. Dadurch entfällt das Component Scanning und es wird auch kein Reflection für die Factory Methoden verwendet. Ferner werden auch keine Annotationśkonfigurationen (Annotationpostprocessor) verwendet.

ProxyBeanMethods

Mit @Configuration(proxyBeanMethos=false) kann nun verhindert werden dass zur Laufzeit CGLIB Subklassen erzeugt werden. Dieses wird für die Imageerstellung mit der GraalVM benötigt.

Nachteil: Kein Abfangen von Cross @Bean Calls mehr möglich!

3rd Party Libs

Spring Boot Data unterstützen spring.data.jpa.repositories.bootstrap-mode=deferred. Damit werden der ORM Hibernate lazy initialisiert. Das ist ein nicht unerheblicher Anteil der Startupzeit einer Spring Anwendung. Die Initialisierung wird nun bei dem Ersten Zugriff durchgeführt.

Hibernate wurde auf 5.4.5 aktualisiert, welches interne Performance Steigerungen und weniger Speicherverbrauch zur Folge hat. Desweiteren wurde der Umgang mit Bytecode verbessert (Voraussetzung für lazy loading).

Jackson wurde auf mindesten 2.9.7 angehoben. Auch hier wurde die Startupzeit sowie die Responsezeit beim parsen von JSON verbessert.

Nur durch die Anhebung des Spring / Spring Boot Frameworks lassen sich Performancesteigerungen also erzielen.

GraalVM

Die Verwendung der GraalVM ist bereits seit Spring Framework 5.1 in Vorbereitung. Es werden unnötige interne Reflection vermieden. Nun ist in der Zwischenzeit set Spring Framework 5.1 das 19 GA von GraalVM erschienen. Spring Framework 5.2 kann mit native Image verwendet werden, aber es müssen die explizite Reflection Konfiguration und benötigte Kommandozeilenparameter angegeben werden damit ein Image erzeugt werden kann. Ziel der Entwicklung für Spring Framework 5.3 ist ein automatisches Setup (ootb) der Reflection Einstellungen durch eine Custom Graal Konfiguration zu ermöglichen.

Neues in Spring Data

In Spring Data Moore Release gibt es wieder jede Menge neues. Einiges davon stelle ich kurz in diesem Artikel vor. Einen großen Anteil hat die reaktive Programmierung inne.

Reaktives Transactional

Um Methoden Transaktionssicher zu machen, genügt es nun wie man es aus der imperativen Programmierung von Spring Data kennt die Annotation @Transactional an der Methode zu annotieren.

@Transactional
public Mono<Type> doSomething(Long id) {
    return repo.findById(id);
}

Elasticsearch reaktiv

Auch Elasticsearch hat eine reaktive API bekommen.

Die Vorteile der Reaktiven Programmierung sind das die Ergebnismenge nicht auf einmal in den Speicher geladen werden muss und sobald die ersten Daten bereitstehen werden sie im Flux zur Weiterverarbeitung bereitgestellt. Dieses ist besonders bei großen Datenmengen von Vorteil.

Es muss nur die Konfiguration vorgenommen werden. Der Typ ReactiveElasticsearchClient basiert auf dem WebClient.

class Config extends AbstractReaktiveElasticsearchConfiguration {
    @Bean
    @Override
    public ReactiveElasticsearchClient reactiveClient() {
        return ReactiveRestClients.create(localhost());
    }
} 

Nun kann das Template für die Abfrage genutzt werden. Als Ergebnis erhält man einen Flux.

@Autowired
ReactiveElasticsearchTemplate template;

// Query bauen
Criteria criteria = new Criteria("thema").contains("natur");
CriteriaQuery query = new CriteriaQuery(criteria);

// komplett reaktiv 
Flux<Dokument> result = template.find(query, Dokument.class);

Alternativ kann man Spring Data typisch eine reaktives Repository erstellen und Derived Queries darauf anwenden.

interface TestRepo extends ReactiveCrudRepository<...> {
     Flux<Dokument> findAllByKeywords(...);
}

@Autowired
TestRepo repo;

Flux<Dokument> result = repo.findAllByKeywords("natur");

Reactive QueryDSL

interface TestRepo extends ..., ReactiveQuerydslPredicateExecutor<...> {}

@Autowired
TestRepo repo;

//...
Predicate predicate = QCustomer.customer.lastname.eq("Pfau");
Flux<Customer> result = repo.findAll(predicate);

Performance in Spring Data

Eine Neuentwicklung sind die spring-data-dev-tools. Diese erlauben es JMH Benchmarks in JUnit Tests auszuführen. JMH dient als Messinstrument für die Performanceoptimierung in Spring Data.

Hier die Ergebnisse der Optimierungen zwischen RC1 und RC2:

Methode RC1 RC2 % Steigerung
findAll 52.213 61.244 17%
findByTitle 74.192 123.040 66%
repositoryByTitle 70.344 120.333 71%
repositoryByTitleDeclared 65.236 111.760 71%

OpenAPI 3.0.2

OpenAPI 3.0.2 ist der Nachfolger von Swagger API in der Version 2. OpenAPI erfreut sich immer der Beliebtheit und rückt den API Frist Designansatz immer mehr in den Focus. Der Contract der API ist der zentrale Dreh- und Angelpunkt. Es eigent sich gleichermaßen Server und Clientcode in beliebigen Sprachen über Codegeneratoren zu erstellen.

Eclipse Tools für OpenAPI

Für Eclipse gibt es zwar ein paar Tools, jedoch sind einige davon kommerziell und daher nicht in meinem Interesse. Diese sind aber durchaus gut für Anfänger, da sie dass Bearbeiten der API in einer UI in Eclipse ermöglichen, ohne das man wirklich OpenAPI in all seinen Fassetten kennen muss.

KaiZen OpenAPI Editor

Zunächst möchte ich den unter der EPL frei verfügbaren Editor KaiZen vorstellen. Neben den Syntaxhighlighting bietet der Editor kontextbasierte Codevervollständigung, so dass man immer weiß was an der Stelle erlaubt ist.

https://marketplace.eclipse.org/content/kaizen-openapi-editor

Codewind Codegenerator

Das Projekt Codewind ist noch sehr jung und befindet sich im Alphastadium. Ermöglicht aber die Codegenerierung anhand der OpenAPI spec anzustoßen.

https://marketplace.eclipse.org/content/codewind-openapi-tools

Dynamische Liste von Oder Bedingungen

Gegeben sei eine Map parameters die alle Konditionen enthält die abgefragt werden sollen. In der Map ist der Key gleich dem Feldnamen in der Tabelle und der Value der Wert mit dem der Inhalt der Spalte verglichen werden soll.

Map<String, Object> params = ...;

Jetzt kann man die Oder Bedingung mit Jooq dynamisch zusammensetzen.

Condition result =
params
    .entrySet()
    .stream()
    .reduce(
        DSL.noCondition(),
        (condition, entry) -> condition.or(
            // Hier wird mit Hilfe Table.field() das Feld aus der Tabelle nach ihrem Namen gesucht.
            // Achtung: Nicht vorhandene Felder führen zu einer NullPointerException.
            ((Field<Object>) E.field(entry.getKey())).eq(entry.getValue())
        ),
        Condition::or
    );

Für alle Einträge in der Map (EntrySet) wird ein Stream erzeugt. Jeder Entry wird mit der Reduce Methode mit Condition::or verknüpft. Initialisiert wird die Reduce Operation mit DSL.noCondition(). Also wenn die Map leer ist, dann wird noCondition() zurückgegeben.

Mit Gradle Java kompilieren

Wenn man mit ./gradlew einen Build anstößt, so verwendet Gradle immer den Java Compiler den es in der Environment Variable JAVA_HOME findet. Das ist normalerweise auch ok so, aber manchmal möchte eine IDE oder im Allgeimeinen eine ältere Version verwendet, aber Quelltexte mit einer höheren Java Version übersetzen. Dann steigt Gradle aus, da der Compiler dieses nicht kompilieren kann.

Crosscompiling

Gegeben sei ein Quelltext der Java 12 voraussetzt. Dieser soll nun bei gegebener JVM OpenJDK 64-Bit GraalVM CE 19.2.0.1 übersetzt werden. Dazu muss man Gradle ein anderes Executable javac gesetzt werden. Folgender Code setzt für die Tasks AbstractCompile, Javadoc, JavaExec, Test den Javac in dem Java Home /usr/lib/jvm/java-12-openjdk.

sourceCompatibility = 12

def javaHome="/usr/lib/jvm/java-12-openjdk"
def javaExecutablesPath = new File(javaHome, 'bin')
def javaExecutables = [:].withDefault { execName ->
    def executable = new File(javaExecutablesPath, execName)
    assert executable.exists(): "There is no ${execName} executable in ${javaExecutablesPath}"
    executable
}
tasks.withType(AbstractCompile) {
    options.with {
        fork = true
        forkOptions.javaHome = file(javaHome)
    }
}
tasks.withType(Javadoc) {
    executable = javaExecutables.javadoc
}
tasks.withType(Test) {
    executable = javaExecutables.java
}
tasks.withType(JavaExec) {
    executable = javaExecutables.java
}

KeyCloak

Der KeyClaok von JBoss ist ein IM (Identity Management) Tool. Mit KeyClaok lassen sich einfach SSO (Single Sign on) Lösungen mit verschiedenen Protokollen umsetzen. Dazu zählen das etwas ältere SAML2 genauso wie der aktuelle quasi Standard OpenID Connect alias OIDC.

Docker

Die Verwendung von KeyCloak mit Docker ist sehr einfach. Wie man es von Docker gewohnt ist, wird keine weitere Installation benötigt.

docker run -d -p 9001:8080 -e DB_VENDOR="h2" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak jboss/keycloak    

Hier wird die interne in Memory Datenbank H2 explizit verwendet und die Anwendung auf dem Port 9001 exposed. Als Benutzername und Passwort wird admin festgelegt.

Docker mit Datenbank auf dem Host verwenden

KeyCloak kann mit verschiedenen Datenbanken betrieben werden. Diese können mit dem Docker Container in einem isolierten Netzwerk zusammen mit KeyCloak betrieben werden. Manchmal ist aber auch schon eine Datenbank auf dem Host vorhanden und diese soll auch benutzt werden. In diesem Beispiel möchte ich den KeyCloak mit einer auf dem Host laufenden MariaDB verwenden.

Vor dem ersten Start

Wichtig ist das beim Anlegen des Schemas auf latin2 geachtet wird. Muss vor dem Starten des Containers erfolgen, sonst erhält man beim Starten des Containers Fehlrermeldungen, dass die Rowsize nicht ausreiched sei. KeyCloak benötigt die 4096 Zeichen, weil hier die Key abgespeichert werden.

Den Host bekannt machen

Mit der –add-host Anweisung wird in dem Container ein Eintrag in der /etc/hosts Datei erzeugt. Hier also unser Hostname des Hosts und dessen statische IP Adresse. In diesem Fall hat der Host die statische Adresse 192.168.2.101. Sind Syntax ist hierbei Hostname:IP Adresse.

docker run --add-host=manjaro-server:192.168.2.101 --rm -d -p 9001:8080 -e DB_VENDOR=mariadb -e DB_ADDR=manjaro-server -e DB_DATABASE=keycloak -e DB_USER=root -e DB_PASSWORD=spfau -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak jboss/keycloak

Hinter NginX als Reverse Proxy mit SSL

Für die SSL Verschlüsselung kommt NginX als Reverse Proxy zum Einsatz. Die Konfiguration ist pretty straight forward.

Einstellungen in der NginX Konfiguration

# HTTP -> HTTPS
server {
    listen 80;
    server_name keycloak.xxx.org;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name keycloak.xxx.org;

    # SSL
    include /etc/nginx/ssl/*.xxx.org;

    # logging
    error_log /var/log/nginx/keycloak_error.log debug;
    access_log /var/log/nginx/keycloak_access.log;

    #
    # Serve KeyCloak
    #
    location / 
    {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        # static IP address of KeyCloak host
        proxy_pass              http://192.168.2.101:9001/;

        proxy_read_timeout      600s;
        proxy_send_timeout      600s;
    }
}

Anpassung des KeyCloak Docker Containers

Es muss das Proxy Address Forwarding aktiviert werden. Dazu wird in dem Container mit der JBoss-Cli die Einstellung geändert. Diese kann von außen mit Docker exec aufgerufen werden. Nun noch den Container neu starten, dann sollte der KeyCloak mit SSL über den Reverse Proxy abgesichert sein.

docker exec keycloak /opt/jboss/keycloak/bin/jboss-cli.sh --connect "/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding, value=true)"