Lombok 1.18.12 erschienen

Die neue Version 1.18.12 von Project Lombok ist veröffentlicht worden.

Typ Beschreibung
PLATFORM Support für JDK13 (yield in Switch Ausdrücken)
PLATFORM Support for JDK14 (Mit pattern match instanceof Ausdrücken).
FEATURE In der lombok.config können jetzt weitere config Dateien eingebunden werden und das sogar von .zip oder .jar Dateien.
FEATURE Mit @Builder(setterPrefix = “set”) lässt sich das Prefix für die Setter Methoden ändern. Dieses wird aber nicht empfohlen, aber man hat die Möglcihkeit, falls es eine Library so benötigt.
FEATURE Wenn man @Builder’s @Singular verwendet, dann wird auch ein Plural generiert.
FEATURE Lombok kann nullity annotations eingfügen wo es relevant ist. Dei Einstellung wird in der lombok.config vorgenommen und wird auf die return value von toString, withX, chainable setX, static constructors, build, builder, usw und dem Parameter von equals, canEqual.
BUGFIX Fix für das Sonarplugin
BUGFIX lombok.experimental.Wither wurde in lombok.With umbenannt.

Alle Änderungen sind im Changelog zu finden.

Spring Boot

Ab dem Release 2.3.0 M2 ist das Update von Lombok 1.18.12 enthalten.

JSoup 1.12.2

Nach 9 Monaten Entwicklungszeit steht nun die neue Version 1.12.2 der beliebten JSoup Java HTML parsing Library bereit. Es wurden Verbesserungen im Bereich der Performance vorgenommen und jede Menge Bugsfixes eingepflegt.

  • has kann mit relative Selektoren umgehen
  • keepalive funktioniert nun, wenn der Content via body() oder bodyAsBytes() geholt wird
  • Bulk insert von Child Node massiv beschleunigt

Und vieles mehr. Alle wichtigen Änderungen sind in der Changes Datei zu finden.

Die neue Version einbinden

Das neue Release von JSoup steht auf Meven central bereit.

<dependency>
  <!-- jsoup HTML parser library @ https://jsoup.org/ -->
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.12.2</version>
</dependency>

Gradle
// jsoup HTML parser library @ https://jsoup.org/
compile 'org.jsoup:jsoup:1.12.2'

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
}

Die neue in Memory Datenbank

Microstream ist die neue in Memory Datenbank für Java. Aus dem Hause XDev stammt diese revolutionäre Datenbank. Nachdem im Frühjahr das Produkt umbenannt worden ist, heißt es nicht mehr JetstreamDB sondern Microstream. Die Microstream Datenbank ist kostenlos und kann frei verwendet werden.

Was ist Microstream?

Microstream ist ein Framework das Java Objekte ultraschnell serialisieren und desialisieren des kann. Das macht sich Microstream zu nutze und umgeht damit unvermeidbare Netzwerklatenzen, die ansonsten bei klassischen RDBMs entstehen.

Wie funktioniert Microstream?

Es gibt eine Root Entität. Diese enthält alles was in der Datenbank gespeichert werden soll. Es gibt also hier kein Datenbank Schema oder ähnliches. Die Java Objekte sind die Entitäten. Das war es. Alles was sich also in dem Objektgraph unterhalb der Root Entität befindet, kann persistiert werden.

Was ist mit Queries?

Abfragen, wie man es bei einer normalen RDBMs kennt, gibt es nicht. Und braucht man auch nicht. Man verwendet einfach das Streaming API von Java und erreicht Wahnsinnig schnelle Abfragen in Millisekunden Bereich.

Demoanwendung

Es soll hier in einer kleinen Demoanwendung die Verwendung der Microstream API veranschaulicht werden. Ich verwende hier das SpringBoot Framework in der aktuellsten Version 2.1.4. Die Anwendung erzeugt bei jedem Start eine neue Einladung. Die Einladungen bestehen aus einer ID und einem Namen. Super einfach. Die ID wird hierbei immer um einen erhöht. Der Name ist hier statisch. Um den Code vom unnötigen Boilerplate frei zu halten, verwende ich Lombok.

Abhängigkeiten einbinden

Es muss das Repository von microstream eingebunden werden. Die Group ID .heißt one.microstream und es muss das Artefakt storage.embedded in der Version 01.00.00-MS-RC1 Hier das Gradle Buildscript:

repositories {
    mavenCentral()
    maven { url 'https://repo.microstream.one/repository/maven-releases/' }
}


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation('one.microstream:storage.embedded:01.00.00-MS-RC1')
    annotationProcessor('org.projectlombok:lombok')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Die Entität

Die Invocartion stellt die Entität des Repositories dar. Sie besteht aus der ID und dem Namen der Einladung.

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Invocation {
    private Long id;

    private String name;
}

Pretty simple, aber erfüllt für die Demo genau seinen zweck.

Insert new Invocation

Der CommandLineRunner wird beim Starten des SpringBoot Container ausgeführt. Es sind für die Verwendung der in Memory Datenbank zwei statische Variable nötig. Die eine enthält das Root des Objektgraphen hier ROOT genannt und eine STORAGE die für das Persistieren auf der Festplatte.

Wie gelange jetzt die Daten in den Objektgraphen?

@Component
@Log
public class InitDatabase implements CommandLineRunner {
    // to stop spring boot application
    @Autowired
    private ConfigurableApplicationContext cac;

    // this is the ROOT of object graph
    private static Reference<InvocationRepository>ROOT = 
        X.Reference(new InvocationRepository());

    // Storage manager
    private static EmbeddedStorageManager STORAGE = 
        EmbeddedStorage.start(ROOT);

    @Override
    public void run(String... args) throws Exception {
        // load all into a local list
        var rootData = ROOT.get();
        List<Invocation> invocations = rootData.getInvocations();

        // print size of Repository
        log.info("size: " + rootData.size());

        // add a new Incocation to Repository
        Invocation invocation = new Invocation();
        invocation.setId(Long.valueOf(rootData.size()+1));
        invocation.setName("Test");
        invocations.add(invocation);
        invocations.forEach(i-> log.info(i.toString()));

        // store
        STORAGE.store(ROOT.get().getInvocations());

        // close
        STORAGE.shutdown();

        // shutdown SpringBoot container
        cac.close();
    }
}

Das Repository

Das Repository ist sehr einfach gehalten.

public class InvocationRepository {
    private final List<Invocation> invocations = new ArrayList<>();

    /**
     * get a List of Invocations
     * @return List of Invocation
     */
    public List<Invocation> getInvocations() {
        return invocations;
    }


    /**
     * Size of Repository
     * @return
     */
    public int size() {
        return invocations.size();
    }
}

Einschränkungen

Leider gibt es mit dem Classloader ein Problem, wenn man die DevTools von SpringBoot mit einbindet. Daher habe ich diese aktuell noch nicht eingebunden.

Neue Version 1.18.6 von Lombok

Am 12.02.2019 ist die Neue Version von Lombok erschienen. Neben einigen Bugfixes enthält das Update initialen Support für die EA von JDK 12 und weiteren neuen Features wie zum Beispiel die Generierung von Javadocs.

Die wichtigsten Änderungen im Einzelnen

  • FEATURE: Javadoc an Feldern werden nun auch an den Builders Setter Methoden kopiert.
  • FEATURE: Es gibt jetzt einen Konfigurationseinstellung um das Verhalten von toString() die den super() Konstruktor aufruft zu beeinflussen
  • ENHANCEMENT: Verbesserte Konsolenausgabe wenn toString auf Enums angewandt wird. Es wird nun der Name der Konstanten ausgegeben
  • Unterstütung für JDK12. Aufgrund von Änderungen an Switchstatements gab es Probleme mit Lombok
  • BUGFIX: @Delegate in Zusammenhang mit @NonNull Annotation verursacht nun unter JDK8 keinen Fehler mehr
    BUGFIX: Delombok funktionierte seit der Version 1.18.4 nicht korrekt, weil ein NoClassDefFoundError Fehler ausgegeben worden ist

Die aktuelle Version von Lombok.jar befindet sich wie immer im Downloadbereich von dem Projekt.

Szenario

Häufig hat man eine Liste von Werten in einem String die durch Komma getrennt sind. Diese Wertelisten möchte man natürlich in einer Liste weiterverarbeiten. Die Methode .split() dient dazu, aber man erhält ein Array von Strings und das lässt sich nicht so schön verwenden.

Lösung

Die Klasse Stream bietet mit der Methode .of() uns eine Möglichkeit dieses Array in einen Stream zu wandeln. Dieses mappen wir in neue String Objekte und verwandeln es mit der finalen Methode collect mit dem statischen Collectors.toList() Argument in eine Liste.

public static List<String> split(String string){
    return Stream.of(string.split(","))
      .map (element -> new String(element))
      .collect(Collectors.toList());
}

Was ist die GraalVM

Die GraalVM ist eine neuartige Virtuelle Maschine die hochoptimierten Code erzeugen kann. Die VM ist Polyglot, das heißt sie ist nicht auf eine Programmiersprache begrenzt und auch nicht auf ein Zielsystem festgelegt. Es lassen sich JavaScript Programme für Oracle Datenbank kompilieren und es ist auch schon MySQL in Planung.

Die Version 1.0.0 ist kurz vor dem Release und es ist Release Candidate 9 aktuell. Da GraalVM völlig OpenSource ist, ist auch jeder der interesse an dem Projekt hat, aufgerufen das auszuprobieren und Fehler und Verbesesserungsvorschläge zu melden.

In diesem Betrag stelle ich kurz einige der Möglichkeiten von GraalVM vor. Ich beleuchte Polyglotte Programmierung und das Kompilieren in native Code. Auf Details zur Implementierung der VM und andere technische Hintergründe gehe ich in diesem Beitrag nicht ein.

Installation

Wie immer hier die nötigen Pakete die unter Manjaro/Arch Linux zu installieren sind.

yaourt --noconfirm -S graal-bin graalpython-bin truffleruby-bin npm
yaourt --noconfirm -S fastr-bin

Installation überprüfen

$ sudo archlinux-java status
Available Java environments:
  java-10-openjdk
  java-11-openjdk
  java-8-graal
  java-8-openjdk (default)

Es muss noch die Default VM geändert werden.

sudo archlinux-java set java-8-graal

Nun sollte auch das Graal Component Updater (gu) im Pfad vorhanden sein.

$ gu
GraalVM Component Updater v1.0.0

Usage: 
        gu info [-cFlprstuv] <param>      prints info about specific component (from file, URL or catalog)
        gu available [-lv] <expr>         lists components available in catalog
        gu install [-0cfFnorvyxY] <param> installs a component package
        gu list [-clv] <expression>       lists installed components, or components from catalog
        gu uninstall [-0fxv] <id>         uninstalls a component
        gu rebuild-images                 rebuilds native images. Use -h for detailed usage

Common options:
...

Mit dem Graal Updater lassen sich Komponenten nachinstallieren und updaten. Da wir für die Installation den Paketmanager Pacman verwendet haben und die Installation im Systemverzeichnis liegt, schlägt ein Update oder eine Installation fehl.

Polyglottes Beispiel

Auf der Homepage von Graal gibt es ein paar Beispiele. Ein Beispiel was mich am meisten fasziniert, ist das Beispiel wo die Anwendung der Polyglotten Programmierung gezeigt wird.

Vorbereitung

Zunächst müssen die Quelltexte ausgecheckt werden. Dazu wird die übliche Prozedur mit GIT clone verwedendet. Danach rufen wir das Buildskript auf, dass per NPM die benötigten Dependencies installiert.

git clone https://github.com/graalvm/graalvm-demos
cd graalvm-demos/polyglot-javascript-java-r
./build.sh

Quelltext des Servers

Die server.js ist sehr kurz und daher zeige ich hier den gesamten Quelltext des Servers.

/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const express = require('express')
const app = express()

const BigInteger = Java.type('java.math.BigInteger')


app.get('/', function (req, res) {
  var text = 'Hello World from Graal.js!<br> '

  // Using Java standard library classes
  text += BigInteger.valueOf(10).pow(100)
          .add(BigInteger.valueOf(43)).toString() + '<br>'

  // Using R methods to return arrays
  text += Polyglot.eval('R',
      'ifelse(1 > 2, "no", paste(1:42, c="|"))') + '<br>'

  // Using R interoperability to create graphs
  text += Polyglot.eval('R',
    `svg();
     require(lattice);
     x <- 1:100
     y <- sin(x/10)
     z <- cos(x^1.3/(runif(1)*5+10))
     print(cloud(x~y*z, main="cloud plot"))
     grDevices:::svg.off()
    `);

  res.send(text)
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Was heißt Polyglotte Programmierung?

Wenn man den Quelltext des Server sich anschaut, dann erkennt man das Polyglot.eval() aufgerufen wird. Mit .eval können Ausdrücke von anderen Sprachen (hier R) evaluiert werden und das Ergebnis kann normal weiterverarbeitet werden. Auch der Zugriff auf Java Klassen funktioniert, ohne eine spezielle Anweisung.

Starten des NodeJS Server

Der Server der mitgelieferten NodeJS Version in dem graal-bin Package gestartet. Der Parameter –polyglot ermöglicht das die Polyglotte Ausführung möglich wird. Der Parameter –jvm sorgt dafür, dass GraalVM zum Zuge kommt und die Optimierungen durchgeführt werden.

/usr/lib/jvm/java-8-graal/jre/bin/node --polyglot --jvm server.js

Nach dem Starten kann man die Anwendung unter http://localhost:3000 aufrufen.

Debuggen der Anwendung

Die Anwendung läßt sich im Chrome Devtools Debugger debuggen. Man muss den Server nur mit dem Parameter –inspect starten.

$ /usr/lib/jvm/java-8-graal/jre/bin/node --inspect --polyglot --jvm server.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ea4a92b-3aa8163241f

Nun sehen wir in der Konsole die URL mit der man die Anwendung debuggen kann.

Hinweis: Aktuell kennt KDE das Custom Protocol nicht, so dass die URL aus der Konsole heraus nicht direkt geöffnet werden kann. Es läßt sich bestimmt ein Protocol Handler registieren, so dass die URI direkt geöffnet werden kann.

Jetzt muss man nur den Ordner mit den Quelltexten in den Workspace ziehen und kann dann den Quelltext des server.js öffnen und zum Beispiel einen Breakpoint setzen und Variablen inspizieren.

Nativer Code

Die GraalVM kann aus einer JavaVM und einem JAR-File ein Blob erzeugen, der sich genauso verhält wie eine Anwendung die in einer “normalen” Java VM läuft. Die Startupzeiten sind dann extrem gering, im Vergleich zu den Startupzeiten einer HotSpot VM. Dieses macht Java Anwendungen noch interessanter in Containern, wo schnell eine neue Instanz einer Anwendung gespawnt werden muss.

Hierfür ist ein Sub Projekt der GraalVM zuständig. Es nennt sich SubstrateVM. JVM Ökosystem muss noch an GraalVM angepasst werden. Für Spring ist mit der Version 5.1 initialer Support für die neue VM vorhanden.

Übersetzen eines JAR

Für die Übersetzung wird in der GraalVM ein Programm native-image mitgeliefert, welches eine JAR untersucht und dann ein Blob erzeugen kann. Der Blob kann dann agnz normal ausgeführt werden.

native-image -jar /Pfad/zum/JAR

Limitierungen

Die SubstrateVM befindet sich noch in der aktiven Entwicklung und daher werden noch nicht alle Funktionen vollständig unterstützt (siehe hier).

In nativen Code übersetzen

Das bauen von nativen Anwendungen mit native-image funktioniert mit Spring Boot Anwendungen noch nicht, weil das dynamische laden von Klassen wohl (noch) nicht voll umfänglich unterstützt wird.

Hier die Fehlermeldung, wenn versucht ein Spring Boot Application zu übersetzen.

$ native-image -jar ./build/libs/spring-data-jdbc-demo-0.0.1-SNAPSHOT.jar 
Build on Server(pid: 8784, port: 42925)
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]    classlist:     334.59 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        (cap):   3,266.94 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        setup:   3,846.85 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]     analysis:   4,875.69 ms
error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
        at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1118)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory): 
        at java.net.URL.setURLStreamHandlerFactory(URL.java:1110)
        at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:401)
        at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:391)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
        at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
        at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:164)
        at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)

Error: Processing image build request failed

Lombok 1.18.4

Am 30.10.2018 ist die neue Version 1.18.4 von Lombok erschienen. Wie immer sind einige Bugs in Zusammenhang mit Java Versionen größer 9 behoben worden. Es sind aber auch inkompatible Änderungen vorgenommen worden. Das Jar kann von hier runtergeladen werden.

Lombok unterstützt nun Eclipse Photon. Der lombok.patcher verwendete OpCodes.ASM4, welches nun nicht mehr von Eclipse verwendet wird. Eclipse verwendet nun invoke dynamic.

Inkompatible Änderungen

  • FieldNameConstants wurden aufgrund eines Tickets neu redesigned. Das Feature bleibt weiterhin als experimentel gekennzeichnet, da noch nicht klar ist, ob es wirklich eine Verbesserung der Lesbarkeit mit sich bringt. Dokumentation FieldNameConstants.

  • Lombok kopiert nun immer bestimmte Annotationen, damit das Verhalten nicht verloren geht. Z.B. @NonNull.

Weitere Änderungen und neue Features

Alle Änderungen sind im https://projectlombok.org/changelog zu entnehmen.