Bei Major Releases ein Upgrade durchführen

Bei größeren Updates der Datenbank, kann es sein das sich auch Strukturen auf der Platte ergeben, so dass unter Umständen dann zu Fehler kommt. Zum Beispiel die MySql Workbench kann die Tabellen in den Schemata auf einmal nicht mehr lesen oder ähnliches.

Dann wird es spätestens Zeit die Strukturen anzupassen. Dazu bringt MySql und MariaDB das Tool mysql_upgrade mit. Auf der Konsole kann so einfach die Datenbankstruktur auf der Festplatte auf den aktuellen Stand angepasst werden.

mysql_upgrade -u root -p

Jetzt noch kurz das Passwort für Root angeben und kurz abwarten. Dann sollten auch alle seltsamen Fehler (siehe oben) verschwunden sein.

Alte Methode um einen bestehenden Index zu verändern

Alte Methode

Unter Verwendung der alter table Anweisung musste zunächst der Index gelöscht und dann neu erstellt werden.

ALTER TABLE `articles` 
  DROP INDEX `selled`,
  ADD INDEX `selled` (`selled` ASC) 

Neue Methode die als Alias implementiert ist

Mit create or replace (ab MariaDB 10.1.4) kann der Index angelegt werden, wenn er noch nicht existiert oder modifiziert werden.

CREATE OR REPLACE 
  INDEX selled 
  USING BTREE 
  ON `articles` (`selled` ASC) 
  COMMENT 'Abfragen zu Verkaufsstatistiken...';

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.

Projekt reactor

Das Spring Framework nutzt für den Starter spring-boot-starter-webflux das Projekt reaktor. Dieses verleiht dem Spring Framework reaktive Streams. Das Framework Reactor ist von Hause aus Multithreading fähig, so dass es dem Entwickler hier hilft.

Warum reaktiv?

Meine Anwendung ist doch asynchron und verwendet Threads! Warum soll sie jetzt auf einmal reaktiv sein? Was ist der Unterschied zwischen asynchron und reaktiv? Diese Fragen stellte ich mir auch. Nun ja, einer der Unterschiede ist, dass das Framework ein Backpreasure kennt. Dieser “Gegendruck” verhindert zum Beispiel das ein Subscriber mit den vom Publisher gelieferten Daten überfordert wird.

Mono und Flux

Mono hat genau 0 … 1 Elemente und Flux hat 0 … N Elemente.

Hot und Cold Streams

Bei Hot-Streams ist die Anzahl der Elemente unbekannt und fortwährend. Man stelle sich zum Beispiel eine Datenquelle wie einen Temperatursensor vor. Solange die Anwendung läuft, können wir Messwerte abfragen und das unendlich.

Nimmt man als Quelle für einen reaktiven Stream zum Beispiel eine Liste, dann ist die Anzahl der Elemente bekannt und endlich. In der Sprache von reaktor heißt das Cold-Stream.

Es gibt noch einen weiteren unterschied zwischen Hot- und Cold-Streams: Sind Elemente in einem Hot-Stream einmal konsumiert durch einen Subscriber, dann sind Sie weg. Schreibt man sich bei einem Cold-Stream ein zweites mal ein, dann werden erneut alle Elemente publiziert.

Dependencies

implementation('io.projectreactor:reactor-core')


dependencyManagement {
     imports {
          mavenBom "io.projectreactor:reactor-bom:Californium-RELEASE"
     }
}

Wie mache ich aus einem blockierenden Methodenaufruf ein reaktiven Stream?

Mit der Methode .fromCallable() von Mono kann man einen blockierende Aufruf als Lambda Funktion übergeben und erhält unmittelbar ein Mono Objekt zurück. Als kurzes Beispiel wird hier eine blockierende Methode getInfo() gewrappt.

/**
    * Wrap blocking service call getInfo
    * 
    * @param Long itemId
    * @return Mono<Info>
    */
private Mono<Info> getInfo(Long id) {
        Mono<Info> blockingWrapper = Mono.fromCallable(() -> { 
                return service.getInfo(id);
        });

        return blockingWrapper.subscribeOn(Schedulers.elastic());
}

Das ganze kann man jetzt in einen Stream aus id verpacken.

private Flux<Info> getInfos(Flux<Long> ids) {
        return ids.flatMap(id -> getInfo(id), 2); // max 2 concurrent
}

Begrenzung der Aufrufe

In dem letzten Abschnitt haben wir gesehen wie man einen blockierenden Aufruf wrappen kann. In reaktiven Streams ist es so, dass keine Daten produziert werden solange kein Subscriber vorhanden ist. Wenn jetzt ein Subscriber für den Flux von getInfos() aboniert, dann werden alle id parallel im Excecutor ThreadPool abgfragt. Dieses kann zum Beispiel bei teuren Funktionen wie zum Beispiel Rest Calls zu einer Überlastung führen. Daher muss hier ein weiterer Parameter in flatMap mit angegeben werden. Damit kann man die Anzahl der Concurrent Calls begrenzen. Hier in diesem Beispiel sind immer maximal 2 konkurierende Aufruf gleichzeitig.

Codepfade

In einem Projekt das für Eingangsdaten sehr viele unterschiedliche Parser verwenden muss, ist es nur natürlich das wenn dieses in einer Klasse geschehen würde, dass das sehr unübersichtlich wird. Daher ist es besser, wenn man die Ganzheit in kleinere separate Parserklassen splittet.

Vor Spring

Nach diesem Schema bin ich in dem Projekt auch vorgegangen und habe für jede Gruppe einen eigenen Parser geschrieben. Alle Parser implementieren das Interface Parser, sodass sich folgender Code ergab:

//
// Instantiate class and execute parse
//
try {
    Class<?> c = Class.forName("de.pfau.parser.Parser00001");

    try {
        Parser p = (Parser) c.getDeclaredConstructor().newInstance();
        autowireCapableBeanFactory.autowireBean(p);

        p.parse();
    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
        throw new Exception ...
    } 
} catch (ClassNotFoundException e) {
    log...
}

Da das Projekt noch aus einer Zeit stammt in der es noch nicht mit Spring kontakt hatte, kommt hier die newInstance() Methode zum Einsatz. Damit in den Klassen auch das Autowiring weiterhin funktioniert, muss nach der Verwendung von DI mit autowireCapableBeanFactory.autowireBean(p) Spring hierüber in Kenntnis gesetzt werden. Ansonsten haben die Autowired Felder null Werte.

Die Umsetzung mit Spring

Spring bringt hierfür Frist Class Support mit, da das grundlegend für das Dependency Injection ist.

Mit dem ClassPathScanningCandidateComponentProvider kann man den Classpath durchsuchen. Beim Instanzieren des Objektes kann ein Parameter useDefaultFilters angegeben werden. Setzt man ihn auf false, dann hat man eine leere Filterkette.

ClassPathScanningCandidateComponentProvider scanner =
        new ClassPathScanningCandidateComponentProvider(false);

Als nächstes fügen wir einen neuen Includefilter ein. Es gibt verschiedene Filter die eingefügt werden können (siehe TypeFilter). Für meine Zwecke eignet sich hier der RegexPatternTypeFilter. Als Parameter übergeben wir einen kompilierten RegEx Ausdruck, sodass Standard Java RegEx zum tragen kommt.

Die Klassen heißen alle ParserXXXXX. Wobei XXXXX für fünf stellige Dezimalzahl steht.

scanner.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile("de.pfau.parser.*Parser\\d{5}")));

Über die Methode scanner.findCandidateComponents kann man eine gefilterte Liste der Kandidaten erhalten. Der Rest ist identisch zur alten Version.

Damit das Autowiring von autowireCapableBeanFactory funktioniert, darf der Code nicht im Konstruktor ausgeführt werden. Hierzu verwende ich die @PostConstruct Annotation. Die Instanzen der Parser werden in einer HashMap gespeichert, um so später darauf zuzugreifen.

ClassPathScanningCandidateComponentProvider scanner =
        new ClassPathScanningCandidateComponentProvider(false);

scanner.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile("de.pfau.parser.*Parser\\d{5}")));

for (BeanDefinition bd : scanner.findCandidateComponents("de.pfau.parser")) {
    //
    // Instantiate parser and parse loaded document
    //
    try {
        var classname = bd.getBeanClassName();
        Class<?> c = Class.forName(classname);

        try {
            Parser p = (Parser) c.getDeclaredConstructor().newInstance();
            autowireCapableBeanFactory.autowireBean(p);
            map.put(classname, p);

            log.info("parser " + classname + " successfuly loaded");
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            throw new Exception...
        } 
    } catch (ClassNotFoundException e) {
        log.severe("could not load class");
    }
}

Hat man einen Web Server z.B. Tomcat im Classpath, dann startet Spring Boot diesen automatisch. Es kann aber sein, das man die selbe Codebasis für zwei Anwendungen benutzt. Es lässt sich das Starten des Webservers steuern. Zum einen über die application.properties und programmatisch.

Spring Boot 2.x

Hier über die Properties:
spring.main.web-application-type=none

oder

public static void main(String[] args) {
    new SpringApplicationBuilder(Application.class)
        .web(WebApplicationType.NONE) 
        .run(args);
}

Spring Boot 1.x

Ab 2.0 haben sie die Bezeichnungen geändert. Zuvor musste die web-environment auf false gesetzt werden.

spring.main.web-environment=false

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());
}

Geringer Plattenplatz zur Verfügung?

Jeder kennt die Situation das schnell etwas mehr Plattenplatz her muss, weil zum Beispiel wichtige Updates anliegen. Wenn es schnell gehen muss, dann reicht oft eine Liste der Größten Dateien um zu entscheiden was gelöscht werden kann. Dazu reichen die Bordmittel von Linux aus. Ein bischen find, sort und head und schon bekommt man die größten Dateien auf dem Rootfilesystem angezeigt.

sudo find / -type f -ls | sort -k 7 -r -n | head -20

So lassen sich schnell die 20 größten Verbraucher an Plattenplatz bestimmen.

Aber Achtung: Es werden auch die virtuellen Dateisysteme (wenn eingehangen) mit untersucht und somit können auch bei viel Arbeitsspeicher Abbilder (/proc/kcore) oder VRAM der Grafikkarte mit in den Top 20 sein.

Am 20.12.2018 wurde die aktuellste Version der Spring Tools Suite 4.1.0 veröffentlicht.

Changelog für Spring Tools Suite 4.1.0

Wie immer wurden Fehler behoben und Aktualisierungen eingefügt. Nennenswertes ist diesmal das Eclipse Update. Eclipse erscheint seit einiger Zeit in einem 3 monatigen Rhythmus und so war Eclipse bei der Aktualisierung auch mit dabei. Spring Tools Suite basiert jetzt auf Eclipse 2018-12.

Weitere Änderungen liste ich hier kurz auf:

Spring Boot

  • Mit diesem Release ist erstmals eine Unterstützung für die Autovervollständigung (content-assist) für Spring Data Repository definitionen mit dabei.
  • Die Live Hover Hyperlinks zu den Javatypen funktionieren auch für Projekte die Java 9 oder höher verwenden.
  • Es wurde ein Problem mit beendeten Anwendungen behoben, wenn diese mit JMX über einen SSH Tunnel verbunden sind.

Eclipse

  • Eclipse Javadoc Live Hover ist nun auch ohne eine Abhägigkeit zum spring-boot-actuator wieder verfügbar.
  • Java 11 Support ohne Update vom Eclipse Marketplace
  • Verbesserter Kompatibilitätscheck von JDK Versionen
  • Live Reueast von Reactiven WebFlux Anwendungen können nun auch im Boot Dashboard angezeigt werden.
  • Fehlerbeseitung beim kommentieren mit Hilfe des Shortcuts

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