Spring (Boot) erweitern

Angenommen, ich würde Web-Applikationen verkaufen. Zum Teil customized auf Basis desselben Cores, zum Teil unabhängig davon. Mein Code müsste also modular sein; ich möchte Libraries wieder­­verwenden, ganze Features ein- und ausschalten können, vielleicht Teile nur für einen Kunden überschreiben. Wie würde ich das wohl mit Spring Boot machen?

Sucht man im Internet nach Modularisierung von Spring (Boot) Anwendungen, landet man recht schnell bei Microservices:

While there is no precise definition of this architectural style, there are certain common characteristics [...]: an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.

Zuerst mal nützt mir das noch nichts für wiederzuverwendende Libraries. Aber selbst wenn, dann ist mir zugegebenermaßen nicht ganz klar, wie man sich das in einer Webapplikation vorzustellen hat: Kommuniziert jeder Teil der Anwendung (Chat, Kalender, ...) nur mit "seinem" Backend, separiert zum Beispiel über Ports? Oder läuft alles über Port 80 an den Server, und der verteilt das intern an den jeweiligen Prozess? So oder so müsste man das anwendungsweit berücksichtigen (richtig?). Nicht falsch verstehen: Ich sehe die Vorteile (schnelles Austauschen eines Service, Ausfallsicherheit der Dach-Anwendung. Skalierbarkeit), <update>Hier ein gutes Tutorial zum Thema "microservices mit Spring, Spring Boot and Spring Cloud"</update> will mich hier aber auf einen anderen Ansatz konzentrieren:

Denn "schöner" (im Sinne von gewohnter) wäre es, wenn man die einzelnen Module zu einer Anwendung zusammenstecken könnte, trotzdem mit den o.g. Eigenschaften... Und letztendlich ist das genau, wie Spring Boot an sich funktioniert: Per Maven (jaja, oder Gradle) konfiguriert man, welche "Starter" man braucht, und plötzlich hat man zB JPA-Support, kann die Adapter aber frei austauschen oder umkonfigurieren. Interessant wäre es "nur" noch, wie die Starter an sich aussehen (wie gut oder schnell lassen sie sich (weiter-)entwickeln?) und ob sie zB auch eigene URL-Endpoints erlauben (nötig für Module mit Frontendanbindung wie Kalender oder Chat).

Vorab – das geht alles.

  • Jeder Starter ist für sich eine vollwertige eigenständige Webanwendung, kann aber trotzdem ohne Änderung als Dependency eingebunden werden. Das macht das Entwickeln extrem komfortabel.
  • Die Dependencies bringen eigene Endpoints mit, die aber ebenfalls dynamisch "woanders hingeroutet" werden können. Konfiguration etc. ist ebenfalls, auch partiell, überschreibbar.
  • Es gibt im einbindenden Projekt keine zwingende Abhängigkeit im Code zum Starter. Man kann aber, und das sogar komfortabel über konditionale Annotations, darauf reagieren, ob eine Dependency da ist oder nicht. Und zB default-Implementierungen aus der Dependency überschreiben.

Im Detail: Hier gibt es ein exzellentes Tutorial, das ich mir erlaubt habe zu übernehmen; hier gibt es meine entzerrte, "white-label"-Implementierung der interessantesten Punkte:

  1. Jeder Starter ("Service A", "Service B") ist ein vollständiges Spring Boot Projekt, und standalone lauffähig. ("Some Projekt" ist natürlich auch standalone, allerdings müssen "Service A" und "Service B" in das lokale Repo installiert sein, siehe README)
  2. Wenn ein Starter in ein anderes Projekt eingebunden wird, muss man Spring sagen, welche Klasse die Basis-Konfiguration enthält. Dazu legt man /src/main/resources/META-INF/spring.factories an und hinterlegt die Klasse wie im Beispiel-Code.
  3. Um Namenskonflikte der Beans zu vermeiden, sollte man @ComponentScan.nameGenerator verwenden – zur Not tut es aber auch das name-Feld von @Component wie im Beispiel.
  4. Konfigurationsdateien werden per @PropertySource("classpath:acme-aservice.properties") eingebunden. Werte daraus werden per @Value("${aservice.messages.test1}") private String test1; injected oder bsplw. per @RequestMapping("${aservice.mapping.url:/service-a}") in einem dadurch konfigurierbaren Routing verwendet (nach dem Doppelpunkt folgt der default).
  5. Konfigurationsdateien können von haus aus nur komplett überschrieben werden, indem sie unter demselben Dateinamen im Rahmenprojekt abgelegt werden. In der Beispielimplementierung habe ich eine Methode @PostConstruct public void initialize() in "Some Project", die das aufbohrt, und das Überschreiben von einzelnen Settings erlaubt, auch wenn diese in einer anders benannten Datei definiert sind.
  6. Das folgende Konstrukt in com.acme.bservice.IndexController erlaubt das Ersetzen von Beispielimplementierungen (MessageProvider ist ein Interface):
        @Autowired(required = false)
         public void setMessageProvider(MessageProvider messageProvider)
    Ersetzt wird der default dann völlig transparent, indem man nur das Interface MessageProvider implementiert und die Implementierung als @Component annotiert.

Für Details der Implementierung siehe Code auf Github – der ist wirklich überschaubar: "Service A" hat zwei Klassen plus eine Config, "Service B" drei Klassen, "Some Project" zwei Klassen plus Config 🙂

Gson: JSON-Serializer für bestimmte Typen überschreiben

Play nutzt (zumindest in Version 1.3?) Gson für JSON-Serialisierung. Gson macht aus Float.NaN die Zahl NaN, was für JSON nicht erlaubt ist:

play.exceptions.JavaExecutionException: NaN is not a valid double value as per JSON specification. To override this behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.

Schöner wäre es, daraus den String "NaN" zu machen.

Ansatz 1, wie empfohlen:

Gleicher Fehler. Deshalb Ansatz 2:

mit:

und DoubleSerializer analog.

Spring Data JPA: Subqueries

JPQL-Subqueries können nicht im FROM stehen – aber Native Queries haben diese Einschränkung nicht (zwingend). Klar, denn native Queries sind eben nativ, sprich in der Sprache der jeweiligen Datenbank. Wer also damit leben kann, die Portabilität zu verlieren, der kann wie folgt "beliebige" Queries verwenden:

Ajax Interceptor mit jQuery und Angular

jQuery:

Angular:

Für die CSRF-Token siehe hier.

Spring: Dependency Injection in statische Variablen

Es gibt n+1 Artikel, warum das architektonisch keinen Sinn ergeben würde – aber wenn man das trotzdem möchte, findet sich hier die Lösung (leicht abgewandelt, zB ist der Setter privat), die keine Konstruktor-Parameter erfordert:

PS: Der Setter wird nur beim Startup aufgerufen, und nicht mit jedem Bean, insofern entsteht dadurch kein Performance-Issue.

PPS, Achtung: Dieser Kommentar hat recht, deshalb Vorsicht! Ggf. sollte das Feld transient sein

ungetestete Alternative

via:

Öfter mal was Neues: Neuer Job

(Zu Folge 4)

Zum ersten mal überhaupt seit Bestehen dieses Blogs (immerhin Mitte 2008!) habe ich den Job gewechselt: Seit 1.11. arbeite ich nicht mehr bei Jung von Matt/next, sondern bei mindsmash.

Bei der next hatte sich mein Job über die Jahre stark gewandelt: Eingestellt als Web Developer war ich fast von Beginn an der Flash Developer, der ich mich dann auch bald nennen durfte. Darin brachte ich es bis zum Senior; aber als Flash dann starb, war ich tatsächlich gerade mit einem komplexen Java-Projekt beschäftigt. Java-Projekte sind im Werbeagenturumfeld eher selten, das zweite Standbein wurde deshalb PHP, und ich zum Application Developer. Java blieb trotzdem das Wunschziel, und bei mindsmash bin ich dann auch "Java Web Developer".

Was das Ganze mit diesem Blog zu tun hat: Es dürften vermehrt Java-Posts folgen* – embrace yourself!

*ein paar kamen ja schon.

PS, die Aussicht aus'm Büro nach hinten raus:

https://instagram.com/p/vDgiD1y2Qi/

PPS: Darum geht's:

Spring Boot Security: 403 Forbidden

Es gibt durchaus gute Tutorials und Examples für Spring Boot Security. Nur benutzen die alle Thymeleaf als Templating Engine. Und Thymeleaf hat eine Besonderheit: Wenn man im Form "th:action" statt "action" verwendet, dann wird automatisch ein hidden Input Field mit dem CSRF-Token injected!

Wenn man stattdessen bsplw. Velocity verwendet, und das Template 1:1 übersetzt, merkt man nicht, dass etwas fehlt m( Der Server gibt "403 Forbidden" ohne weitere Fehlermeldung zurück (Tipp zum Debuggen: Der Request wird irgendwann auf die Error-Route umgebogen, aber der Fehler (hier noch inklusive Fehlermeldung) steht im Response-Object).

Offenbar muss man den Token dann manuell dem Template übergeben (Quelle, weicht aber leicht ab):

Gegenprobe:

Update

Übrigens: Hier wird beschrieben, wie man in Thymeleaf den Token in jeden Ajax-Request bekommt. Leider funktioniert das so nicht, denn um den Platzhalter zu ersetzen, muss man th:content verwenden (Quelle indirekt), sonst wird der Platzhalter nicht ausgewertet m(

Dann: Siehe hier für das Absetzen der Requests (in jQuery oder Angular)