<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-2194980644624669461</atom:id><lastBuildDate>Fri, 23 Jan 2026 08:40:19 +0000</lastBuildDate><category>Spring</category><category>Java</category><category>Clean Code</category><category>Microservice</category><category>Security</category><category>agile</category><category>Spring-Basics</category><category>test</category><category>AI</category><category>Kotlin</category><category>reactive</category><category>Linux</category><category>OpenAPI</category><category>MongoDB</category><category>Thymeleaf</category><category>AWS</category><category>DevOps</category><category>Redis</category><category>Selenium</category><category>Vue.js</category><title>Agile Coding mit Java, Kotlin, Spring und Microservices</title><description>Hier entwickeln wir Microservices und IT-Systemen für die Cloud mit Java oder Kotlin. Für die einfache Nutzung von Datenbanken (z.B. MongoDB), Security- oder Frontend-Technologien stelle ich das Spring Framework mit dem Spring Boot Projekt vor. Mit Clean Code Prinzipen und Test-Automatisierungstechniken zeige ich, wie Wartbarkeit und Qualität sichergestellt wird.</description><link>https://agile-coding.blogspot.com/</link><managingEditor>noreply@blogger.com (Elmar Brauch - Microservice Master)</managingEditor><generator>Blogger</generator><openSearch:totalResults>61</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-8038444387781469564</guid><pubDate>Sun, 28 Sep 2025 06:02:00 +0000</pubDate><atom:updated>2025-09-28T14:27:42.133+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">Java</category><title>Java 25: Die wichtigsten Features seit Version 21</title><description>&lt;p&gt;&lt;b&gt;Seit September 2025 gibt es das neue Java 25 LTS-Release. Neben den typischen Performance-Verbesserungen und Ressourcen-Optimierungen fallen vor allem die Vereinfachungen für Java-Neulinge auf. Mehr dazu in diesem Artikel.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7em5WZT7ebsYgWnE_9UBf-6DvOXU7fmkBd9JyR_5JQwLjmjhK59_UD-i0ILKD-TC5vErU6opT4OMOqPMXYYHDN-3j79rPI12CJvZ_Ngs_dcqbWP3F5T3U4Ntbxr4Xic2Xa9xfGxVnICG0ZRJd-oYM2oJdfKtgCFifW0I_VOkmuvonbzPjoS9Hn8kBPkp5/s1536/ChatGPT%20Image%2028.%20Sept.%202025,%2008_08_01.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1536&quot; height=&quot;213&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7em5WZT7ebsYgWnE_9UBf-6DvOXU7fmkBd9JyR_5JQwLjmjhK59_UD-i0ILKD-TC5vErU6opT4OMOqPMXYYHDN-3j79rPI12CJvZ_Ngs_dcqbWP3F5T3U4Ntbxr4Xic2Xa9xfGxVnICG0ZRJd-oYM2oJdfKtgCFifW0I_VOkmuvonbzPjoS9Hn8kBPkp5/s320/ChatGPT%20Image%2028.%20Sept.%202025,%2008_08_01.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Java 25 Features&lt;/h2&gt;&lt;div&gt;Als neues LTS-Release bringt Java 25 einige Neuerungen im Vergleich zu Java 21 mit. Unter der Haube wurde viel optimiert, das Web-EntwicklerInnen bei der täglichen Arbeit eventuell gar nicht bemerken. Das sind zum Beispiel Optimierungen bei den Garbage-Kollektoren, der Support-Stop für 32 Bit Rechner oder mehr Unterstützung für die AOT-Kompilierung. In diesem Artikel fokussiere ich mich auf Änderungen, die auffallen. Alles Weitere findet ihr im Detail hier:&amp;nbsp;&lt;a href=&quot;https://openjdk.org/projects/jdk/25/&quot;&gt;https://openjdk.org/projects/jdk/25/&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/3mR4jCVtgRk&quot; width=&quot;320&quot; youtube-src-id=&quot;3mR4jCVtgRk&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Rückblick Java 21 Features&lt;/i&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Performance Tuning durch Versionsupdate&lt;/h3&gt;&lt;div&gt;Java 25 ist performanter als Java 21. Ich teste es mit einer kleinen, produktiven Spring Boot Webanwendung. Dazu starte ich die Anwendung 10 Mal mit Java 21 und 10 Mal mit Java 25. Dabei protokolliere ich die Startzeiten, wie Spring Boot sie loggt.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Java 21&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Schnellste Zeit: 2,976 Sekunden&lt;/li&gt;&lt;li&gt;Langsamste Zeit: 3,376 Sekunden&lt;/li&gt;&lt;li&gt;Durchschnitt: 3,087 Sekunden&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Java 25&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Schnellste Zeit: 2,537 Sekunden&lt;/li&gt;&lt;li&gt;Langsamste Zeit: 2,760 Sekunden&lt;/li&gt;&lt;li&gt;Durchschnitt: 2,653 Sekunden&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;div&gt;In diesem einfachen Performancetest ist Java 25 circa 14% schneller als Java 21. Im Vergleich zu älteren Versionen, wie Java 17 oder 11 wäre die Performance-Steigerung noch deutlicher.&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Module Import&lt;/h3&gt;&lt;div&gt;&lt;div&gt;In Java 25 wurde der Modul-Import eingeführt. Vorher konnte man einzelne Klassen, Konstanten oder Pakete importieren, mit JEP 511 können ganze Module importiert werden:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 11,3pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import module &lt;/span&gt;java.base;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Java25 &lt;/span&gt;{&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;static void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;jep511_ModuleImport&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;for &lt;/span&gt;(&lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;item &lt;/span&gt;: &lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;1&quot;&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;0&lt;/span&gt;))&lt;br /&gt;            &lt;span style=&quot;font-style: italic;&quot;&gt;jep456_UnnamedVariable&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;item&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;...&lt;/span&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Der vorherige Code-Ausschnitt benutzt die Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;List &lt;/span&gt;innerhalb der Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Java25&lt;/span&gt;. Statt einem Einzelklassen-Import (&lt;span style=&quot;background-color: white; color: #0033b3; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;background-color: white; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;java.util.List&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;;&lt;/span&gt;) wird sie zusammen mit den anderen Klassen des&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;java.base&lt;/span&gt;-Moduls importiert.&lt;/div&gt;&lt;h3&gt;Keine Klasse und vereinfachte main-Methode&lt;/h3&gt;&lt;div&gt;Java 25 lernt sich leichter für Neulinge, weil:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;die &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode kein &quot;&lt;span style=&quot;font-family: courier;&quot;&gt;public static&lt;/span&gt;&quot; und kein &quot;&lt;span style=&quot;font-family: courier;&quot;&gt;String[] args&lt;/span&gt;&quot; mehr benötigt.&lt;/li&gt;&lt;li&gt;keine Klasse mehr für die &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode braucht.&lt;/li&gt;&lt;li&gt;die Klassen des &lt;span style=&quot;font-family: courier;&quot;&gt;java.base&lt;/span&gt;-Moduls (hier &lt;span style=&quot;font-family: courier;&quot;&gt;ArrayList&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;IO&lt;/span&gt;) automatisch importiert sind. Wäre die &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode in einer Klasse, so müssten wir die Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;ArrayList&lt;/span&gt; importieren.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Hier der Inhalt einer kompletten Java-Datei, welche die neue Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;IO &lt;/span&gt;verwendet, um mit der Konsole zu interagieren:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 11,3pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;main&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;list &lt;/span&gt;= &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;ArrayList&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;String&lt;/span&gt;&amp;gt;();&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;while &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.size() &amp;lt; &lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;) {&lt;br /&gt;        &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;print&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Enter max 2 items in Console: &quot;&lt;/span&gt;);&lt;br /&gt;        &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.add(&lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;readln&lt;/span&gt;());&lt;br /&gt;    }&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Java 25 items are &quot; &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;);&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;(Mehr dazu in JEP 512.)&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Unbenannte Variablen&lt;/h3&gt;&lt;div&gt;JEP 456 führt unbenannte Variablen in Java 22 ein. Bisher brauchte jede Variable einen Namen, auch wenn wir sie nicht benutzt haben. Nun können wir ungenutzte Variablen einfach mit Unterstrich _ benennen. Im folgenden Beispiel verwende ich unbenannte Variablen für eine Exception und einen String, die im Code ignoriert werden:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 11,3pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;static void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;jep456_UnnamedVariable&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;Object obj&lt;/span&gt;) {&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;try &lt;/span&gt;{&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;switch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;obj&lt;/span&gt;) {&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;String _ &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Any Text.&quot;&lt;/span&gt;);&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Integer number &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;10 &lt;/span&gt;/ &lt;span style=&quot;color: black;&quot;&gt;number&lt;/span&gt;);&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;default &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Unknown type&quot;&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;    } &lt;span style=&quot;color: #0033b3;&quot;&gt;catch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;Exception _&lt;/span&gt;) {&lt;br /&gt;        &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Exception caught without caring about details.&quot;&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Kommentare in Markdown&lt;/h3&gt;&lt;div&gt;Seit Java 23 wird Markdown als Format für Kommentare unterstützt. Dazu muss jede Kommentarzeile mit &lt;span style=&quot;font-family: courier;&quot;&gt;///&lt;/span&gt; starten. Das sieht dann so aus:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 11,3pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;/// # JEP 467&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;/// Java supports comments in Markdown format.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;/// Other Java 25 JEPs in this class are:&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;/// * JEP 511&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;/// * JEP 456&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Java25 &lt;/span&gt;{&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;...&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Flexible Konstruktor-Bodies&lt;/h3&gt;&lt;div&gt;JEP 513 lockert die Regel für Code im Konstruktor. Vor Java 25 durfte vor einem this(...) oder super(...) Konstruktoraufruf kein anderer Code stehen. Durch den flexiblen Konstruktor-Body ist dies nun erlaubt:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 11,3pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Java25 &lt;/span&gt;{&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #00627a;&quot;&gt;Java25&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Code before super() is allowed with JEP 513.&quot;&lt;/span&gt;);&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;super&lt;/span&gt;();&lt;br /&gt;        &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Code after super() was allowed before.&quot;&lt;/span&gt;);&lt;br /&gt;    }
    ...&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Stream Gatherers&lt;/h3&gt;&lt;div&gt;Gatherers (JEP 485) bereichern die Stream-Verarbeitung seit Java 24. Dazu hat die Stream API eine neue Methode &lt;span style=&quot;font-family: courier;&quot;&gt;gather&lt;/span&gt;, die Elemente im Stream sammelt. &lt;span style=&quot;font-family: courier;&quot;&gt;gather &lt;/span&gt;ist eine intermediate Operation, so dass der Stream nach &lt;span style=&quot;font-family: courier;&quot;&gt;gather &lt;/span&gt;weiter verarbeitet werden kann. Die Grundlagen zu Streams findet ihr &lt;a href=&quot;https://agile-coding.blogspot.com/2021/07/streams.html&quot; target=&quot;_blank&quot;&gt;hier&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Gatherer &lt;/span&gt;ist ein neues Interface, durch dessen Implementierung wir mit der &lt;span style=&quot;font-family: courier;&quot;&gt;gather &lt;/span&gt;Methode one-to-one, one-to-many, many-to-one und many-to-many Mappings im Stream durchführen können. Im folgenden zeige ich 5 Implementierungen, die Teil von Java 25 sind.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;fold&lt;/b&gt;&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Ein many-to-one Mapping im Stream.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;Integer&lt;/span&gt;&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;numbers &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;1&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;3&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;4&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;5&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Prints 15 (Stream has only 1 element after gather)&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;numbers&lt;/span&gt;.stream()&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;fold&lt;/span&gt;(() -&amp;gt; &lt;span style=&quot;color: #1750eb;&quot;&gt;0&lt;/span&gt;, &lt;span style=&quot;color: black;&quot;&gt;Integer&lt;/span&gt;::&lt;span style=&quot;font-style: italic;&quot;&gt;sum&lt;/span&gt;))&lt;br /&gt;        .forEach(&lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;::&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;scan&lt;/b&gt;&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;scan nimmt den aktuellen und den vorherigen Wert und wendet eine BiFunction an, um den aktuellen Wert zu ändern.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Result: [1, 3, 6, 10, 15]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;numbers&lt;/span&gt;.stream()&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;scan&lt;/span&gt;(() -&amp;gt; &lt;span style=&quot;color: #1750eb;&quot;&gt;0&lt;/span&gt;, (&lt;span style=&quot;color: black;&quot;&gt;previous&lt;/span&gt;, &lt;span style=&quot;color: black;&quot;&gt;current&lt;/span&gt;) -&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;previous &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;current&lt;/span&gt;))&lt;br /&gt;        .toList();&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;windowFixed&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Sammelt jedes Element in gleichgroßen Unterlisten.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Result: [[1, 2], [3, 4], [5]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;Integer&lt;/span&gt;&amp;gt;&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;windows &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;numbers&lt;/span&gt;.stream()&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;windowFixed&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;))&lt;br /&gt;        .toList();&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;slidingWindow&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Ähnlich wie windowFixed nur mit Überlagerungen der Elemente in den gesammelten Unterlisten.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Result: [[1, 2], [2, 3], [3, 4], [4, 5]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;Integer&lt;/span&gt;&amp;gt;&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;slidingWindows &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;numbers&lt;/span&gt;.stream()&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;. &lt;span style=&quot;font-style: italic;&quot;&gt;windowSliding&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;))&lt;br /&gt;        .toList();&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;mapConcurrent&lt;/b&gt;&lt;/h4&gt;&lt;div&gt;Parallele Ausführung des Mappings im Gatherer, dabei wird die Ordnung des Streams erhalten.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Result: true - proves that order was kept, 
// because each sliding window has a difference of 1.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;IntStream&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;range&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;0&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;10000&lt;/span&gt;)&lt;br /&gt;        .boxed()&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;mapConcurrent&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;100&lt;/span&gt;, &lt;span style=&quot;color: black;&quot;&gt;x &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;x &lt;/span&gt;+ &lt;span style=&quot;color: #1750eb;&quot;&gt;1&lt;/span&gt;))&lt;br /&gt;        .gather(&lt;span style=&quot;color: black;&quot;&gt;Gatherers&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;windowSliding&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;))&lt;br /&gt;        .allMatch(&lt;span style=&quot;color: black;&quot;&gt;pair &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;pair&lt;/span&gt;.getLast() - &lt;span style=&quot;color: black;&quot;&gt;pair&lt;/span&gt;.getFirst() == &lt;span style=&quot;color: #1750eb;&quot;&gt;1&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Scoped Values&lt;/h3&gt;&lt;div&gt;Scoped Values JEP 506 sind eine Modernisierung von &lt;span style=&quot;font-family: courier;&quot;&gt;ThreadLocal&lt;/span&gt;. Mit &lt;span style=&quot;font-family: courier;&quot;&gt;ScopedValue&lt;/span&gt; definieren wir Objekte, die nur im Rahmen definierter Threads sichtbar sind. Im folgenden Beispiel startet die &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode einen Thread. Im Thread wird nur die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;printScopedValue &lt;/span&gt;ausgeführt. Diese Methode greift per &lt;span style=&quot;font-family: courier;&quot;&gt;get()&lt;/span&gt; auf das &lt;span style=&quot;font-family: courier;&quot;&gt;Integer&lt;/span&gt;-Objekt innerhalb des Scopes zu, welches mit &lt;span style=&quot;font-family: courier;&quot;&gt;ScopedValue.where&lt;/span&gt; für die Ausführung des Threads gesetzt wurde.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private static final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;Integer&lt;/span&gt;&amp;gt; &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;X &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;newInstance&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private static void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;printScopedValue&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Instead of method-parameter a scoped value is used: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;X&lt;/span&gt;.get());&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;main&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;where&lt;/span&gt;(&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;X&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;666&lt;/span&gt;).run(() -&amp;gt; &lt;span style=&quot;font-style: italic;&quot;&gt;printScopedValue&lt;/span&gt;());&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Is scoped value bound in main thread: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;X&lt;/span&gt;.isBound());
    ...
}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Das Beispiel zeigt 2 Vorteile von ScopedValues:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Werte im Scope des Threads müssen nicht per Methoden-Parameter übergeben werden. Bei einem Wert ist das sicherlich kein Problem, ab 3 Parametern ist es aus Clean Code Sicht problematisch. Mit &lt;span style=&quot;font-family: courier;&quot;&gt;ScopedValue &lt;/span&gt;machen wir beliebig viele Objekte durch mehrfache&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;where&lt;/span&gt;-Aufrufe in einem Thread verfügbar.&lt;/li&gt;&lt;li&gt;Außerhalb des Scopes sind die Werte nicht mehr verfügbar und werden so auch nicht unnötig lang im Speicher gehalten. Deshalb liefert &lt;span style=&quot;font-family: courier;&quot;&gt;X.isBound()&lt;/span&gt; im obigen Beispiel den Wert&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;false&lt;/span&gt;. Ein Aufruf von &lt;span style=&quot;font-family: courier;&quot;&gt;X.get()&lt;/span&gt; liefert nur innerhalb des Thread den Wert &lt;span style=&quot;font-family: courier;&quot;&gt;666.&lt;/span&gt;&amp;nbsp;Außerhalb des Scopes (z.B. in der &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode) wirft es eine&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;NoSuchElementException&lt;/span&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Hier ein weiteres Beispiel zur Veranschaulichung der&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;NoSuchElementException&lt;/span&gt;. Im 2. Aufruf von &lt;span style=&quot;font-family: courier;&quot;&gt;thread.run()&lt;/span&gt;&amp;nbsp;wird diese Exception geworfen, weil &lt;span style=&quot;font-family: courier;&quot;&gt;Y&lt;/span&gt; nur beim ersten Thread-Aufruf (&lt;span style=&quot;font-family: courier;&quot;&gt;.run(thread)&lt;/span&gt;) einen Wert im Scope hat.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 10,5pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;scopedValueExample2&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;String&lt;/span&gt;&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;Y &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;newInstance&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;Runnable thread &lt;/span&gt;= () -&amp;gt; {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;try &lt;/span&gt;{&lt;br /&gt;            &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Print scoped value: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: #851691;&quot;&gt;Y&lt;/span&gt;.get());&lt;br /&gt;        } &lt;span style=&quot;color: #0033b3;&quot;&gt;catch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;NoSuchElementException _&lt;/span&gt;) {&lt;br /&gt;            &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Scoped value was not bound to this thread.&quot;&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;ScopedValue&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;where&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;Y&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;12345&quot;&lt;/span&gt;).run(&lt;span style=&quot;color: black;&quot;&gt;thread&lt;/span&gt;);&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;thread&lt;/span&gt;.run();&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;IO&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;println&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Is scoped value bound in main thread: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;Y&lt;/span&gt;.isBound());&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Meine Java 25 Highlights sind Unbenannte Variablen, Gatherers und die Performance-Tunings, weil ich von diesen 3 Features im Berufsalltag am meisten profitieren werde.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die Vereinfachungen um die &lt;span style=&quot;font-family: courier;&quot;&gt;main&lt;/span&gt;-Methode kommen Java-Neulingen mit Sicherheit zugute. Einsteiger in die Programmierung können sich so besser auf das Wesentliche konzentrieren. Sie lernen zuerst Schleifen, Variablen und if-Statements lernen, bevor sie mit Klassen und statischem Code &quot;kämpfen&quot; müssen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Das Update unserer Spring Boot Anwendung von Java 21 auf Java 25 verlief problemlos. Wir mussten nur die Java-Version in der Maven-Konfiguration anpassen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den kompletten Code zu diesem Artikel gibt es hier:&lt;br /&gt;&lt;a href=&quot;https://github.com/elmar-brauch/java25&quot;&gt;https://github.com/elmar-brauch/java25&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2025/09/java25.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7em5WZT7ebsYgWnE_9UBf-6DvOXU7fmkBd9JyR_5JQwLjmjhK59_UD-i0ILKD-TC5vErU6opT4OMOqPMXYYHDN-3j79rPI12CJvZ_Ngs_dcqbWP3F5T3U4Ntbxr4Xic2Xa9xfGxVnICG0ZRJd-oYM2oJdfKtgCFifW0I_VOkmuvonbzPjoS9Hn8kBPkp5/s72-c/ChatGPT%20Image%2028.%20Sept.%202025,%2008_08_01.png" height="72" width="72"/><georss:featurename>64 Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-7030201289921992065</guid><pubDate>Wed, 28 May 2025 05:55:00 +0000</pubDate><atom:updated>2025-06-27T08:32:09.839+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">Java</category><title>Lessons Learned – von der Idee bis zum Go-Live unseres KI-Chatbots</title><description>&lt;p&gt;&lt;em data-end=&quot;346&quot; data-start=&quot;292&quot;&gt;Erfahrungen aus der Entwicklung bei Magenta Business&lt;/em&gt;&lt;/p&gt;
&lt;p data-end=&quot;720&quot; data-start=&quot;348&quot;&gt;Die Entwicklung eines KI-Chatbots mit modernen Sprachmodellen (LLMs) war für unser Team nicht nur spannend, sondern auch lehrreich. Der Weg war gespickt mit Erfolgen, Rückschlägen und vielen Lessons Learned. Heute wird unser Chatbot aktiv von Kund:innen genutzt, aber der Weg dorthin war nicht geradlinig.&lt;/p&gt;
&lt;p data-end=&quot;861&quot; data-start=&quot;722&quot;&gt;In diesem Beitrag teilen wir unsere Erkenntnisse – damit andere Teams schneller und mit weniger Stolpersteinen zum Ziel kommen.&lt;/p&gt;
&lt;hr data-end=&quot;866&quot; data-start=&quot;863&quot; /&gt;
&lt;h2 data-end=&quot;911&quot; data-start=&quot;868&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgi1gPoJtvD_AvKz7Z9FOOGKCEwbWIN8gIihz5bWv1RnnBfl_gaQ-P9c6YVglJatgulNq9jMWjGmVLJ34CDMatrINkw-toXNM2qRixoy9P8tz10fvf9OGOPB8JS6-SGATGC9XxVsfwMWZ99lknWVzE4f3qzIsqizHOXD5tI3gaQMtF1DN8CxL-fxa4SPu8B&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1411&quot; data-original-width=&quot;1363&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgi1gPoJtvD_AvKz7Z9FOOGKCEwbWIN8gIihz5bWv1RnnBfl_gaQ-P9c6YVglJatgulNq9jMWjGmVLJ34CDMatrINkw-toXNM2qRixoy9P8tz10fvf9OGOPB8JS6-SGATGC9XxVsfwMWZ99lknWVzE4f3qzIsqizHOXD5tI3gaQMtF1DN8CxL-fxa4SPu8B&quot; width=&quot;232&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/h2&gt;&lt;h2 data-end=&quot;911&quot; data-start=&quot;868&quot;&gt;KI in Java? Geht – und zwar ziemlich gut&lt;/h2&gt;
&lt;p data-end=&quot;1319&quot; data-start=&quot;913&quot;&gt;Wer KI-Anwendungen entwickeln möchte, muss nicht zwingend in Python unterwegs sein. Für Java-Teams gibt es mittlerweile sehr gute Frameworks wie &lt;strong data-end=&quot;1071&quot; data-start=&quot;1058&quot;&gt;Spring AI&lt;/strong&gt; und &lt;strong data-end=&quot;1091&quot; data-start=&quot;1076&quot;&gt;LangChain4J&lt;/strong&gt;, die die Anbindung von Sprachmodellen wie GPT, Claude oder Mistral deutlich vereinfachen. Selbst das Integrieren von Backend-Funktionalität oder anderer Firmen-APIs per Tool in die KI ist mit diesen Frameworks einfach. So könnt Ihr relativ leicht einfache &lt;b&gt;KI Agenten&lt;/b&gt; bauen.&lt;/p&gt;
&lt;p data-end=&quot;1576&quot; data-start=&quot;1321&quot;&gt;Unser Tipp: Baut frühzeitig einen &lt;b&gt;kleinen Prototypen&lt;/b&gt; – zum Beispiel eine REST-API, die ein paar typische Kundenfragen beantwortet. So schafft ihr Vertrauen im Team, gewinnt durch Demos Unterstützung aus der Fachseite und könnt Aufwände realistischer abschätzen.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/PeHY-Lj2_Ng&quot; width=&quot;320&quot; youtube-src-id=&quot;PeHY-Lj2_Ng&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;i&gt;Video LangChain4J&lt;/i&gt;&lt;/div&gt;
&lt;hr data-end=&quot;1581&quot; data-start=&quot;1578&quot; /&gt;
&lt;h2 data-end=&quot;1609&quot; data-start=&quot;1583&quot;&gt;MVP statt Feature-Wildwuchs&lt;/h2&gt;
&lt;p data-end=&quot;1916&quot; data-start=&quot;1611&quot;&gt;Ein häufiger Fehler bei neuen Technologien: Man will gleich alles auf einmal. Unser erster KI-Chatbot war technisch aufwendig und in seinem Kontext sehr mächtig – aber leider nicht relevant genug für unsere Kund:innen. Der Use Case war zu spitz, das Interesse zu gering. Die Folge: kaum Nutzung, viel Aufwand, wenig Impact.&lt;/p&gt;
&lt;p data-end=&quot;2196&quot; data-start=&quot;1918&quot;&gt;&lt;span data-end=&quot;1938&quot; data-start=&quot;1918&quot;&gt;Lektion gelernt:&lt;/span&gt; &lt;b&gt;Startet mit einem MVP (Minimal Viable Product)&lt;/b&gt;.&lt;br data-end=&quot;1989&quot; data-start=&quot;1986&quot; /&gt;
Ein schlanker Chatbot, der ein echtes Problem löst, bringt mehr als ein hochgerüsteter Alleskönner mit viel zu kleiner Zielgruppe. Im laufenden Betrieb lässt sich immer noch optimieren und ausbauen – basierend auf echten Nutzungsdaten.&lt;/p&gt;
&lt;p data-end=&quot;2441&quot; data-start=&quot;2198&quot;&gt;Beim zweiten Anlauf waren wir strategischer: Zuerst haben wir analysiert, wie stark der klassische Hilfe-Chat überhaupt genutzt wird. Daraufhin platzierten wir den KI-Chatbot an einer &lt;b&gt;prominenten, deutlich frequentierten Stelle&lt;/b&gt; im Portal – und wurden mit deutlich höherer Nutzung belohnt.&lt;/p&gt;&lt;p data-end=&quot;2441&quot; data-start=&quot;2198&quot;&gt;Trotzdem empfehlen wir, erste KI-Bots &lt;b&gt;an weniger kritischen Stellen zu testen&lt;/b&gt;. Denn KI-Systeme sind nicht immer vorhersehbar – und Fehler können schnell teuer werden. Beispiele wie der versehentliche Verkauf von Neuwagen für 1 Dollar zeigen, wie wichtig kontrollierte Einführungen sind.&lt;/p&gt;&lt;p data-end=&quot;2441&quot; data-start=&quot;2198&quot;&gt;Wir empfehlen auch den Chatbot am Anfang nur in einem &lt;b&gt;kleinen Kontext&lt;/b&gt; einzusetzen.&amp;nbsp;Das bedeutet beispielsweise, dass ihr den Chatbot auf einer einzelnen Webseite einsetzt und ihn auch nur für den UseCase auf dieser Webseite trainiert. Per SystemPrompt erklärt ihr dem Chatbot, dass er Fragen außerhalb des kleinen Kontextes freundlich Ablehnen soll oder an andere Kanäle (z. B. Hotline oder Hilfe-Seiten) verweisen soll.&lt;/p&gt;
&lt;hr data-end=&quot;2769&quot; data-start=&quot;2766&quot; /&gt;
&lt;h2 data-end=&quot;2815&quot; data-start=&quot;2771&quot;&gt;Klare Kennzeichnung: UX beginnt beim Icon&lt;/h2&gt;
&lt;p data-end=&quot;3092&quot; data-start=&quot;2817&quot;&gt;Ein weiteres Learning aus der Praxis: Nur weil ein neuer Chatbot live ist, heißt das noch lange nicht, dass er auch genutzt wird. Anfangs wurde unser KI-Chat von vielen Kund:innen schlicht übersehen – wir hatten nämlich dasselbe Icon wie beim klassischen Live-Chat verwendet.&lt;/p&gt;
&lt;p data-end=&quot;3314&quot; data-start=&quot;3094&quot;&gt;Den 2. KI-Chatbot haben wir mit einem neuen &lt;b&gt;Icon und Beschriftung &quot;KI-Chat&quot;&lt;/b&gt; direkt am Chat-Starten-Button versehen. Ob das direkt zur gestiegenen Nutzung geführt hat, lässt sich nicht sicher sagen – aber: Die Nutzung ist da, und der klarere Hinweis hat wahrscheinlich geholfen, den Chatbot besser einzuordnen.&lt;/p&gt;
&lt;hr data-end=&quot;3319&quot; data-start=&quot;3316&quot; /&gt;
&lt;h2 data-end=&quot;3357&quot; data-start=&quot;3321&quot;&gt;Guter Chatbot ≠ komplexer Chatbot&lt;/h2&gt;
&lt;p data-end=&quot;3623&quot; data-start=&quot;3359&quot;&gt;Nicht jeder Bot muss agentisch sein und eine prall gefüllte Wissensdatenbank haben. In unserem Fall reichte für viele Anliegen ein &lt;b&gt;einfacher &lt;/b&gt;&lt;strong data-end=&quot;3499&quot; data-start=&quot;3484&quot;&gt;FAQ-Chatbot.&lt;/strong&gt;&amp;nbsp;Zusammen mit dem Service-Team haben wir analysiert:&lt;/p&gt;
&lt;ul data-end=&quot;3725&quot; data-start=&quot;3625&quot;&gt;
&lt;li data-end=&quot;3675&quot; data-start=&quot;3625&quot;&gt;
&lt;p data-end=&quot;3675&quot; data-start=&quot;3627&quot;&gt;Welche Probleme melden Kund:innen am häufigsten?&lt;/p&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3725&quot; data-start=&quot;3676&quot;&gt;
&lt;p data-end=&quot;3725&quot; data-start=&quot;3678&quot;&gt;Welche Antworten gibt der Service immer wieder?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3904&quot; data-start=&quot;3727&quot;&gt;Mit diesem Wissen haben wir einen Bot gebaut, der ohne aufwendige Logik zuverlässig funktioniert – ganz einfach mit einem &lt;b&gt;LLM, einem SystemPrompt und einer gepflegten FAQ-Liste&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;4209&quot; data-start=&quot;3906&quot;&gt;Ein zusätzlicher Pluspunkt: Die &lt;b&gt;Zusammenarbeit mit der Fachseite&lt;/b&gt; lief auch ohne KI-Vorerfahrung reibungslos. Die Kolleg:innen mussten nur ihr Wissen in eine strukturierte Liste bringen – ein KI-Experte übernahm das Feintuning. Umgekehrt musste der KI-Experte das Fachthema gar nicht im Detail verstehen.&lt;/p&gt;
&lt;hr data-end=&quot;4214&quot; data-start=&quot;4211&quot; /&gt;
&lt;h2 data-end=&quot;4261&quot; data-start=&quot;4216&quot;&gt;Sicherheit, Kostenkontrolle und Guardrails&lt;/h2&gt;
&lt;p data-end=&quot;4549&quot; data-start=&quot;4263&quot;&gt;Sobald ein Chatbot live geht, wird er getestet – auch von weniger wohlmeinenden Nutzer:innen. Die ersten Angriffe auf unseren Bot waren klassische Injection-Versuche über die REST-API zwischen Frontend und Backend. Gut, dass wir bereits auf &lt;b&gt;bekannte Security-Mechanismen&lt;/b&gt; gesetzt hatten:&lt;/p&gt;&lt;ul data-end=&quot;4635&quot; data-start=&quot;4551&quot;&gt;
&lt;li data-end=&quot;4605&quot; data-start=&quot;4573&quot;&gt;
&lt;p data-end=&quot;4605&quot; data-start=&quot;4575&quot;&gt;&lt;span data-end=&quot;4603&quot; data-start=&quot;4575&quot;&gt;Validierung aller Eingabenparameter - also auch der Chatnachricht&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li data-end=&quot;4635&quot; data-start=&quot;4606&quot;&gt;
&lt;p data-end=&quot;4635&quot; data-start=&quot;4608&quot;&gt;&lt;span data-end=&quot;4633&quot; data-start=&quot;4608&quot;&gt;Detailliertes Logging zur Analyse von Angriffen&lt;/span&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4981&quot; data-start=&quot;4637&quot;&gt;Dazu kamen &lt;b&gt;KI-spezifische Maßnahmen&lt;/b&gt;:&lt;br data-end=&quot;4676&quot; data-start=&quot;4673&quot; /&gt;
Weil LLM-Anfragen pro Call abgerechnet werden, haben wir &lt;b&gt;Kostenlimits &lt;/b&gt;und &lt;b&gt;DoS-Schutzmechanismen&lt;/b&gt; implementiert. Außerdem empfehlen wir, sogenannte &lt;strong data-end=&quot;4836&quot; data-start=&quot;4822&quot;&gt;Guardrails&lt;/strong&gt; zu aktivieren – also Filter für sensible Inhalte (Hass, Gewalt, Sexualität, Jailbreak etc.), die vom Cloud-Provider zusammen mit dem LLM als Service angeboten werden.&lt;/p&gt;
&lt;hr data-end=&quot;4986&quot; data-start=&quot;4983&quot; /&gt;
&lt;h2 data-end=&quot;5015&quot; data-start=&quot;4988&quot;&gt;Lernen durch echte Chats&lt;/h2&gt;
&lt;p data-end=&quot;5303&quot; data-start=&quot;5017&quot;&gt;Egal wie viel man testet – im Live-Betrieb zeigt sich die Realität. Und die sieht oft anders aus als gedacht. &lt;b&gt;Kund:innen schreiben&lt;/b&gt; nicht immer vollständige Fragen, sondern oft &lt;b&gt;nur Stichworte&lt;/b&gt; wie „Rechnung“, „Störung“ oder „EVN“ (Einzelverbindungsnachweis). Trotzdem erwarten sie eine sinnvolle Antwort.&lt;/p&gt;
&lt;p data-end=&quot;5523&quot; data-start=&quot;5305&quot;&gt;Aktuelle Sprachmodelle kommen damit erstaunlich gut zurecht – aber sie brauchen Kontext. Deshalb analysieren wir regelmäßig die echten Chatverläufe, um fehlendes Wissen nachzupflegen und den SystemPrompt zu verbessern.&lt;/p&gt;
&lt;p data-end=&quot;5770&quot; data-start=&quot;5525&quot;&gt;Ein Beispiel: LLMs kennen in der Regel &lt;span data-end=&quot;5592&quot; data-start=&quot;5564&quot;&gt;nicht das &lt;/span&gt;&lt;strong data-end=&quot;5592&quot; data-start=&quot;5564&quot;&gt;aktuelle Datum&lt;/strong&gt;. Wird nach „Rechnung im Mai 2025“ gefragt, kann das Modell falsche Schlüsse ziehen, weil seine Trainingsdaten aus 2024 sind. Wir lösen das inzwischen, indem wir das aktuelle Datum automatisch im SystemPrompt mitgeben.&lt;/p&gt;
&lt;hr data-end=&quot;5775&quot; data-start=&quot;5772&quot; /&gt;
&lt;h2 data-end=&quot;5801&quot; data-start=&quot;5777&quot;&gt;Chatbots mit KI Hilfe testen&lt;/h2&gt;
&lt;p data-end=&quot;6043&quot; data-start=&quot;5803&quot;&gt;Klassische Tests wie Unit- oder Integrationstests reichen bei KI-Systemen nicht aus, da die Antworten nicht immer eindeutig oder fest definiert sind. Entscheidend ist vielmehr, ob die Antworten hilfreich, konsistent oder missverständlich sind.&lt;/p&gt;&lt;p data-end=&quot;6043&quot; data-start=&quot;5803&quot;&gt;Deshalb setzen wir zusätzlich &lt;b&gt;ein zweites KI-Modell ein, das die Antworten unseres Chatbots bewertet&lt;/b&gt;. In folgendem Video führe ich das vor.&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/HllakE0R2fo&quot; width=&quot;320&quot; youtube-src-id=&quot;HllakE0R2fo&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;br /&gt;
&lt;hr data-end=&quot;6348&quot; data-start=&quot;6345&quot; /&gt;
&lt;h2 data-end=&quot;6391&quot; data-start=&quot;6350&quot;&gt;Vektor-Datenbanken: Einfach reicht oft&lt;/h2&gt;
&lt;p data-end=&quot;6691&quot; data-start=&quot;6393&quot;&gt;Für viele Anwendungen braucht es keine dedizierte Vektor-Datenbank wie Milvus oder chroma. Wir haben sehr gute Erfahrungen mit der &lt;strong data-end=&quot;6577&quot; data-start=&quot;6528&quot;&gt;Vektor-Erweiterung für MongoDB und PostgreSQL&lt;/strong&gt; gemacht – ideal für einfache RAG-Szenarien. Der große Vorteil: keine zusätzliche Infrastruktur, falls ihr diese stark verbreiteten Datenbanken schon im Einsatz habt.&lt;/p&gt;
&lt;hr data-end=&quot;6696&quot; data-start=&quot;6693&quot; /&gt;
&lt;h2 data-end=&quot;6735&quot; data-start=&quot;6698&quot;&gt;Fazit&lt;/h2&gt;&lt;p data-end=&quot;6937&quot; data-start=&quot;6737&quot;&gt;&lt;b&gt;KI-Chatbots/-Anwendungen sind keine Raketenwissenschaft&lt;/b&gt; – besonders nicht in Java. Wer Webanwendungen bauen kann, bringt die besten Voraussetzungen mit, um auch KI-gestützte Chatbots schnell und effektiv umzusetzen. Moderne Frameworks wie Spring AI oder LangChain4J machen den Einstieg leicht – ganz ohne Python.&lt;/p&gt;&lt;p data-end=&quot;6937&quot; data-start=&quot;6737&quot;&gt;Der schnellste Weg zum Erfolg führt über erste &lt;b&gt;Prototypen statt langer PowerPoint-Präsentationen&lt;/b&gt;. Schon ein einfacher Prototyp, der grundlegende Fragen beantwortet, überzeugt Kollegen und schafft interne Unterstützung. Ein aufwendiges Frontend ist dafür nicht nötig – der Nutzen steht im Vordergrund.&lt;/p&gt;&lt;p data-end=&quot;6937&quot; data-start=&quot;6737&quot;&gt;Am wichtigsten: &lt;b&gt;Nutzt echte Kundendialoge und lernt daraus.&lt;/b&gt; Kunden sind kreativer als jedes Testskript und zeigen durch ihre Fragen, wo die KI glänzt und wo noch nachgebessert werden muss. Die enge Zusammenarbeit mit dem Service-Team hilft, echte Probleme zu verstehen und den Chatbot gezielt zu verbessern.&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2025/05/chatbot-lessons-learned.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgi1gPoJtvD_AvKz7Z9FOOGKCEwbWIN8gIihz5bWv1RnnBfl_gaQ-P9c6YVglJatgulNq9jMWjGmVLJ34CDMatrINkw-toXNM2qRixoy9P8tz10fvf9OGOPB8JS6-SGATGC9XxVsfwMWZ99lknWVzE4f3qzIsqizHOXD5tI3gaQMtF1DN8CxL-fxa4SPu8B=s72-c" height="72" width="72"/><georss:featurename>53 Bonn, Deutschland</georss:featurename><georss:point>50.73743 7.0982068</georss:point><georss:box>27.919113538936081 -28.0580432 73.555746461063933 42.2544568</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-7096260054215534925</guid><pubDate>Thu, 17 Apr 2025 13:55:00 +0000</pubDate><atom:updated>2025-04-24T19:09:53.395+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">DevOps</category><title>GIT Einführung und Best Practices</title><description>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Ob Entwicklung, Test, Betrieb oder Projektmanagement – Git ist nicht nur ein Thema für Developer. Es ist ein zentrales Werkzeug, das die Zusammenarbeit im gesamten IT-Team unterstützt und absichert. Doch was macht Git eigentlich so wertvoll?&lt;/b&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Zentraler Zugriff auf den Code&lt;/h3&gt;&lt;p&gt;Mit Git arbeitet das ganze Team an einer gemeinsamen Codebasis. Egal ob Backend, Frontend, Testautomatisierung oder Infrastruktur-as-Code – alle Dateien liegen im selben Repository. Das sorgt für Transparenz und verhindert, dass einzelne Teammitglieder mit unterschiedlichen Versionen arbeiten. Jeder kann nachvollziehen, wie sich der Code entwickelt und welche Änderungen aktuell sind.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Nachvollziehbarkeit von Änderungen&lt;/h3&gt;&lt;p&gt;Git protokolliert jede Änderung. Wer hat wann was geändert – und warum? Diese Informationen sind Gold wert, wenn man verstehen möchte, warum ein Fehler entstanden ist oder wie ein Feature genau implementiert wurde. Gute Commit-Nachrichten und sauber strukturierte Branches machen es möglich, die Entwicklung lückenlos nachzuvollziehen.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Sicherheit durch Versionierung&lt;/h3&gt;&lt;p&gt;Mit Git ist jede Änderung gesichert. Man kann jederzeit auf einen früheren Stand zurückspringen – etwa wenn nach einem Deployment plötzlich ein Fehler auftritt. Git fungiert damit wie ein intelligentes Backup-System für die gesamte Codebasis. Es schützt nicht nur vor Datenverlust, sondern gibt auch Sicherheit bei Experimenten und schnellen Hotfixes.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6oSvGPz3kQB-K_Sfc00oX3XGT-WLmenREerLgx0smURb2giKfpHJrQoErUG75qm2oixYD_YflDOImQ5Z_2CaH865klNPXxGrQ0MnWQO1-3zdJICjuYjpmQI_AI8AHGueEZB5vueI0n1-Jhxc4-_HLVS7zU9egndlmyXSFb0vDYMK1vuq5EKpIJRgmjcIn/s1024/ChatGPT%20Image%20Apr%2017,%202025,%2003_51_58%20PM.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6oSvGPz3kQB-K_Sfc00oX3XGT-WLmenREerLgx0smURb2giKfpHJrQoErUG75qm2oixYD_YflDOImQ5Z_2CaH865klNPXxGrQ0MnWQO1-3zdJICjuYjpmQI_AI8AHGueEZB5vueI0n1-Jhxc4-_HLVS7zU9egndlmyXSFb0vDYMK1vuq5EKpIJRgmjcIn/w640-h640/ChatGPT%20Image%20Apr%2017,%202025,%2003_51_58%20PM.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Git Basics – Die wichtigsten Konzepte&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Repository&lt;/h3&gt;&lt;p&gt;Ein Repository ist der Speicherort für den Projektcode und dessen gesamte Versionsgeschichte. Man unterscheidet zwischen zwei Arten:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Das lokale Repository liegt auf dem eigenen Rechner. Hier arbeitet man direkt am Code, führt Änderungen durch und sichert diese in Form von Commits.&lt;/li&gt;&lt;li&gt;Das Remote Repository liegt auf einem Server, zum Beispiel bei GitHub oder GitLab. Es dient als zentrale Anlaufstelle für das gesamte Team. Von hier wird der Code verteilt, synchronisiert und zusammengeführt.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Ein typischer Arbeitsablauf beginnt damit, dass man ein Remote Repository einmalig klont und anschließend lokal daran arbeitet.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Commit und Push&lt;/h3&gt;&lt;p&gt;Änderungen am Code werden in sogenannten Commits gesichert. Jeder Commit enthält eine Momentaufnahme der geänderten Dateien sowie eine Nachricht, die beschreibt, was geändert wurde. So entsteht eine nachvollziehbare Änderungshistorie.&lt;/p&gt;&lt;p&gt;Sobald ein oder mehrere Commits lokal erstellt wurden, kann man sie durch einen Push an das Remote Repository übertragen. Erst dann werden die Änderungen für andere sichtbar.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Pull&lt;/h3&gt;&lt;p&gt;Während man selbst Änderungen vornimmt, arbeitet auch das Team weiter. Damit die eigene lokale Version aktuell bleibt, werden die Änderungen anderer Teammitglieder regelmäßig aus dem Remote Repository abgerufen. Dieser Vorgang heißt Pull.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Branch&lt;/h3&gt;&lt;p&gt;Ein Branch ist ein Entwicklungszweig, in dem Änderungen isoliert vorgenommen werden können. Auf diese Weise lassen sich neue Features, Bugfixes oder Experimente entwickeln, ohne den Hauptzweig des Projekts zu beeinflussen.&lt;/p&gt;&lt;p&gt;Ein Projekt hat in der Regel einen Hauptbranch (z. B. main oder master), von dem aus neue Branches erstellt und später wieder integriert werden.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Merge&lt;/h3&gt;&lt;p&gt;Wenn ein Branch fertig entwickelt wurde, sollen die Änderungen wieder in den Hauptzweig einfließen. Mit einem Merge werden die beiden Zweige zusammengeführt, wobei die komplette Entwicklungshistorie erhalten bleibt. Git erstellt dabei einen zusätzlichen Merge-Commit, der die Zusammenführung dokumentiert und die Nachvollziehbarkeit sicherstellt.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Log und History&lt;/h3&gt;&lt;p&gt;Git speichert jede Änderung mit Zeitstempel, Autor und Nachricht. Über die Historie lässt sich jederzeit nachvollziehen, wer wann welche Änderung vorgenommen hat. Das ist besonders hilfreich, wenn man verstehen möchte, warum ein bestimmter Fehler auftritt oder wie sich ein Feature entwickelt hat.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/8HQrce8sWSU&quot; width=&quot;320&quot; youtube-src-id=&quot;8HQrce8sWSU&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Video mit Demo zu diesem Abschnitt.&lt;/i&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;GIT im täglichen Einsatz &amp;amp; Best Practices&lt;/h2&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Git Kommandozeile oder Tools&lt;/h3&gt;&lt;div&gt;Git kann auf unterschiedliche Weise genutzt werden – je nach persönlicher Vorliebe und eingesetztem Werkzeug. Viele moderne Entwicklungsumgebungen wie IntelliJ IDEA oder Visual Studio Code haben Git bereits integriert. So lassen sich typische Aktionen wie Commit, Pull oder das Anlegen von Branches direkt aus der IDE heraus durchführen – ohne zwischen Programmen wechseln zu müssen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Alternativ kann man Git auch direkt über die Kommandozeile bedienen. Das bietet maximale Kontrolle und hilft dabei, die Abläufe im Hintergrund besser zu verstehen. Besonders für wiederkehrende Aufgaben oder automatisierte Prozesse ist die Kommandozeile oft die bevorzugte Wahl.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ein weiterer beliebter Weg ist die Nutzung von TortoiseGit. Dieses Tool integriert sich direkt in den Windows Explorer und macht viele Git-Funktionen über das Kontextmenü mit der rechten Maustaste zugänglich. Gerade wer häufig mit Dateien und Ordnern arbeitet, profitiert von dieser nahtlosen Einbettung in die gewohnte Arbeitsumgebung.&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Bei kleinen Änderung&lt;/h3&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Pull first&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;Bevor man Änderungen an einem Projekt vornimmt, sollte man sich angewöhnen, zunächst einen Pull durchzuführen. Damit stellt man sicher, dass man auf dem aktuellen Stand des Remote-Repositories arbeitet und alle Änderungen der Teammitglieder berücksichtigt.&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Gerade bei kleineren Anpassungen – etwa an Konfigurationsdateien, Texten oder Dokumentation – ist die Gefahr groß, dass mehrere Personen gleichzeitig dieselbe Datei bearbeiten. Ein vorheriger Pull reduziert das Risiko von Merge-Konflikten erheblich und sorgt dafür, dass die eigenen Änderungen reibungslos integriert werden können.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Diese einfache Gewohnheit spart im Alltag viel Zeit und vermeidet unnötige Unterbrechungen im Arbeitsfluss.&lt;/div&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Review vor Commit &amp;amp; Push&lt;/h4&gt;&lt;div&gt;Bevor Änderungen committet und gepusht werden, sollte man sie kurz überprüfen. Ein Blick auf die geänderten Dateien und Inhalte hilft, versehentliche Änderungen oder temporäre Code-Anpassungen zu erkennen. Dieser Schritt verbessert die Qualität und verhindert unnötige Korrekturen im Nachhinein.&lt;/div&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Gute Commit-Message mit Ticket-Referenz&lt;/h4&gt;&lt;div&gt;In der Teamarbeit ist eine eindeutige Ticket-Referenz in der Commit-Message besonders wichtig. Sie ermöglicht es, jede Änderung direkt einer Aufgabe oder einem Fehlerbericht zuzuordnen. Idealerweise folgt die Nachricht einem einheitlichen Format im Team – zum Beispiel:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;JIRA-123: Validation for email field added&lt;/span&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Ein kurzer, präziser Satz reicht meist aus, wenn er zusammen mit der Ticket-ID den Kontext klar macht.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Ein Commit – eine Änderung&lt;/h4&gt;&lt;div&gt;Ein Commit sollte sich immer auf genau eine inhaltliche Änderung konzentrieren. Wenn man beispielsweise gleichzeitig die Texte in der Benutzeroberfläche ändert und ein neues Logging einführt, gehören diese Änderungen in getrennte Commits. So bleibt die Historie nachvollziehbar, und einzelne Änderungen lassen sich bei Bedarf gezielt zurückverfolgen oder rückgängig machen.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Revert statt Löschen&lt;/h4&gt;&lt;div&gt;Ein Revert nimmt eine Änderung zurück, indem ein neuer Commit erzeugt wird, der genau das Gegenteil der ursprünglichen Änderung macht. Der ursprüngliche Commit bleibt in der Historie erhalten, sodass der Verlauf vollständig nachvollziehbar bleibt – es gibt also keine Löschung, sondern eine bewusste Korrektur.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ein Revert muss nicht unbedingt einen Fehler beheben. Manchmal hat man bewusst etwas ausprobiert, das später nicht mehr benötigt wird, und möchte es daher zurücknehmen. Dabei entstehen nach einem Revert zwei Commits im Branch: der ursprüngliche Commit und der Revert-Commit.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Obwohl es grundsätzlich möglich ist, Commits zu löschen, ist dies in der Praxis eher umständlich.&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Große Änderung mit feature-Branches&lt;/h3&gt;&lt;div&gt;&lt;div&gt;Bei größeren Änderungen, die umfangreiche Anpassungen oder die Entwicklung neuer Funktionen betreffen, ist es ratsam, einen Feature-Branch zu verwenden. Ein Feature-Branch ist ein separater Entwicklungszweig, der vom Hauptzweig (z. B. main oder master) abzweigt und speziell für die Entwicklung eines bestimmten Features oder einer größeren Änderung gedacht ist.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Der Vorteil von Feature-Branches liegt darin, dass man isoliert an neuen Funktionen arbeiten kann, ohne die Stabilität des Hauptzweigs zu gefährden. Änderungen im Feature-Branch werden lokal entwickelt, getestet und überprüft, bevor sie durch einen Merge oder Pull-Request in den Hauptzweig integriert werden.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Mit dieser Methode bleibt der Hauptzweig stets stabil und funktionsfähig, während das Team parallel an neuen Features oder Verbesserungen arbeitet. Feature-Branches bieten außerdem den Vorteil, dass mehrere Teammitglieder gleichzeitig an verschiedenen Features arbeiten können, ohne dass sich ihre Änderungen gegenseitig beeinflussen.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLn8yH-yM93aLeGPuM93zr_HTayxGG1tT2_d9_puwsgpFC6nFDBP87vQIZ48SDEu-qo-f8Z9zQ1BHGF40oihkK0Ms0ckd-YcxF-T_Z7x54Svyp0t3DNpGVPc3VnZQmSL1llDgzl2rWAx_kE8Iw68xHePPaWuOuqsZ1PzFvR4vJKv2rD7VrlYEQfBuwYD8W/s511/branch.drawio.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;111&quot; data-original-width=&quot;511&quot; height=&quot;141&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLn8yH-yM93aLeGPuM93zr_HTayxGG1tT2_d9_puwsgpFC6nFDBP87vQIZ48SDEu-qo-f8Z9zQ1BHGF40oihkK0Ms0ckd-YcxF-T_Z7x54Svyp0t3DNpGVPc3VnZQmSL1llDgzl2rWAx_kE8Iw68xHePPaWuOuqsZ1PzFvR4vJKv2rD7VrlYEQfBuwYD8W/w640-h141/branch.drawio.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Rebase&lt;/h4&gt;&lt;div&gt;&lt;div&gt;Ein Rebase wird häufig eingesetzt, bevor ein Feature-Branch in den master- oder main-Branch gemerged wird. Ziel ist es, den Feature-Branch auf den aktuellen Stand des Hauptzweigs zu bringen, um spätere Merge-Konflikte zu vermeiden. Dabei werden die eigenen Commits so umgehängt, als wären sie auf dem aktuellen Stand von master entstanden.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Technisch gesehen verschiebt Git dabei die Commits aus dem Feature-Branch auf die aktuelle Spitze des Hauptzweigs. So entsteht eine lineare Historie, in der alle Änderungen sauber aufeinander folgen – ganz ohne zusätzlichen Merge-Commit.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI3XfN8FQ17DKRE4l_7GDOMVomeQPtswpfZBSBsTyFZ2OZRSVNPJtkselwhh9UMwo4noRp1KlSFIB2EuPTa4AnMbLcwDlMg0Sp0jMlarr5e0tx57zCKQNAByfUj0MOmg1ep1tkwyHL3LmrNg_jSgZGTSsA2K7-D_7qnX1zy2qtTTfiu0shH5ROLKLdswMd/s571/rebase.drawio.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;111&quot; data-original-width=&quot;571&quot; height=&quot;124&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI3XfN8FQ17DKRE4l_7GDOMVomeQPtswpfZBSBsTyFZ2OZRSVNPJtkselwhh9UMwo4noRp1KlSFIB2EuPTa4AnMbLcwDlMg0Sp0jMlarr5e0tx57zCKQNAByfUj0MOmg1ep1tkwyHL3LmrNg_jSgZGTSsA2K7-D_7qnX1zy2qtTTfiu0shH5ROLKLdswMd/w640-h124/rebase.drawio.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Empfehlung: Merge mit Squash&lt;/h4&gt;&lt;div&gt;Bevor ein Feature-Branch in den master- oder main-Branch gemerged wird, empfiehlt es sich, alle Commits aus dem Feature-Branch zu einem einzigen Commit zusammenzufassen – dieser Vorgang wird „Squash“ genannt. Gerade bei der Entwicklung eines Features entstehen oft viele kleine Commits, die aus Sicht der Projekt-Historie nicht einzeln relevant sind.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ein Merge mit Squash sorgt dafür, dass im Hauptzweig nur ein klar benannter Commit auftaucht, der das gesamte Feature beschreibt. Das hält die Historie übersichtlich und hilft, die Entwicklungsschritte später leichter nachzuvollziehen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Diese Methode ist besonders nützlich bei der Arbeit im Team, da sie Wildwuchs in der Historie vermeidet und den Überblick über eingeführte Features erleichtert.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Git Blame - wer war es?&lt;/h3&gt;&lt;div&gt;Mit dem Git-Befehl blame kann man herausfinden, wer eine bestimmte Zeile in einer Datei zuletzt geändert hat – und in welchem Commit das passiert ist. Das ist besonders hilfreich, wenn man verstehen möchte, warum ein bestimmter Code geschrieben wurde oder wer bei Rückfragen der richtige Ansprechpartner ist.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Git Diff - was hat sich genau geändert?&lt;/h3&gt;&lt;div&gt;Mit git diff kann man sich Unterschiede zwischen zwei Versionen von Dateien anzeigen lassen – etwa zwischen dem aktuellen Stand im Arbeitsverzeichnis und dem letzten Commit oder zwischen zwei Branches. So sieht man genau, welche Zeilen hinzugefügt, geändert oder entfernt wurden.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Tags&lt;/h3&gt;&lt;div&gt;Tags in Git sind Markierungen, mit denen bestimmte Commits eindeutig gekennzeichnet werden – etwa für Versionen, die für den Produktivbetrieb freigegeben wurden. Ein Tag ist fest mit einem bestimmten Stand des Codes verbunden und verändert sich nicht mehr.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tags eignen sich besonders gut zur Kennzeichnung von produktionsreifen Versionen. Anstatt separate Branches wie release oder prod zu pflegen, kann direkt ein Tag auf den Commit gesetzt werden, der in Produktion geht. So bleibt die Historie klar und nachvollziehbar: Man erkennt genau, welche Version zu welchem Zeitpunkt deployed wurde.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In vielen Projekten werden Tags nach dem Prinzip der semantischen Versionierung vergeben – zum Beispiel v1.0.5. So lässt sich schnell erkennen, ob es sich um einen neuen Funktionsumfang, ein Bugfix oder eine größere Änderung handelt.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Git ist weit mehr als nur ein Versionierungstool – es ist ein unverzichtbares Fundament moderner Softwareentwicklung und ein Schlüsselbestandteil effektiver Teamzusammenarbeit. Git ermöglicht nicht nur die Verwaltung von Code, sondern spielt auch eine zentrale Rolle in modernen Release- und Deployment-Prozessen. Durch die enge Integration in Build- und Deployment-Pipelines können Änderungen im Git-Repository automatisch ganze Prozesse anstoßen, von der Erstellung von Builds bis hin zum Deployment in die Produktion. Dies fördert eine nahtlose und automatisierte Entwicklung und sorgt für eine effiziente, fehlerresistente Bereitstellung von Software.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2025/04/git.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6oSvGPz3kQB-K_Sfc00oX3XGT-WLmenREerLgx0smURb2giKfpHJrQoErUG75qm2oixYD_YflDOImQ5Z_2CaH865klNPXxGrQ0MnWQO1-3zdJICjuYjpmQI_AI8AHGueEZB5vueI0n1-Jhxc4-_HLVS7zU9egndlmyXSFb0vDYMK1vuq5EKpIJRgmjcIn/s72-w640-h640-c/ChatGPT%20Image%20Apr%2017,%202025,%2003_51_58%20PM.png" height="72" width="72"/><georss:featurename>64 Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-886712925202284099</guid><pubDate>Sat, 05 Apr 2025 06:02:00 +0000</pubDate><atom:updated>2025-04-05T09:55:01.294+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">test</category><title>Testgetriebene Entwicklung (TDD) Schritt für Schritt</title><description>&lt;p&gt;&lt;b&gt;Testgetriebene Entwicklung besteht aus 3 Schritten:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: red;&quot;&gt;Rot: &lt;/span&gt;&lt;span&gt;Schreib ausreichend viele fehlschlagende Unit Tests.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;Grün: &lt;/span&gt;&lt;span&gt;Implementiere nur so viel Code, dass alle Unit Tests erfolgreich durchlaufen.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #2b00fe;&quot;&gt;Blau: &lt;/span&gt;&lt;span&gt;Falls nötig, refaktorisiere den Code und Deine Unit Tests.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;b&gt;Dieser Blog-Artikel demonstriert TDD anhand des Clean Code Bowling Game Katas in Kotlin mit JUnit Tests. Außerdem betrachten wir wie KI unsere Arbeitsweise bei TDD verändert. Passen die Idee von TDD und aktuelle KI-Tools wie GitHub Copilot oder ChatGPT überhaupt noch zusammen?&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Testgetriebene Entwicklung (TDD)&lt;/b&gt;&lt;/h2&gt;&lt;div&gt;Ich kenne testgetriebene Entwicklung aus Extreme Programming (von Kent Beck) und Clean Code (von Robert C. Martin). Beide beschreiben den TDD-Zyklus als Entwicklungsprozess in 3 sich wiederholenden Schritten. Im folgenden Bild seht Ihr den Zyklus mit entsprechenden Farben, abgeleitet aus dem Status der Testergebnisse.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirxUGO0_1R9RcqVvSYqrhLgRfGvRc8WzOVsb-HJWxO5JyAFJIpPqGB1YCvwYamkdVBxmjxv6zzcw_hwBjisAPpbpaHziobNthNMdoONGZFh5jntT4_2t7DoKia_uA3LutJ5avE5lMhBjEz/s548/TDD-Kreis.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;445&quot; data-original-width=&quot;548&quot; height=&quot;325&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirxUGO0_1R9RcqVvSYqrhLgRfGvRc8WzOVsb-HJWxO5JyAFJIpPqGB1YCvwYamkdVBxmjxv6zzcw_hwBjisAPpbpaHziobNthNMdoONGZFh5jntT4_2t7DoKia_uA3LutJ5avE5lMhBjEz/w400-h325/TDD-Kreis.PNG&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Rot - Test schlägt fehl&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;In der ersten Phase ROT wird mindestens ein neuer Testfall geschrieben oder ein bestehender erweitert. Wichtig ist dabei, dass dieser Testfall dann fehlschlägt. Daher die Status Farbe Rot. Es ist erlaubt mehrere Testfälle zu schreiben. Wichtig ist vor allem, dass die Tests &lt;b&gt;vor&lt;/b&gt; dem eigentlich Code geschrieben werden. Meistens schlägt der angepasste Test aufgrund einer Assertion (Überprüfung einer Testbedingung) fehl. Am Anfang kann der Test auch aufgrund eines Compile-Fehlers fehlschlagen, wenn z. B. eine nicht existierende Klasse getestet werden soll. Weiter unten demonstriere ich das.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Grün - Implementierung des Testfalls&lt;/h3&gt;&lt;div&gt;In der zweiten Phase GRÜN wird die Implementierung des Testfalls bzw. der produktive Code geschrieben. Dabei ist es wichtig möglichst minimalistisch vorzugehen. Schreibt nur den notwendigen Code, so dass der Testfall grün wird. Mehr produktiven Code schreibt ihr erst wieder einen nächsten TDD-Zyklus. Ein neuer Testfall zur Überprüfung einer Boolean-Methode erwartet z. B. true als Rückgabe. Die notwendige bzw. minimale Implementierung dieser Methode ist &quot;return true&quot; - mehr nicht!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Manche EntwicklerInnen fangen im ersten Zyklus mit Skeleton Implementierungen an, damit sie fehlschlagende Testfälle aufgrund von Compile-Fehler und die trivialen Methoden Implementierungen überspringen. Passt dabei aber auf, dass ihr wirklich bei einer Skeleton Implementierung bleibt, da ihr ansonsten keine testgetriebene Entwicklung mehr macht.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Blau - Refactoring&lt;/h3&gt;&lt;div&gt;Die dritte Phase ist optional. Aus Clean Code Büchern wisst ihr, wie wichtig sauberer Code ist. Nehmt euch immer die Zeit zu refaktorisieren bzw. aufzuräumen, wenn alle Test grün sind. Dann seht ihr durch die Tests direkt, ob eure Änderungen etwas kaputt gemacht haben oder ob das Refactoring erfolgreich war und alles funktioniert. Das Refaktorisieren betrifft sowohl den produktiven Code als auch die Unit Tests. Sauberer Code wird überall benötigt, schaut euch dazu diesen Blog-Artikel an:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/JUnit5andSpringBootTest.html&quot;&gt;JUnit5andSpringBootTest.html&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;TDD Schritt für Schritt Demo&lt;/h2&gt;&lt;div&gt;In einem vorherigen Blog-Artikel stellte ich das Bowling Game Kata vor, siehe &lt;a href=&quot;https://agile-coding.blogspot.com/2021/06/clean-code-dojo.html&quot;&gt;clean-code-dojo.html&lt;/a&gt;. Mittlerweile implementierte ich dieses Kata mittles testgetriebener Entwicklung. Die Implementierung habe ich in der Programmiersprache Kotlin mit JUnit 5 gemacht. Ich zeige euch hier die einzelnen Schritte aus den ersten TDD-Zyklen. Den fertigen Code mit einem Commit nach jedem Zyklus findet ihr in GitHub: &lt;a href=&quot;https://github.com/elmar-brauch/cleanCodeDojo/blob/master/src/test/kotlin/de/bsi/kata/tdd/bowling/GameTest.kt&quot;&gt;https://github.com/elmar-brauch/cleanCodeDojo/.../GameTest.kt&lt;/a&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Zyklus: ROT&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;GameTest {&lt;br /&gt;    &lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;bowlingGame&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;val &lt;/span&gt;game = Game()&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-weight: normal;&quot;&gt;Vorab habe ich nur ein leeres Projekt erstellt. Der Test &lt;span style=&quot;font-family: courier;&quot;&gt;bowlingGame &lt;/span&gt;schlägt tatsächlich fehl, weil die Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Game &lt;/span&gt;zu diesem Zeitpunkt noch nicht existiert - also rot wegen Compile-Fehler.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/h4&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Zyklus: GRÜN&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;Game {}&lt;/pre&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-weight: normal;&quot;&gt;Das ist die minimale Implementierung, um den Test aus dem ersten Zyklus grün zu bekommen...&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-weight: normal;&quot;&gt;Den optionalen Refactoring Schritt können wir überspringen.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/h4&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Zyklus: ROT&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;bowlingGame&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;val &lt;/span&gt;game = Game()&lt;br /&gt;    game.role(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;)&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Wieder Fehlschlag wegen Compile-Fehler, da Methode &lt;span style=&quot;font-family: courier;&quot;&gt;role &lt;/span&gt;nicht existiert.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Zyklus: GRÜN&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;Game {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;role&lt;/span&gt;(pins: Int) {}&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Erneut nur eine minimale Implementierung, um den Compile-Fehler zu beheben.&lt;/li&gt;&lt;li&gt;Ihr könnt diese Zyklen auch zusammenfassen, indem ihr in ROT direkt den Test aus Zyklus 2 schreibt oder mit der Skeleton Implementierung startet.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Zyklus: ROT&lt;/h4&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly0&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;val &lt;/span&gt;game = Game()&lt;br /&gt;    game.role(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;)&lt;br /&gt;    assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;game.score())&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly1&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;val &lt;/span&gt;game = Game()&lt;br /&gt;    game.role(&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;)&lt;br /&gt;    assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;game.score())&lt;br /&gt;}&lt;/pre&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Hier hätte man auch 2 Zyklen machen können: Compile-Fehler für Methode &lt;span style=&quot;font-family: courier;&quot;&gt;score &lt;/span&gt;und Test für &lt;span style=&quot;font-family: courier;&quot;&gt;role(0)&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;role(1)&lt;/span&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Zyklus: GRÜN&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;Game {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private val &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled &lt;/span&gt;= IntArray(&lt;span style=&quot;color: #6897bb;&quot;&gt;21&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;role&lt;/span&gt;(pins: Int) {&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;] = pins&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;score&lt;/span&gt;(): Int {&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;.&lt;span style=&quot;color: #ffc66d; font-style: italic;&quot;&gt;sum&lt;/span&gt;()&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;font-weight: 400;&quot;&gt;&lt;li&gt;&lt;i&gt;Nachträgliche Anmerkung: Für pinsRolled ein Array der Größe 21 zu verwenden, ist zu diesem Zeitpunkt zu früh und ein Verstoß gegen das Prinzip den minimales Code zu schreiben. Ein einfacher Int reicht in diesem Zyklus.&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/h4&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Zyklus: BLAU&lt;/h4&gt;&lt;div&gt;Die Test-Klasse kann nun refaktorisiert werden, indem wir &lt;span style=&quot;font-family: courier;&quot;&gt;game &lt;/span&gt;als Klassenattribut verwenden:&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;GameTest {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;val &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game &lt;/span&gt;= Game()&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly0&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.role(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;)&lt;br /&gt;        assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.score())&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly1&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.role(&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;)&lt;br /&gt;        assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.score())&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Zyklus: ROT&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;GameTest {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private val &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game &lt;/span&gt;= Game()&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly0&lt;/span&gt;() {&lt;br /&gt;        roleMany(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;20&lt;/span&gt;)&lt;br /&gt;        assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.score())&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #bbb529;&quot;&gt;@Test&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleOnly1&lt;/span&gt;() {&lt;br /&gt;        roleMany(&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;20&lt;/span&gt;)&lt;br /&gt;        assertEquals(&lt;span style=&quot;color: #6897bb;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.score())&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleMany&lt;/span&gt;(pins: Int&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;roles: Int) {&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;for &lt;/span&gt;(i &lt;span style=&quot;color: #cc7832;&quot;&gt;in &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;..roles)&lt;br /&gt;            &lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.role(pins)&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Statt einem Wurf machen wir nun viele bzw. 20, so dass die Assertion im &lt;span style=&quot;font-family: courier;&quot;&gt;roleOnly1 &lt;/span&gt;Test fehlschlägt.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Zyklus: GRÜN&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;Game {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private val &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled &lt;/span&gt;= IntArray(&lt;span style=&quot;color: #6897bb;&quot;&gt;21&lt;/span&gt;)&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private var &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;role &lt;/span&gt;= &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;role&lt;/span&gt;(pins: Int) {&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #9876aa;&quot;&gt;role&lt;/span&gt;++] = pins&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Zum Fixen des fehlgeschlagenen Tests, mache ich nur Anpassungen in der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;role&lt;/span&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Zyklus: BLAU&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@ParameterizedTest&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@CsvSource&lt;/span&gt;(&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;0,0,20,&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;20,1,20&quot;&lt;/span&gt;)&lt;br /&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleTest&lt;/span&gt;(expectedScore: Int&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;pins: Int&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;roles: Int) {&lt;br /&gt;    roleMany(pins&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;roles)&lt;br /&gt;    assertEquals(expectedScore&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;game&lt;/span&gt;.score())&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Mittels parametrisierten Tests können wir die JUnit Tests &lt;span style=&quot;font-family: courier;&quot;&gt;roleOnly0&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;roleOnly1&lt;/span&gt; zusammenfassen. Die Testfälle für das perfekte Spiel (nur Strikes) &quot;300,10,12&quot; und nur Spares &quot;150,5,21&quot; können wir dann später einfach in der &lt;span style=&quot;font-family: courier;&quot;&gt;@CsvSource&lt;/span&gt; aufnehmen.&lt;br /&gt;Weitere Details zu parametrisierten Tests mit JUnit 5 findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/JUnit5andSpringBootTest.html&quot;&gt;JUnit5andSpringBootTest.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;5. Zyklus: ROT&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@ParameterizedTest&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #bbb529;&quot;&gt;@CsvSource&lt;/span&gt;(&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;0,0,20,&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;20,1,20&quot;, &quot;150,5,21&quot;&lt;/span&gt;)&lt;br /&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;roleTest&lt;/span&gt;(expectedScore: Int&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;pins: Int&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;roles: Int)...&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;In diesem Zyklus füge ich den neuen Testfall &quot;Spare in jeder Runde&quot; bzw. &quot;150,5,21&quot; hinzu. In diesem Testfall wirft der Bowling Spieler mit jedem Wurf 5 Pins um.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;5. Zyklus: GRÜN&lt;/h4&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #2b2b2b; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;class &lt;/span&gt;Game {&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private val &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled &lt;/span&gt;= IntArray(&lt;span style=&quot;color: #6897bb;&quot;&gt;21&lt;/span&gt;)&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;private var &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;currentRole &lt;/span&gt;= &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;role&lt;/span&gt;(pins: Int) {&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #9876aa;&quot;&gt;currentRole&lt;/span&gt;] = pins&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;currentRole&lt;/span&gt;++&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;score&lt;/span&gt;(): Int {&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;var &lt;/span&gt;sum = &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #6897bb;&quot;&gt;18&lt;/span&gt;] + &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #6897bb;&quot;&gt;19&lt;/span&gt;] + &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[&lt;span style=&quot;color: #6897bb;&quot;&gt;20&lt;/span&gt;]&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;for &lt;/span&gt;(role &lt;span style=&quot;color: #cc7832;&quot;&gt;in &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;..&lt;span style=&quot;color: #6897bb;&quot;&gt;17&lt;/span&gt;) {&lt;br /&gt;            sum += &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[role]&lt;br /&gt;            &lt;span style=&quot;color: #cc7832;&quot;&gt;if &lt;/span&gt;(spare(role))&lt;br /&gt;                sum += &lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;[role + &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;]&lt;br /&gt;        }&lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;sum&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;private fun &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;spare&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;(role: Int) = role % &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;2 &lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;== &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;0 &lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;&amp;amp;&amp;amp;&lt;br /&gt;            &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;[role] + &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;pinsRolled&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;[role + &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;] == &lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #6897bb;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #a9b7c6;&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Hier habe ich ein Refaktoring schon während der Implementierung gemacht, ich habe das Klassen Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;role &lt;/span&gt;in &lt;span style=&quot;font-family: courier;&quot;&gt;currentRole &lt;/span&gt;umbenannt, da es ein Pointer zum aktuellen Wurf ist.&lt;/li&gt;&lt;li&gt;Ansonsten wurde die Berechnung der Punkte in der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;score&lt;/span&gt; so angepasst, dass die Punkte des nächsten Wurfs nach einem Spare doppelt zählen.&lt;/li&gt;&lt;li&gt;Außerdem habe ich eine Hilfsmethode &lt;span style=&quot;font-family: courier;&quot;&gt;spare&lt;/span&gt; zum Erkennen von Spares geschrieben. Um unseren Test grün zu bekommen, muss ich in diesem Fall noch nicht zwischen Spare und Strike unterscheiden. Daher akzeptiert die &lt;span style=&quot;font-family: courier;&quot;&gt;spare &lt;/span&gt;Methode in diesem Zyklus auch Strikes.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Strikes werden zu diesem Zeitpunkt nicht richtig berechnet, da es dafür auch noch keinen Testfall gibt.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Die restlichen Zyklen...&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;Mittlerweile sollte das Vorgehen beim testgetriebenen Entwickeln klar geworden sein. In den folgenden Zyklen schreibe ich noch Tests für Spiele mit Strikes und Fehlerbehandlungen, wenn z.B. ein weiterer Wurf gemacht wird, obwohl das Spiel vorbei ist oder eine negative Zahl an die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;role &lt;/span&gt;übergeben wird. Das zeige ich hier aber nicht mehr. &lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Wenn euch der komplette Code interessiert, schaut ihn euch hier in GitHub an:&amp;nbsp;&lt;a href=&quot;https://github.com/elmar-brauch/cleanCodeDojo/blob/master/src/test/kotlin/de/bsi/kata/tdd/bowling/GameTest.kt&quot;&gt;https://github.com/elmar-brauch/cleanCodeDojo/.../GameTest.kt&lt;/a&gt;&lt;br /&gt;Meine Commits entsprechen nicht den Zyklen, sondern den Wechseln beim Bowling Game Kata, wenn es im Wasa von 2 Entwickler implementiert wird 😉&lt;/div&gt;&lt;div&gt;Falls ihr mit den Kampfsport-Begriffen nichts anfangen könnt:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/06/clean-code-dojo.html&quot;&gt;clean-code-dojo.html&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Auswirkungen von KI auf testgetriebene Entwicklung&lt;/h2&gt;&lt;div&gt;Künstliche Intelligenz verändert zunehmend unsere tägliche Arbeit als EntwicklerInnen – auch beim testgetriebenen Entwickeln. Tools wie GitHub Copilot, ChatGPT oder Windsurf schreiben nicht nur Code, sondern auch Testfälle. Doch was bedeutet das für TDD?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;KI als Testgenerator&lt;/h3&gt;&lt;div&gt;KI kann beim Schreiben von Unit Tests unterstützen, indem sie Vorschläge basierend auf bestehenden Code-Strukturen generiert. Dennoch bleibt es essenziell, Testfälle vor dem produktiven Code zu formulieren – wie es TDD verlangt. Die Überlegung &quot;Was soll mein Code eigentlich tun?&quot; lässt sich nicht an eine KI delegieren. Die Qualität der Tests hängt weiterhin davon ab, wie gut wir das gewünschte Verhalten verstehen und formulieren.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Unterstützung in Phase GRÜN&lt;/h3&gt;&lt;div&gt;Wenn es darum geht, die minimale Implementierung zu schreiben, kann eine KI durchaus passende Code-Vorschläge machen. Gerade bei einfachen Funktionen spart das Zeit. Aber Achtung: Wer testgetrieben entwickelt, möchte ganz bewusst nicht sofort die komplette Lösung sehen. Die Herausforderung liegt im disziplinierten Vorgehen – und hier kann ein KI-Codevorschlag vom TDD-Weg ablenken.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Hilfe beim Refactoring&lt;/h3&gt;&lt;div&gt;In der optionalen BLAU-Phase kann KI nützlich sein: Sie erkennt doppelte Logik, schlägt sauberere Strukturen vor und hilft beim Umbau von Code. Auch hier gilt: Nutze KI als Assistent, nicht als Architekt. Letztlich entscheidet der Mensch, was „clean“ ist – und was nicht.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;KI-Driven Development als Anti-Pattern?&lt;/h3&gt;&lt;div&gt;Ein wachsender Trend ist es, zuerst Code durch KI generieren zu lassen und anschließend Tests hinzuzufügen – also genau umgekehrt wie bei TDD. Das führt oft zu Tests, die nur bestätigen, was ohnehin schon programmiert wurde. Die Gefahr: Man verliert den prüfenden, hinterfragenden Blick, der TDD so wertvoll macht.&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;&lt;div&gt;Wenn ihr bisher TDD nicht verwendet habt, hoffe ich, dass euch dieser Artikel zeigt, wie man testgetriebene Entwicklung ganz konkret umsetzen kann – mit kleinen Schritten, klaren Zyklen und einem bewussten Fokus auf das Warum hinter dem Code. Ob mit oder ohne KI: TDD hilft uns, strukturiert zu denken, Fehlverhalten früh zu erkennen und robuste Software zu schreiben.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Künstliche Intelligenz kann dabei eine wertvolle Unterstützung sein – beim Schreiben von Tests, bei der minimalen Implementierung oder beim Refactoring. Aber sie ersetzt keine saubere Denkarbeit. TDD bleibt ein Werkzeug für uns Menschen, um Kontrolle über unsere Software zu behalten – gerade weil KI unsere Entwicklung beschleunigt und automatisiert. Erst testen, dann coden – das ist und bleibt der Kern.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Probiert es doch einfach mal aus – am besten mit einem Kata wie dem Bowling Game. Denn genau dafür wurden sie gemacht: Um gute Entwicklungspraktiken zu trainieren. Schritt für Schritt. Test für Test.&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2021/06/tdd.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirxUGO0_1R9RcqVvSYqrhLgRfGvRc8WzOVsb-HJWxO5JyAFJIpPqGB1YCvwYamkdVBxmjxv6zzcw_hwBjisAPpbpaHziobNthNMdoONGZFh5jntT4_2t7DoKia_uA3LutJ5avE5lMhBjEz/s72-w400-h325-c/TDD-Kreis.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>27.026130441578204 -26.505057100000002 72.7195201584218 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-9104870884525441128</guid><pubDate>Sun, 16 Feb 2025 10:35:00 +0000</pubDate><atom:updated>2025-03-25T13:49:31.991+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">Security</category><category domain="http://www.blogger.com/atom/ns#">Spring-Basics</category><title>Spring Boot vs. Security Vulnerabilities</title><description>&lt;p data-pm-slice=&quot;1 1 []&quot;&gt;&lt;b&gt;Der Security-Scanner in eurer CI/CD-Pipeline hat eine kritische Security Vulnerability gefunden. In einer 3rd-Party-Bibliothek gibt es eine Schwachstelle. &lt;br /&gt;Der Scanner kennt bereits die Lösung: ein Upgrade der Bibliothek. &lt;br /&gt;Doch ist das wirklich immer die beste Idee? &lt;br /&gt;Oder gibt es Aspekte wie Wartbarkeit und Stabilität zu beachten?&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Security Scanner und Vulnerabilities&lt;/h2&gt;&lt;p&gt;CI/CD-Pipelines mit Code-Scannern sind essenzielle Werkzeuge in der professionellen Softwareentwicklung. In meinem Projekt nutzen wir eine GitLab CI/CD-Pipeline mit diversen Scannern. Einer davon analysiert unsere Third-Party-Bibliotheken auf Sicherheitslücken, also veraltete Abhängigkeiten mit bekannten Schwachstellen. Diese werden in einem Report aufgelistet. Für jede Schwachstelle sind detaillierte Informationen abrufbar.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Hier ein Beispiel aus einem Java-Projekt mit Spring Boot 3.4.2.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSomTCLa9T_8V7rRhV6OdjnX3FZ8-4r3W445ssPZMlRwInMXaM6AuzTNQ2vTfp5GjfTqZ7xYEgqi4VFhZHKjuelktc_azHT-My_NFPVMiL1wuceTy47BPxckXO1Ao1I_5TT0uEWX6zdrbwI9s4VrrrQskQa1tU3aZWfjvK8omWEVCGo2JAFShHZpO8Ij_Y/s1875/GitLab_Security_Vulnerability.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1632&quot; data-original-width=&quot;1875&quot; height=&quot;557&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSomTCLa9T_8V7rRhV6OdjnX3FZ8-4r3W445ssPZMlRwInMXaM6AuzTNQ2vTfp5GjfTqZ7xYEgqi4VFhZHKjuelktc_azHT-My_NFPVMiL1wuceTy47BPxckXO1Ao1I_5TT0uEWX6zdrbwI9s4VrrrQskQa1tU3aZWfjvK8omWEVCGo2JAFShHZpO8Ij_Y/w640-h557/GitLab_Security_Vulnerability.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Titel und Beschreibung&lt;/b&gt; der Vulerability bzw. des Security Problems.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Severity:&lt;/strong&gt; Kategorisiert die Kritikalität in fünf Stufen von &quot;Info&quot; bis &quot;Kritisch&quot; (im Beispiel &quot;Hoch&quot;).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Location:&lt;/strong&gt; Zeigt die betroffene Stelle, hier das Dependency-Management in der &lt;code&gt;pom.xml&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Evidence &amp;amp; Solution:&lt;/strong&gt; Nennen die betroffene Bibliothek und die gefixte Version.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/PscxViX_SHU&quot; width=&quot;320&quot; youtube-src-id=&quot;PscxViX_SHU&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Video about this article in English&lt;/i&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Security Vulnerability gefunden - was nun?&lt;/h2&gt;&lt;p&gt;Ist eine Sicherheitslücke gefunden, scheint die Lösung klar: &lt;br /&gt;Upgrade der betroffenen Bibliothek auf die gefixte Version. &lt;br /&gt;Doch ist das immer der beste Weg?&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Wann ein direktes Upgrade notwendig ist&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Ja - bei extrem kritischen Sicherheitslücken! Diese sollten so schnell wie möglich gefixt werden, da eine unmittelbare Bedrohung besteht (z. B. Remote Code Execution oder bekannte Exploits in der Wildnis).&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Wann ein direktes Upgrade problematisch sein kann&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;In allen anderen Fällen müssen wir die Konsequenzen solcher Upgrades abwägen:&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;1. Auswirkungen auf die Stabilität&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Unsere Software wurde mit den bestehenden 3rd-Party-Bibliotheken getestet. Wird eine Version geändert, können unerwartete Probleme auftreten. Deshalb sind nach einem Upgrade erneute, idealerweise automatisierte Tests notwendig, um Stabilitätsrisiken und neue Bugs auszuschließen.&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;2. Herausforderungen für die Wartbarkeit&lt;/strong&gt;&lt;/h4&gt;&lt;p&gt;Die meisten professionellen Software-Systeme setzen auf ein Framework wie Spring Boot, weil es das Dependeny- und Versions-Management übernimmt. Wird eine Dependency manuell aktualisiert, kann das zu Inkonsistenzen führen. Konkret stellt sich die Fragen:&lt;/p&gt;&lt;ul data-spread=&quot;false&quot; style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Kann der Security-Fix später entfernt werden, wenn Spring Boot das Problem selbst löst?&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Ist die neue Spring-Boot-Version kompatibel mit der gefixten Bibliothek?&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Jede manuelle Änderung erhöht den Wartungsaufwand.&lt;/p&gt;&lt;h3&gt;Konkretes Beispiel: Fix einer betroffenen Bibliothek&lt;/h3&gt;&lt;div&gt;&lt;div&gt;Zurück zur konkreten Security Vulnerability aus dem vorherigen Screenshot. Die Bibliothek&amp;nbsp;&lt;code&gt;json-smart&lt;/code&gt;&amp;nbsp;ist betroffen und benötigt ein Upgrade. Mit IntelliJ analysiere ich die Maven-Konfiguration, um herauszufinden, dass&amp;nbsp;&lt;span style=&quot;font-family: monospace;&quot;&gt;json-smart&lt;/span&gt;&amp;nbsp;von spring-boot-startet-test benutzt wird.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP13xDdmOi5h20eCMyCkTIhYfidHA3o-kdo6OONvO1SXSdF1D5YNpc6zu6B_yo-4IVfYiNlYgjGuU_TpRCyA-GAMnwFROqYHp59lNkA8bkfbAPklHGck7_bzpX-hKhUirc5teWsCfH3lzp6CporEHhRxtIQzXaQEJozhtZ3vxlPwyCGOoovOU7K5jvrg8M/s742/Security_Patch.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;439&quot; data-original-width=&quot;742&quot; height=&quot;378&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP13xDdmOi5h20eCMyCkTIhYfidHA3o-kdo6OONvO1SXSdF1D5YNpc6zu6B_yo-4IVfYiNlYgjGuU_TpRCyA-GAMnwFROqYHp59lNkA8bkfbAPklHGck7_bzpX-hKhUirc5teWsCfH3lzp6CporEHhRxtIQzXaQEJozhtZ3vxlPwyCGOoovOU7K5jvrg8M/w640-h378/Security_Patch.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Der Fix der Dependency erfolgt in 2 Schritten:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Die betroffene Bibliothek&amp;nbsp;&lt;span style=&quot;font-family: monospace;&quot;&gt;json-smart&lt;/span&gt;&amp;nbsp;wird per &quot;exclusion&quot; aus der Bibliothek&amp;nbsp;&lt;span style=&quot;font-family: monospace;&quot;&gt;spring-boot-starter-test&lt;/span&gt;&amp;nbsp;entfernt.&lt;/li&gt;&lt;li&gt;Die Upgrade-Version der Bibliothek wird mit Versionsangabe &quot;2.5.2&quot; extra in die Liste der Dependencies aufgenommen.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;Konkret sieht es so in der &lt;code&gt;pom.xml&lt;/code&gt;&amp;nbsp;aus:&lt;/div&gt;&lt;/div&gt;&lt;pre&gt;&lt;div style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;    &lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;&amp;lt;!-- other dependencies --&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;    &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-starter-test&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;test&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;exclusions&lt;/span&gt;&amp;gt;&lt;br /&gt;          &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;exclusion&lt;/span&gt;&amp;gt;&lt;br /&gt;             &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;json-smart&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;             &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;net.minidev&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;          &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;exclusion&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;exclusions&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;net.minidev&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;json-smart&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;2.5.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;test&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;Ohne Security-Fix würden wir uns das Management von&amp;nbsp;&lt;span style=&quot;font-family: monospace;&quot;&gt;json-smart&lt;/span&gt;&amp;nbsp;sparen, dann macht Spring Boot das.&lt;/div&gt;&lt;div style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;&lt;pre style=&quot;color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;    &lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;&amp;lt;!-- other dependencies --&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;    &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-starter-test&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;test&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;pre style=&quot;color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Praktischer Umgang mit Dependency-Vulnerabilities&lt;/h2&gt;&lt;p&gt;Es gibt drei mögliche Strategien im Umgang mit Sicherheitslücken in Abhängigkeiten:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Direkter Fix&lt;/b&gt; (Upgrade der betroffenen Bibliothek)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Vorteile: Schnell umsetzbar&lt;/li&gt;&lt;li&gt;Nachteile: Erhöhter Wartungsaufwand, mögliche Stabilitätsprobleme&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Indirekter Fix&lt;/b&gt; (Upgrade des Frameworks, z. B. Spring Boot)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Vorteile: Nachhaltiger, besser für Wartbarkeit&lt;/li&gt;&lt;li&gt;Nachteile: Weitere indirekte Updates, die getestet werden müssen, &lt;br /&gt;nicht immer sofort verfügbar&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Ignorieren&lt;/b&gt; (wenn nicht relevant)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Vorteile: Kein zusätzlicher Aufwand&lt;/li&gt;&lt;li&gt;Nachteil: Risiko, wenn Schwachstelle doch kritisch ist&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3&gt;Wann ignorieren eine sinnvolle Option ist&lt;/h3&gt;&lt;p&gt;Nicht jede gemeldete Schwachstelle ist relevant. Security Scanner haben oft False Positives. Ein typisches Beispiel ist eine Sicherheitslücke in einer Test-Dependency.&lt;/p&gt;&lt;p&gt;Im obigen Beispiel betrifft &lt;code&gt;json-smart&lt;/code&gt; nur &lt;code&gt;spring-boot-starter-test&lt;/code&gt;, das ausschließlich in automatisierten Tests verwendet wird (siehe &lt;code&gt;scope=&quot;test&quot;&lt;/code&gt;). Da es nicht produktiv genutzt wird, kann man den Fix ignorieren, insbesondere wenn die aktuellste Version von Spring Boot genutzt wird und kein indirektes Update verfügbar ist.&lt;/p&gt;&lt;h3&gt;Indirekter Fix durch Upgrade des Frameworks&lt;/h3&gt;&lt;p data-pm-slice=&quot;1 1 []&quot;&gt;Ein indirekter Fix bedeutet, dass wir die Version unseres Frameworks (z. B. Spring Boot) aktualisieren. Dadurch erhalten wir nicht nur den Security-Fix, sondern modernisieren gleichzeitig unser System. Dabei werden auch weitere Bibliotheken aktualisiert, was zusätzliche Änderungen im System mit sich bringt. Dies kann zu unerwarteten Nebenwirkungen führen, weshalb Regressionstests notwendig sind. Dennoch ist dies die beste langfristige Lösung für die Wartbarkeit, da sie das System aktuell hält und zukünftige Sicherheitsrisiken reduziert.&lt;/p&gt;&lt;p&gt;Gerade in großen Projekten mit vielen Schwachstellen führen direkte Fixes zu einer Verschlechterung der Wartbarkeit. Daher sollte bevorzugt geprüft werden, ob eine Aktualisierung des Frameworks möglich ist.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;p&gt;Security-Schwachstellen sollten ernst genommen werden, aber nicht blind gefixt werden. &lt;br /&gt;Die richtige Strategie hängt vom konkreten Fall ab:&lt;/p&gt;&lt;p&gt;✅ &lt;strong&gt;Extrem kritische Lücke?&lt;/strong&gt; → Direktes Upgrade notwendig
&lt;br /&gt;✅ &lt;strong&gt;Weniger kritische Lücke?&lt;/strong&gt; → Indirektes Upgrade oder warten auf indirektes Upgrade&lt;br /&gt;✅ &lt;strong&gt;Nicht produktiver Code betroffen?&lt;/strong&gt; → Ignorieren oder indirektes Upgrade&lt;/p&gt;&lt;p&gt;Im Idealfall fixt ihr Sicherheitslücken, indem ihr euer Framework aktualisiert. &lt;br /&gt;Das sorgt nicht nur für Sicherheit, sondern hält euer Software-System langfristig wartbar und stabil.&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2025/02/security-vulnerabilities.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSomTCLa9T_8V7rRhV6OdjnX3FZ8-4r3W445ssPZMlRwInMXaM6AuzTNQ2vTfp5GjfTqZ7xYEgqi4VFhZHKjuelktc_azHT-My_NFPVMiL1wuceTy47BPxckXO1Ao1I_5TT0uEWX6zdrbwI9s4VrrrQskQa1tU3aZWfjvK8omWEVCGo2JAFShHZpO8Ij_Y/s72-w640-h557-c/GitLab_Security_Vulnerability.png" height="72" width="72"/><georss:featurename>64 Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-1494596163717416521</guid><pubDate>Mon, 26 Aug 2024 05:27:00 +0000</pubDate><atom:updated>2024-08-26T07:27:28.940+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">AWS</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>RAG Chatbot mit Spring AI, AWS Bedrock und Vektor Datenbank</title><description>&lt;p&gt;&lt;b&gt;Wie bringt man einem KI Chatbot Unternehmens-Knowhow bei?&amp;nbsp;Retrieval Augmented Generation (RAG) ist eine Lösung, die ich in diesem Artikel mit Amazon Bedrock, der Vektor Datenbank pgvector und Spring AI umsetze.&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;RAG Architektur-Überblick&lt;/b&gt;&lt;/h2&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; font-weight: bold; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgVgosrfuWCaQ7JlpvfQ6w9vz3kbR65j5Z_6zoaZSBrTMYGvpK5w05qoc05k-rEhHDOe0TZGidHixMTs312Ql2V2EZgCUM04eXz3t5jQXZDAfA8b5CBemGIfoZe4NNME3Sb2liyYeNQYAJzXnEgbVTyIhXvjodwnpodKoE-wweIwb8eBYtMrtEaeR2zP9Y7&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;636&quot; data-original-width=&quot;941&quot; height=&quot;433&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgVgosrfuWCaQ7JlpvfQ6w9vz3kbR65j5Z_6zoaZSBrTMYGvpK5w05qoc05k-rEhHDOe0TZGidHixMTs312Ql2V2EZgCUM04eXz3t5jQXZDAfA8b5CBemGIfoZe4NNME3Sb2liyYeNQYAJzXnEgbVTyIhXvjodwnpodKoE-wweIwb8eBYtMrtEaeR2zP9Y7=w640-h433&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Der Benutzer chattet mit dem Chatbot unserer Anwendung (hier &quot;RAG Chat Service&quot;). Diesen Teil implementieren wir selbst mit Java und Spring AI. Als LLM bzw. Chat-Service verwendet unsere Spring AI Anwendung den AWS Dienst Bedrock mit dem Sprachmodell Titan (eine Alternative zu GPT von OpenAI). Ohne eigene Dokumente in einer Vektor Datenbank, hätten wir so einen Chatbot ohne Unternehmens-spezifisches Wissen - vergleichbar mit ChatGPT. So etwas habe ich schon mit GPT gebaut, siehe dazu&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2023/01/gpt-3.html&quot;&gt;gpt-3 in deiner Java Webanwendung&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unsere Anwendung sucht in der Vektor Datenbank nach zur Chat-Anfrage des Benutzers passenden Dokumenten. Mit diesen Unternehmens-spezifische Texten reichert unsere Anwendung die Interaktion mit dem Bedrock Titan Chat Service an. Dadurch bekommt der Benutzer eine Chat-Antwort passend zum Kontext des Unternehmens.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Um die Datenbank mit vektorisierten Dokumenten zu füllen, verwendet der &quot;Document Importer&quot; unserer Anwendung den AWS Bedrock Embedding Service des Sprachmodells Titan. Die Dokumente bzw. Texte werden aus Quellen des Unternehmens eingelesen, mit Hilfe des AWS Embedding Services vektorisiert und dann in der Vektor-DB gespeichert.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/lsNly5nI2rY&quot; width=&quot;320&quot; youtube-src-id=&quot;lsNly5nI2rY&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Video about Spring AI and Azure&lt;/i&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Dokumente in pgvector importieren&lt;/h2&gt;&lt;div&gt;Als Vektor-Datenbank verwende ich PostgreSQL mit der Erweiterung pgvector. Diese Datenbank wird von Spring AI unterstützt und lässt sich im Docker Container starten:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;docker run -d --name vector_db --restart always -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=vector_store -e PGPASSWORD=postgres --log-opt max-size=10m --log-opt max-file=3 -p 5433:5432 ankane/pgvector:v0.5.1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Die Maven Konfiguration für den Document Importer und den Chatbot im nächsten Abschnitt sieht so aus:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.ai&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-ai-bedrock-ai-spring-boot-starter&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.ai&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-ai-pgvector-store-spring-boot-starter&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.postgresql&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;postgresql&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;runtime&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;...&lt;/span&gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencyManagement&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.ai&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-ai-bom&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.8.1&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;type&lt;/span&gt;&amp;gt;pom&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;type&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;import&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencyManagement&lt;/span&gt;&amp;gt;
&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;repositories&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;repository&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;spring-milestones&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;Spring Milestones&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;url&lt;/span&gt;&amp;gt;https://repo.spring.io/milestone&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;url&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;snapshots&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;enabled&lt;/span&gt;&amp;gt;false&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;enabled&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;snapshots&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;repository&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;repositories&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Da Spring AI noch keine 1.0 Version hat, finden wir es noch nicht im zentralen Maven-Repository sondern im Spring Milestones Repository.&lt;/li&gt;&lt;li&gt;Für den Chatbot und die Vektorisierung benötigen wir die &quot;spring-ai-bedrock-ai-spring-boot-starter&quot; Bibliothek.&lt;/li&gt;&lt;li&gt;Für die Vektor-Datenbank die andere Spring AI und PostgreSQL Dependencies.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Damit bauen wir einen einfachen Importer:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;@Component&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;DocumentImport &lt;/span&gt;{&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;private final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;VectorStore &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;vectorStore&lt;/span&gt;;&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;private final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;TokenTextSplitter &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;textSplitter &lt;/span&gt;= &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;TokenTextSplitter();&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;private static final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;EXAMPLE_DOCUMENT &lt;/span&gt;= &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Long text with company specific content.&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;&lt;/span&gt;;&lt;br /&gt;&lt;/pre&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;...&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;importDocuments&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;vectorizedDocumentChunks &lt;/span&gt;= &lt;span style=&quot;color: #871094;&quot;&gt;textSplitter&lt;/span&gt;.split(&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;EXAMPLE_DOCUMENT&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;800&lt;/span&gt;)&lt;br /&gt;                .stream()&lt;br /&gt;                .map(chunk -&amp;gt; &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;Document(chunk, &lt;span style=&quot;color: black;&quot;&gt;Map&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Add&quot;&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;meta&quot;&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;data&quot;&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;here&quot;&lt;/span&gt;)))&lt;br /&gt;                .toList();&lt;br /&gt;        &lt;span style=&quot;color: #871094;&quot;&gt;vectorStore&lt;/span&gt;.accept(&lt;span style=&quot;color: black;&quot;&gt;vectorizedDocumentChunks&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die &lt;span style=&quot;font-family: courier;&quot;&gt;VectorStore&amp;nbsp;&lt;/span&gt;Bean stellt Spring AI automatisch zur Verfügung.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;TokenTextSplitter&lt;/span&gt; ist eine Klasse aus den Spring AI Bibliotheken zum Aufsplitten langer Texte. &lt;span style=&quot;font-family: courier;&quot;&gt;800&lt;/span&gt; definiert die Größe der aufgeteilten Textblöcke. Den Wert &lt;span style=&quot;font-family: courier;&quot;&gt;800&lt;/span&gt;&amp;nbsp;habe ich in einer Spring-Klasse als dortigen default abgeschaut.&lt;/li&gt;&lt;li&gt;Die aufgeteilten Textblöcke wandle ich in Spring AI &lt;span style=&quot;font-family: courier;&quot;&gt;Document&lt;/span&gt; Objekte um. Die &lt;span style=&quot;font-family: courier;&quot;&gt;Document&lt;/span&gt; Objekte könnt ihr mit Metadaten in Form einer &lt;span style=&quot;font-family: courier;&quot;&gt;Map&lt;/span&gt; anreichern.&lt;/li&gt;&lt;li&gt;Der letzte Schritt&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;vectorStore.accept&lt;/span&gt;&amp;nbsp;schreibt eine Liste aus allen &lt;span style=&quot;font-family: courier;&quot;&gt;Document&lt;/span&gt; Objekten des ursprünglichen, Firmen-spezifischen&amp;nbsp;Beispiel-Dokuments in die Vektor-DB. Im &lt;span style=&quot;font-family: courier;&quot;&gt;VectorStore&lt;/span&gt; ist eine Spring AI Bean&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;EmbeddingClient&lt;/span&gt;&amp;nbsp;injiziert, welche die Vektorisierung der &lt;span style=&quot;font-family: courier;&quot;&gt;Document&lt;/span&gt; Objekte vornimmt.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Damit Spring AI automatisch alle benötigten Beans bereitstellt, konfiguriert ihr in &lt;i&gt;application.porperties&lt;/i&gt;:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;background-color: white; color: #083080; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;br /&gt;spring.ai.bedrock.aws.region&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background-color: white; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;eu-central-1&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #083080; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;spring.ai.bedrock.aws.access-key&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background-color: white; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;TODO_set_me&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #083080; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;spring.ai.bedrock.aws.secret-key&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #067d17; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;TODO_set_me&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.ai.bedrock.titan.chat.enabled&lt;/span&gt;=&lt;span style=&quot;color: #0033b3;&quot;&gt;true&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.ai.bedrock.titan.embedding.enabled&lt;/span&gt;=&lt;span style=&quot;color: #0033b3;&quot;&gt;true&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.ai.bedrock.titan.embedding.input-type&lt;/span&gt;=&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;text&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.ai.bedrock.titan.embedding.model&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;amazon.titan-embed-text-v1&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.datasource.password&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;postgres&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.datasource.username&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;postgres&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;spring.datasource.url&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;jdbc:postgresql://localhost:5433/vector_store&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.ai.bedrock.aws&lt;/span&gt; definiert die Verbindung zum AWS Bedrock Service sowohl für den Embedding Service zur Dokumenten-Vektorisierung als auch für den Chat Service.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.ai.bedrock.titan&lt;/span&gt; aktiviert und konfiguriert die beiden AWS Bedrock Dienste. Hier sind noch weitere Feineinstellungen der KI möglich (maxtoken, temprature etc.).&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.datasource&lt;/span&gt; beschreibt die Verbindung zur Datenbank.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;RAG Chatbot mit Vektor-DB&lt;/h2&gt;&lt;div&gt;Einen Chat mit Spring AI und AWS Bedrock implementieren wir so:&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;private final &lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;BedrockTitanChatClient &lt;/span&gt;&lt;span style=&quot;background-color: white; color: #871094;&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #8c8c8c; font-style: italic;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;public void &lt;/span&gt;&lt;span style=&quot;background-color: white; color: #00627a;&quot;&gt;chat&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;(String message) {&lt;br /&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;userMessage &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;= &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;UserMessage(&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;);&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #8c8c8c;&quot;&gt;&lt;span style=&quot;background-color: #fcf4d4;&quot;&gt;&lt;i&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;// TODO Integrate VectorStore
&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;prompt &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;= &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;Prompt(&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-style: italic;&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: black;&quot;&gt;userMessage&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;background-color: white;&quot;&gt;aiResponse &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;= &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #871094;&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;.call(&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white;&quot;&gt;prompt&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;.info(&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #067d17;&quot;&gt;&quot;AI response: {}&quot;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white;&quot;&gt;aiResponse&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;.getResult().getOutput().getContent());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;background-color: white;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die Chat &lt;span style=&quot;font-family: courier;&quot;&gt;message&lt;/span&gt; des Benutzers wandeln wir in ein Objekt der Spring AI Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;UserMessage&lt;/span&gt; um.&lt;/li&gt;&lt;li&gt;Das&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;UserMessage&lt;/span&gt;&amp;nbsp;Objekt überführen wir in ein Spring AI &lt;span style=&quot;font-family: courier;&quot;&gt;Prompt&lt;/span&gt; Objekt. (In das &lt;span style=&quot;font-family: courier;&quot;&gt;Prompt&lt;/span&gt; Objekt integrieren wir in einem späteren Schritt noch die Daten aus der Vektor-DB.)&lt;/li&gt;&lt;li&gt;Das &lt;span style=&quot;font-family: courier;&quot;&gt;Prompt&lt;/span&gt; Objekt schicken wir an den AWS Bedrock Chat Service. Dazu verwenden wir eine Bean, die von Spring AI automatisch erzeugt wurde:&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;BedrockTitanChatClient&lt;/span&gt;. Die Antwort der KI logge ich in die Konsole - es ist eine kleine Demo-Anwendung.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Um aus diesem einfachen Chat-Service ein RAG zu machen, binden wir noch die Vektor-Datenbank mit unserem Unternehmens Know-How ein. Dazu ergänzen wir den zuvor gezeigten Code:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;VectorStore &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;vectorStore&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private static final &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;SystemPromptTemplate &lt;/span&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;template &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;SystemPromptTemplate(&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;&quot;&quot;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        Du assistierst bei Fragen zum UseCase X.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        Verwende die Informationen aus dem Abschnitt DOKUMENTE, um genaue Antworten zu geben,&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        aber tu so, als ob du diese Informationen von Natur aus wüsstest.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        Wenn du dir nicht sicher bist, gib einfach an, dass du es nicht weißt.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        DOKUMENTE:&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        {documents}&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;);&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;public void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;chat&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;color: black;&quot;&gt;(String message) {&lt;/span&gt;
&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;...
    &lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;similarDocuments &lt;span style=&quot;color: #080808;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;vectorStore&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;.similaritySearch(&lt;/span&gt;message&lt;span style=&quot;color: #080808;&quot;&gt;)
&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;        .stream()
&lt;span style=&quot;color: #080808;&quot;&gt;            .map(&lt;/span&gt;Document&lt;span style=&quot;color: #080808;&quot;&gt;::getContent)
&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;            .collect(&lt;/span&gt;Collectors&lt;span style=&quot;color: #080808;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #080808; font-style: italic;&quot;&gt;joining&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;(&lt;/span&gt;System&lt;span style=&quot;color: #080808;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #080808; font-style: italic;&quot;&gt;lineSeparator&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;()));&lt;/span&gt;&lt;/pre&gt;&lt;span style=&quot;color: #080808;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;contextMessage &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;.createMessage(&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #080808; font-style: italic;&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;documents&quot;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;similarDocuments&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;));&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;prompt &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;Prompt(&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #080808; font-style: italic;&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;contextMessage&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;userMessage&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;));&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #080808;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;...
}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Mit der &lt;span style=&quot;font-family: courier;&quot;&gt;VectorStore&lt;/span&gt; Bean suchen wir nach ähnlichen Dokumenten in der Vektor-Datenbank:&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;vectorStore.similaritySearch&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die so gefundenen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Document&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Objekte wandeln wir in einen langen String um. Das passiert in den Stream-Operationen direkt nach der Ähnlichkeits-Suche. &lt;a href=&quot;https://agile-coding.blogspot.com/2021/07/streams.html&quot; target=&quot;_blank&quot;&gt;Mehr zu Streams gibt es hier&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Neben der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UserMessage&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; stecken wir dieses mal ein 2. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Message&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;Objekt (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;contextMessage&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;) als Kontext in die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Prompt&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Instanz. Diese 2. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Message&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; bauen wir mit dem Spring AI&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SystemPromptTemplate&lt;/span&gt;. Das Template beschreibt das Verhalten unserer KI und auf welche Dokumente mit Firmen-spezifischen Wissen die KI zugreift. Auf diese Weise ist im AWS Bedrock Chat Service kein Firmen-spezifisches Wissen dauerhaft persistiert. Den Unternehmenskontext übertragen wir mit jeder Chat Anfrage passend zur Nachricht des Benutzers.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Durch AWS Bedrock habe ich einfachen Zugriff auf KI Sprachmodelle. Eine einfache Vektor Datenbank lässt sich schnell mit Docker starten. Mit Spring AI benutzen wir diese Dienste leicht und verbinden sie so zu einem RAG.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Wenn euch die Qualität des Amazon Titan Sprachmodells nicht überzeugt, testet andere Sprachmodelle wie Mistral AI oder Llama in&amp;nbsp;&lt;a href=&quot;https://aws.amazon.com/de/bedrock/&quot;&gt;AWS Bedrock&lt;/a&gt;. Dank Spring AI braucht ihr nur die Maven-Dependencies und die &lt;i&gt;application.properties&lt;/i&gt; anzupassen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den von mir gezeigten Code, findet ihr bei GitHub: &lt;a href=&quot;https://github.com/elmar-brauch/aws-bedrock-rag&quot;&gt;https://github.com/elmar-brauch/aws-bedrock-rag&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/RMBXTMoiXPI&quot; width=&quot;320&quot; youtube-src-id=&quot;RMBXTMoiXPI&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;i&gt;OpenAI Chatbot&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2024/05/spring-ai-rag.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgVgosrfuWCaQ7JlpvfQ6w9vz3kbR65j5Z_6zoaZSBrTMYGvpK5w05qoc05k-rEhHDOe0TZGidHixMTs312Ql2V2EZgCUM04eXz3t5jQXZDAfA8b5CBemGIfoZe4NNME3Sb2liyYeNQYAJzXnEgbVTyIhXvjodwnpodKoE-wweIwb8eBYtMrtEaeR2zP9Y7=s72-w640-h433-c" height="72" width="72"/><thr:total>2</thr:total><georss:featurename>64 Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-6146286480659507792</guid><pubDate>Thu, 25 Jul 2024 06:00:00 +0000</pubDate><atom:updated>2024-07-25T08:00:00.112+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">Java</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>AI Assistent mit LangChain4j und Spring Boot</title><description>&lt;p&gt;&lt;b&gt;LangChain4j ist ein neues und populäres Framework, um LLMs, Vektor-Datenbanken und weitere KI-Funktionen in Java Systemen zu nutzen. In diesem Artikel integrieren wir LangChain4j in eine Spring Boot Anwendung.&lt;/b&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQboPlaqR61uDeWKHVVUmLl4-Vkj6aeUjdKI6OsYrYPOqBxWdvIrZfOF_6DKO-x2ql-GUqO4BmjDXHCUUPgrAw7sqL_-kEOtdPFxZEUV57Zs1aVFXbbBDJmUov2eWD5BWYJR1NoUwtja5ZbTD8BtWlEvSmiCMHaNzaqzNhsOPmGPmZN1H0JK3FtpZuHc5r/s924/langchain4j.PNG&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;801&quot; data-original-width=&quot;924&quot; height=&quot;277&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQboPlaqR61uDeWKHVVUmLl4-Vkj6aeUjdKI6OsYrYPOqBxWdvIrZfOF_6DKO-x2ql-GUqO4BmjDXHCUUPgrAw7sqL_-kEOtdPFxZEUV57Zs1aVFXbbBDJmUov2eWD5BWYJR1NoUwtja5ZbTD8BtWlEvSmiCMHaNzaqzNhsOPmGPmZN1H0JK3FtpZuHc5r/s320/langchain4j.PNG&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Spring Boot mit LangChain4j&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven Dependencies&lt;/h3&gt;&lt;div&gt;Stand 22.07.2024 hat LangChain4j noch eine Nuller-Version im Maven Repository. LangChain4j integrieren wir als 3rd-Party Bibliothek in unser Spring Boot Projekt:&lt;/div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-starter-web&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;dev.langchain4j&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;langchain4j-spring-boot-starter&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.32.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;dev.langchain4j&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;langchain4j-azure-open-ai-spring-boot-starter&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;       &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.32.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;span style=&quot;background-color: transparent;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;langchain4j-spring-boot-starter&lt;/span&gt; ist keine von Spring gewartete Spring Boot Starter Dependency. Die Spring Boot Starter Konfiguration wird vom LangChain4j Opensource Projekt entwickelt. Deshalb muss die Versionsnummer angegeben werden, hier 0.32.0.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Ich verwende ein OpenAI LLM in Azure. Daher benötige ich neben der Basis Bibliothek &lt;span style=&quot;font-family: courier;&quot;&gt;langchain4j-spring-boot-starter&lt;/span&gt; noch diese Azure-LLM Integrations-Bibliothek&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;langchain4j-azure-open-ai-spring-boot-starter&lt;/span&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Konfiguration in application.properties&lt;/h3&gt;&lt;div&gt;Die beiden Spring Boot Starter Dependencies bringen eine Auto-Konfiguration mit. Diese passen wir für den Zugriff auf unseren OpenAI LLM Azure-Service in den &lt;i&gt;appliation.properties&lt;/i&gt; wie folgt an:&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;div style=&quot;background-color: white;&quot;&gt;&lt;pre style=&quot;color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #083080;&quot;&gt;langchain4j.azure-open-ai.chat-model.api-key&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;geheim&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;langchain4j.azure-open-ai.chat-model.endpoint&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;https://azure.url&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;langchain4j.azure-open-ai.chat-model.deployment-name&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;gpt-4-turbo-preview&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;langchain4j.azure-open-ai.chat-model.log-requests-and-responses&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;logging.level.dev.langchain4j&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;DEBUG&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #083080;&quot;&gt;logging.level.dev.ai4j.openai4j&lt;/span&gt;=&lt;span style=&quot;color: #067d17;&quot;&gt;DEBUG&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...endpoint&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...deployment-name&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; definieren wo unser LLM Service gefunden wird. Anhand des vollen Property-keys &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;langchain4j.azure-open-ai&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&quot; steht fest, dass es die Azure Cloud ist.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;Der API-Key in &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...api-key&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; authentifiziert und autorisiert meine Spring Boot Anwendung für den Zugriff auf den Azure LLM Service.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Alle weiteren Properties sind optional. Hier sehen wir noch Einstellung für das Debug-Logging zur Interaktion mit dem LLM.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;Chatbot Bean mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@AiService&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@SystemMessage&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;/pre&gt;&lt;pre style=&quot;color: #080808;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Einfache Chatbots definieren wir mit LangChain4j per Interface und Annotation.
Der Spring Boot Starter in der LangChain4j Bibliothek erzeugt daraus eine Chatbot Bean
- die Implementierung des Interfaces stellt LangChain4j bereit.&lt;/span&gt;&lt;/pre&gt;&lt;pre style=&quot;color: #080808;&quot;&gt;&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;dev.langchain4j.service.&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;SystemMessage&lt;/span&gt;;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;dev.langchain4j.service.spring.&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;AiService&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;@AiService&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;interface &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Assistant &lt;/span&gt;{&lt;br /&gt;    &lt;span style=&quot;color: #9e880d;&quot;&gt;@SystemMessage&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Du bist ein freundlicher Assistent und antwortest in Deutsch&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;.&quot;&lt;/span&gt;)&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;chat&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;userMessage);    &lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;pre&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@AiService&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; stellt die Implementierung des Interfaces und eine Assistant Bean bereit.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@SystemMessage&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; definiert den 1. System-Prompt des Chatbots.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Die Assistant Bean nutzen wir in unserer Spring Anwendung analog zu beliebigen, anderen Beans. Für einfache Tests bzw. Demos exponieren wir den Chatbot per RestController:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;@RestController&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;AssistantController &lt;/span&gt;{&lt;br /&gt;    &lt;span style=&quot;color: #9e880d;&quot;&gt;@Autowired&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Assistant &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;assistant&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #9e880d;&quot;&gt;@GetMapping&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;/assistant&quot;&lt;/span&gt;)&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;public &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;assistant&lt;/span&gt;(&lt;span style=&quot;color: #9e880d;&quot;&gt;@RequestParam &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;message) {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color: #871094;&quot;&gt;assistant&lt;/span&gt;.chat(message);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Chatverlauf merken&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Testen wir den Chatbot mit diesen beiden GET-Requests (im Browser):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;http://localhost:8080/assistant?message=&quot;Hallo, ich bin Elmar&quot;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;http://localhost:8080/assistant?message=&quot;Wie ist mein Name&quot;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;Dann stellen wir fest, dass der Chatbot kein Gedächtnis hat und im zweiten Request den Namen nicht mehr kennt. Das ändern wir mit einer &lt;/span&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ChatMemory&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt; Bean:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;org.springframework.context.annotation.&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;Bean&lt;/span&gt;;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;org.springframework.context.annotation.&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;Configuration&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;dev.langchain4j.memory.ChatMemory&lt;/span&gt;;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;dev.langchain4j.memory.chat.MessageWindowChatMemory&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;@Configuration&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;class &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;AssistantConfiguration &lt;/span&gt;{&lt;br /&gt;    &lt;span style=&quot;color: #9e880d;&quot;&gt;@Bean&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #9e880d;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;ChatMemory &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;chatMemory&lt;/span&gt;() {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;MessageWindowChatMemory&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;withMaxMessages&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;10&lt;/span&gt;);&lt;br /&gt;    }  &lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;Durch die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ChatMemory&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt; Bean kennt der Assistant in diesem Code-Beispiel die letzten 10 Chat-Nachrichten. In diesem Beispiel teilen sich alle Benutzer dasselbe Gedächtnis im Chatbot - bei mehr als einen Benutzer benötigen wir eine andere Lösung.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;Fazit&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;span style=&quot;font-family: &amp;quot;Times New Roman&amp;quot;; white-space: normal;&quot;&gt;LangChain4j bietet noch viele weitere interessante Features, siehe dazu&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Times New Roman;&quot;&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;a href=&quot;https://docs.langchain4j.dev/&quot;&gt;https://docs.langchain4j.dev/&lt;/a&gt;. Es ist vergleichbar zu &lt;a href=&quot;https://agile-coding.blogspot.com/2024/05/spring-ai-rag.html&quot; target=&quot;_blank&quot;&gt;Spring AI&lt;/a&gt;. Beide Frameworks vereinfachen die Nutzung von LLMs erheblich und bieten praktische Features für die Entwicklung und Integration von AI in bestehende Java-Anwendungen.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2024/07/langchain4j.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQboPlaqR61uDeWKHVVUmLl4-Vkj6aeUjdKI6OsYrYPOqBxWdvIrZfOF_6DKO-x2ql-GUqO4BmjDXHCUUPgrAw7sqL_-kEOtdPFxZEUV57Zs1aVFXbbBDJmUov2eWD5BWYJR1NoUwtja5ZbTD8BtWlEvSmiCMHaNzaqzNhsOPmGPmZN1H0JK3FtpZuHc5r/s72-c/langchain4j.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>5700 Zell am See, Österreich</georss:featurename><georss:point>47.323519999999988 12.79685</georss:point><georss:box>19.013286163821142 -22.3594 75.63375383617884 47.9531</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-7536655546845629114</guid><pubDate>Mon, 22 Apr 2024 13:36:00 +0000</pubDate><atom:updated>2024-04-22T15:36:48.169+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">Microservice</category><title>IT Stability Health Radar</title><description>&lt;p&gt;&lt;b&gt;How to run IT applications stable in production? Instead of providing one more operations readiness checklist, I created an IT Stability health radar. This helps in agile projects to choose the next stability improvement depending on current situation. All non-functional requirements in this health radar are prioritized by levels, so that you and your Product Owner can plan them properly.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjslhnxxV-hOF5hquiJpT136mbsT4tmQW3kXNch_QM0BAkVlQBQyc6U7MDzuqEXG2gL3VTCgtJuUUVa8Ahg-5Csojt44ZkuT8AQk2Y0i-uW0wxnf8Kb69N84_Rf3HEfgCDy0Ac6bz8ZasmVd3TjEFZrv1_7RXusF8ghjyr0BsjqDrlhcyY5MUyS-YnhyxCH/s794/Stability_Health_Radar.jpg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;794&quot; data-original-width=&quot;794&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjslhnxxV-hOF5hquiJpT136mbsT4tmQW3kXNch_QM0BAkVlQBQyc6U7MDzuqEXG2gL3VTCgtJuUUVa8Ahg-5Csojt44ZkuT8AQk2Y0i-uW0wxnf8Kb69N84_Rf3HEfgCDy0Ac6bz8ZasmVd3TjEFZrv1_7RXusF8ghjyr0BsjqDrlhcyY5MUyS-YnhyxCH/w400-h400/Stability_Health_Radar.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;IT Stability Health Radar&lt;/h2&gt;&lt;div&gt;Companies have often operations readiness checklists, which describe what to do before customers can use an IT system in production. These long lists with non-functional requirements are not easy to use and fulfill in agile working environments. Product owners and business stakeholders have a hard time to accept these requirements, if they come all at the same time and might block a big part of the team for several weeks.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In this article I focus on non-functional requirements related to IT stability. I group and order them, so that you learn where to start and which stability requirements should be planned next. I decided to not go for an operations or production readiness health radar, because that would include many more requirement domains like security, legal, etc. Security has obviously an impact on IT stability, but since this is such a big field, I decided to keep this out of scope (for now).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The non-functional requirements of my IT Stability Health Radar are grouped in 4 categories:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;High Availability&lt;/b&gt; with focus on redundancy and elimination of single point of failures.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Performance&lt;/b&gt; is about how your application handles many parallel usage, so that it does not fail during load peaks.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Resilient Code&lt;/b&gt; is about how developers should implement the application to be resilient in case of unexpected events.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Operations&lt;/b&gt; with focus on change process and monitoring with alerting.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Each category has 5 levels. Level 1 contains the critical foundation of a stable IT system. Level 5 is very advanced and contains the last requirements, which should be tackled for new systems. Depending on the maturity of your application and the expected stability at some point you should address all requirements including level 5.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Requirements in each category might depend on each other. E.g. you can not fully address level 2 requirement (horizontal) scalability, if your application can not run redundant on more than 1 server (level 1 high availability requirement). Requirements in different categories do not depend on each other. But I advise to develop non-functional requirements level by level, because the selected level is based on effort and stability impact estimations.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Category: High Availability&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;Level 1&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Redundancy&lt;br /&gt;&lt;/b&gt;In case your application is productive, it should be running on more than one server to ensure high availability.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;u&gt;Level 2&lt;/u&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;span&gt;Scalability&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;In case of occasionally high load, your application must scale automatically to handle successful load peaks.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 3&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;div&gt;&lt;i&gt;Empty level in this category.&lt;/i&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;Level 4&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Multi Availability Zones&lt;br /&gt;&lt;/b&gt;In case your application is high available (redundant servers), it should be deployed in multiple availability zones.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;Level 5&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Chaos monkey&lt;/b&gt;&lt;br /&gt;Test the resilience of your application with chaos engineering: At least in your test environment, you could activate &quot;chaos monkeys&quot; to test your applications resilience.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Single point of failure&lt;br /&gt;&lt;/b&gt;In case your application is high available, you should reduce the single points of failure. E.g., you have 2 servers behind 1 load balancer, that means the load balancer is your single point of failure.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Category: Performance&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 1&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;div&gt;&lt;i&gt;Empty level in this category.&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 2&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Performance tests&lt;br /&gt;&lt;/b&gt;Test if your application can handle the expected load. You must know the limits of your application - which load peeks can be handled by your application.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Throttling (incoming)&lt;br /&gt;&lt;/b&gt;Your application can not scale &quot;infinitely&quot;, so you should set a rate limit for incoming request / calls.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 3&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Caching&lt;br /&gt;&lt;/b&gt;In case of expectable high load, caching should be added to your application. Think about proper caching strategies for your application and Use-Cases. Find more about &lt;a href=&quot;https://agile-coding.blogspot.com/2022/03/cache.html&quot; target=&quot;_blank&quot;&gt;caching with Spring here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Resource limits&lt;br /&gt;&lt;/b&gt;In case your application can scale vertical, set resource limits. Limit CPU and RAM consumption of Containers (vertical scaling) to ensure that horizontal scaling is also possible.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 4&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Performant database design&lt;br /&gt;&lt;/b&gt;In case your system uses a database, verify that database design does not cause bad performances.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 5&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Automatic Performance tests&lt;/b&gt;&lt;br /&gt;Performance tests should be regularly executed by your CICD pipeline. Focus first on the critical UseCases of your application. When your automated performance tests become stable and do not produce false alerts, you can start adding other UseCases.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Throttling (outgoing)&lt;br /&gt;&lt;/b&gt;In case your application sends request to other systems, you could protect those systems with throttling for outgoing requests.&lt;/p&gt;&lt;p&gt;&lt;i&gt;If you work on requirements in performance category, you should know about &lt;a href=&quot;https://agile-coding.blogspot.com/2023/08/performance-tuning.html&quot; target=&quot;_blank&quot;&gt;good practices for performance tuning&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Category: Resilient Code&lt;/h2&gt;&lt;div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 1&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Reconnect&lt;br /&gt;&lt;/b&gt;In case your application loses the connection to another system, database, API etc., it must not crash and is able to reconnect. This event should be logged.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Error handling&lt;br /&gt;&lt;/b&gt;In case of errors, your application must catch and handle them in proper way. This could be logging, retry or converting to own error message depending on error type (business, technical) and context.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Tests&lt;/b&gt;&lt;br /&gt;Your application must be tested. You should automate your tests from scratch, but the key message here is: Untested software can not be considered as stable. If you do not test, your users test and consider found bugs as instabilities.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 2&lt;/u&gt;&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Automatic regression test&lt;br /&gt;&lt;/b&gt;Regression tests should be automated and executed by your CICD pipeline. For E2E-regression tests focus first on the critical Use-Cases of your application.&lt;/p&gt;&lt;div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 3&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;div&gt;&lt;i&gt;Empty level in this category.&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 4&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Retry&lt;br /&gt;&lt;/b&gt;In case of unexpected error while calling a 3rd party, your system should do a retry. Retry logic must not flood other systems, so proper backoff settings must be in place. A retry should only be added, if it makes sense in the context of this Use-Case.&lt;/p&gt;&lt;div&gt;&lt;b&gt;Stable IO design&lt;br /&gt;&lt;/b&gt;&lt;div&gt;In case your application has access to files or other data stream, proper handling must be implemented.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 5&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;div&gt;&lt;i&gt;Empty level in this category.&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Category: Operations&lt;/h2&gt;&lt;div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 1&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Deployment process&lt;br /&gt;&lt;/b&gt;In case your application is productive, and your team is operating it (DevOps-team), you must have a ticket-based and well-defined deployment-/change-process.&lt;/p&gt;&lt;div&gt;&lt;b&gt;Deployment verification&lt;br /&gt;&lt;/b&gt;In case your team changes productive systems, you must be able to test or verify the success of your change.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/HPwwG4aIFUU&quot; width=&quot;320&quot; youtube-src-id=&quot;HPwwG4aIFUU&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;i&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;More about stable deployments&lt;/i&gt;&lt;/div&gt;&lt;/i&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 2&lt;/u&gt;&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Alerting: Logs&lt;br /&gt;&lt;/b&gt;Monitoring must cover critical log entries. Trigger an alert, if unknown errors or well-known critical entries are logged, which require operations involvement.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Alerting: Application health&lt;br /&gt;&lt;/b&gt;Monitoring must cover the health status endpoint of your application. Trigger an alert, if the health status is negative. Your application requires an API endpoint, which returns its health status. The health status shows, that you application is up and running. The health status might also report insights of your application like connection status against database or 3rd party APIs.&lt;/p&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 3&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Alerting: Resources&lt;br /&gt;&lt;/b&gt;Monitoring must cover CPU-, memory- and storage-consumption. Trigger an alert, if consumption is on a critical level.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;Alerting: Certificates&lt;br /&gt;&lt;/b&gt;Monitoring must cover expiry date of (SSL) certificates. Trigger an alert before the certificate expires and renew it in time.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;b&gt;Canary Deployment&lt;br /&gt;&lt;/b&gt;You should use canary deployments as a progressive rollout of your application. The canary deployment splits traffic between an already-deployed version and a new version. The new version is rolled out to a subset of users before it is fully rolled out to all users. This way problems in new version have limited impact due to traffic split and rollback to already-deployed version is quick and easy.&lt;/p&gt;&lt;div&gt;&lt;b&gt;Operations manual&lt;br /&gt;&lt;/b&gt;In case your application is in production and your team is operating it (DevOps-team), you must have an operations manual. This manual describes operation processes (like changes, deployments, database update &amp;amp; backup, etc.) and the incident handling process. It lists also the stakeholders to be contacted in case of incidents or other issues.&lt;/div&gt;&lt;h3&gt;&lt;b&gt;&lt;u&gt;Level 4&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Alerting: Timeouts&lt;br /&gt;&lt;/b&gt;Monitoring must cover number of timeouts. Trigger an alert, if too many timeouts happened in a defined time range.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;Alerting: Latency (incoming)&lt;br /&gt;&lt;/b&gt;Monitoring should cover latency for incoming requests to your application. Trigger an alert, if your application takes too much time to handle incoming requests.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;b&gt;Alerting: Latency (outgoing)&lt;/b&gt;&lt;br /&gt;Monitoring should cover latency for outgoing requests from your application. An alert could be triggered, if outgoing requests take too much time to be answered by other systems - in that case the problem-solving might not be in your team.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;Level 5&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Alerting: Load&lt;br /&gt;&lt;/b&gt;Monitoring must cover current load. Trigger an alert, if current load is higher than expected load in a defined time range.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to use the IT Stability Health Radar?&lt;/h2&gt;&lt;div&gt;If you work on an existing IT system and you want to improve its stability, you should check first at which level you are currently. Start with level 1 non-functional requirements in each category and check, if your systems fulfils these requirements. If not I recommend to start with these requirements. If you fulfil all requirements in one level, check the next level.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In case you start to develop a new system, you should check which stability level is required. For a prototype or a small MVP, you might not need much. But it is always a good idea to do things in the right way from the beginning, so keep the requirements and level in mind, when you start development.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2024/03/it-stability-health-radar.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjslhnxxV-hOF5hquiJpT136mbsT4tmQW3kXNch_QM0BAkVlQBQyc6U7MDzuqEXG2gL3VTCgtJuUUVa8Ahg-5Csojt44ZkuT8AQk2Y0i-uW0wxnf8Kb69N84_Rf3HEfgCDy0Ac6bz8ZasmVd3TjEFZrv1_7RXusF8ghjyr0BsjqDrlhcyY5MUyS-YnhyxCH/s72-w400-h400-c/Stability_Health_Radar.jpg" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>64 Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-8045125121178625166</guid><pubDate>Wed, 10 Jan 2024 21:44:00 +0000</pubDate><atom:updated>2024-01-10T22:44:58.796+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">Java</category><category domain="http://www.blogger.com/atom/ns#">Linux</category><title>Software-Entwicklung mit KI-Hilfe von ChatGPT</title><description>&lt;p&gt;&lt;b&gt;Wie hilft uns KI beim Programmieren? Als erfahrener Software Entwickler zeige ich euch, in welchen Situationen mir ChatGPT beim Entwickeln besonders hilft.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Die Trainingsdaten der KI enthalten viele Programmiersprachen und Technologien. Außerdem kennt sie viele Algorithmen, Tutorials, Opensource Codes, Probleme und deren Lösungen. Dein Arbeits-Kontext, der komplette Sourcecode dazu und euer Problem bzw. dessen echte Root-Cause sind der KI allerdings unbekannt. Die KI bzw. ChatGPT hilft euch also nur so gut, wie ihr euer Problem bzw. die Anforderung mit passendem Kontext klar beschreibt.&lt;/p&gt;&lt;p&gt;Mein generelles Vorgehen dazu ist &lt;b&gt;Teile und Herrsche&lt;/b&gt;. Ich zerlege meine User Story bzw. meine Aufgabe in kleinere Teile. Kleinere Teilprobleme oder Teilanforderungen lassen sich leichter und genauer beschreiben, so dass die KI bzw. ChatGPT mich besser unterstützen kann.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Um euch das zu verdeutlichen, zeige ich im Folgenden Beispiele aus meinem Entwickler-Alltag. In diesen Beispielen habe ich Teil-Probleme oder einzelne Anforderungen an ChatGPT geschickt. Die Lösungen oder mindestens die Lösungsideen von ChatGPT arbeite ich dann in meinen Code ein. Das Generieren ganzer Programme mit ChatGPT ist bei meinen typischen Enterprise-Anforderungen nicht praktikabel.&lt;/p&gt;&lt;p&gt;Den Artikel-Inhalt und mehr zu Programmieren mit KI gibt es von mir bei &lt;a href=&quot;https://www.udemy.com/course/ai4coding/?referralCode=90FF563948A59AC5CFBB&quot;&gt;Udemy als Online-Training&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Java Probleme&lt;/h2&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Programmieraufgaben&lt;/h4&gt;&lt;div&gt;Wie schon in meinen &lt;a href=&quot;https://youtu.be/nS4SN1QKRpY&quot; target=&quot;_blank&quot;&gt;Video ChatGPT vs. Codewars&lt;/a&gt; gezeigt, kann die KI Programmieraufgaben lösen. Das können wir ausnutzen, indem wir Logiken von ihr programmieren lassen, deren Lösung uns nicht direkt einfällt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEizV6JPtZpH-brV6ylfnt9hTLEY92FubEPlW32GcNQ-BxBi86-Q6GlgWwLDBd4agW53rgebKkHwU70dYIJu61jGg2GnGLDogRauMaxL99UOksUjmwJZ-wtk6zciKgQNZrQxrT7YyVKgCaM8pnCXBJTvZ2_FLrt22sL-C-fcyes5Kr4BF8K2lxCwo0D7WA&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;414&quot; data-original-width=&quot;727&quot; height=&quot;364&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEizV6JPtZpH-brV6ylfnt9hTLEY92FubEPlW32GcNQ-BxBi86-Q6GlgWwLDBd4agW53rgebKkHwU70dYIJu61jGg2GnGLDogRauMaxL99UOksUjmwJZ-wtk6zciKgQNZrQxrT7YyVKgCaM8pnCXBJTvZ2_FLrt22sL-C-fcyes5Kr4BF8K2lxCwo0D7WA=w640-h364&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Dieses Probleme können alle EntwicklerInnen selbst lösen, aber evtl. nicht ganz so schnell wie ChatGPT. Junior EntwicklerInnen könnten hier vom Kennenlernen der &lt;span style=&quot;font-family: courier;&quot;&gt;substring&lt;/span&gt;-Methode zusätzlich profitieren.&lt;/div&gt;&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Typ-Konvertierung&lt;/b&gt;&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;Typ-Konvertierungen sind ein weiteres konkretes Problem, welches eine KI leicht lösen kann. Hier benötigte ich innerhalb eines Mappers eine Konvertierung von LocalDate nach OffsetDateTime. Da diese Konvertierungen häufig eleganter als per Konstruktor-Aufruf funktionieren, habe ich ChatGPT um eine Lösung gebeten:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyp6l9SpTGHo_hk3N_m89wnupJmSHYk63A-RdL5dKHvrZpZJUeO6xtyQDo-8P0K3CvajJa3lMZRlxNnxWWyqzLy0_VokC8ejee08FyA-KFWgGqhy6gXTVmhu5cdWm0hclJMLJ_tMZKkHgQQyU13MsOAd-K74Ds3AHdBr_NetjFBR_XPLGGkoaCHPY6XA/s724/Java_Date_Convertion.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;467&quot; data-original-width=&quot;724&quot; height=&quot;412&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyp6l9SpTGHo_hk3N_m89wnupJmSHYk63A-RdL5dKHvrZpZJUeO6xtyQDo-8P0K3CvajJa3lMZRlxNnxWWyqzLy0_VokC8ejee08FyA-KFWgGqhy6gXTVmhu5cdWm0hclJMLJ_tMZKkHgQQyU13MsOAd-K74Ds3AHdBr_NetjFBR_XPLGGkoaCHPY6XA/w640-h412/Java_Date_Convertion.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Unbekannte Technologie verwenden&amp;nbsp;&lt;/h4&gt;&lt;div&gt;Am meisten profitiere ich von KI, wenn ich eine mir unbekannte Technologie einsetze. Meine Aufgabe war eine CSV-Datei als Datenquelle einzulesen und eine REST-API zum Abfragen dieser Daten zu bauen. Da ich mich mit REST-APIs sehr gut auskenne, benötige ich für diesen Teil keine KI-Hilfe. Allerdings hatte ich zuvor noch nicht eine CSV-Datei mit der Bibliothek &lt;span style=&quot;font-family: courier;&quot;&gt;jackson-dataformat-csv&lt;/span&gt; eingelesen. Früher hätte ich nach passenden Beispielen mit Google gesucht, heute stelle ich die Frage ChatGPT:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEx7J-44DiaNtaKLUkiMNqTjqTAy8FExmDFgMtjRrhJVBKYARoOzgBaLsDbbDI5DxFdUdrzjwpXCeDkHH3m1tWliNJMXG1gXC0NuexZ-5xq5N2CWb9UzzILe3S-y_OM1EeqOes2dgjwKydJXlgYU4Os0fF8-1dXH2qGspYqhizLEJiMKDg22ENRzgfCw/s2447/java_read_csv.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2447&quot; data-original-width=&quot;727&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEx7J-44DiaNtaKLUkiMNqTjqTAy8FExmDFgMtjRrhJVBKYARoOzgBaLsDbbDI5DxFdUdrzjwpXCeDkHH3m1tWliNJMXG1gXC0NuexZ-5xq5N2CWb9UzzILe3S-y_OM1EeqOes2dgjwKydJXlgYU4Os0fF8-1dXH2qGspYqhizLEJiMKDg22ENRzgfCw/s16000/java_read_csv.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Das erste Ergebnis fügte sich nicht gut in meinen eigenen Code ein, daher eine 2. Anfrage zum direkten Schreiben in eine Map.&amp;nbsp;Generell könnt ihr ChatGPT sagen, dass es keine Kommentare in den Code schreiben soll oder wie die Methoden-Signatur aussehen muss. Das vereinfacht das Kopieren des Codes.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Debugging&lt;/h4&gt;&lt;div&gt;Wenn der von ChatGPT produzierte Code Compile-Fehler oder Laufzeit-Fehler verursacht, schickt diese Information im selben Chat als Antwort zurück. ChatGPT schafft es manchmal diese Probleme zu lösen. Das nächste Beispiel ist allerdings eine Bad Practice:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBWhB0wevHyzGeZyTfPNipxCdNNMDMQgTu8-ucKg5P2INSR_bPpl1Y1W6JT6qghe8KPfOav1WynMiRYIuyGInIajoTVK0mjneHFaq9QL05qgrWoYmNNCf1_ZJswBtW2ick6Za24GLYQnEfTOqW-QOtiip5TQlwBGQmj48hr2FI70J8se-sk4JHhiXAuw/s718/debug_bad_example.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;577&quot; data-original-width=&quot;718&quot; height=&quot;514&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBWhB0wevHyzGeZyTfPNipxCdNNMDMQgTu8-ucKg5P2INSR_bPpl1Y1W6JT6qghe8KPfOav1WynMiRYIuyGInIajoTVK0mjneHFaq9QL05qgrWoYmNNCf1_ZJswBtW2ick6Za24GLYQnEfTOqW-QOtiip5TQlwBGQmj48hr2FI70J8se-sk4JHhiXAuw/w640-h514/debug_bad_example.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hier ist meine Fehlerbeschreibung zu ungenau. Ich hatte keinen richtigen Kontext an die KI übergeben. Dadurch nimmt die KI an, dass es einen API Change gab. Tatsächlich hatte ich nur die Version des API Generators erhöht. Die unveränderte API Spezifikation war allerdings nicht mehr kompatibel zur neuen Version 6.4.0 des OpenAPI Code Generators. Dieses Beispiel zeigt, dass die Antwort nur so gut bzw. schlecht ist, wie das Problem beschrieben ist.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Spring Probleme&lt;/h2&gt;&lt;div&gt;Neben der Programmiersprache Java kennt ChatGPT auch verbreitete Frameworks wie Spring. Probleme mit Spring könnt ihr also an die KI übergeben.&lt;/div&gt;&lt;h4 style=&quot;clear: both; text-align: left;&quot;&gt;Spring Properties&lt;/h4&gt;Spring EntwicklerInnen kennen die application.properties oder application.yml Datei. Wir speichern dort einfache Konfiguration im Key-Value Format. Gelegentlich besteht unsere Konfiguration aber auch aus einer Liste von Werten. Das zugehörige Format ist uns eventuell unbekannt, aber nicht der KI:&lt;div&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg2qP7piQUTPPGJe_yF-Y1SrsjOSU_qdbBHoGVCFKNb5KvwO_LKf2m9Zcw6VhUuQ5XVYW-iR4pmrgjJnd03yQkt7zwhNe1VKu7l5W-7uM8mF174pOllBYawrB_OBh6oPs2WHeOzp0BaPT7NnwuAJg21Ho2Gugw2v6OtXkshIFuVKmA64k_kQlVvFM2lg/s2561/spring_properties.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2561&quot; data-original-width=&quot;719&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg2qP7piQUTPPGJe_yF-Y1SrsjOSU_qdbBHoGVCFKNb5KvwO_LKf2m9Zcw6VhUuQ5XVYW-iR4pmrgjJnd03yQkt7zwhNe1VKu7l5W-7uM8mF174pOllBYawrB_OBh6oPs2WHeOzp0BaPT7NnwuAJg21Ho2Gugw2v6OtXkshIFuVKmA64k_kQlVvFM2lg/s16000/spring_properties.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Die erste Antwort passte nicht zum Stil des Projektes. Daher die 2. Frage nach einer Lösung mit der&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;@Value&lt;/span&gt;&amp;nbsp;Annotation. Das Aufsplitten des konfigurierten Wertes per &lt;span style=&quot;font-family: courier;&quot;&gt;split &lt;/span&gt;in der Annotation ist ein gutes Beispiel für seltene Notationsstile, die wir meistens nicht auswendig kennen.&lt;/div&gt;&lt;div&gt;&lt;h4&gt;JSON Serialisierung&lt;/h4&gt;&lt;h4&gt;&lt;div style=&quot;font-weight: 400;&quot;&gt;Im nächsten Beispiel geht es um die Serialisierung von Objekten in JSON. Konkret sollen Attribute mit dem Wert &lt;span style=&quot;font-family: courier;&quot;&gt;null &lt;/span&gt;nicht in die JSON-Struktur geschrieben werden. Dazu muss der von Jackson verwendet &lt;span style=&quot;font-family: courier;&quot;&gt;ObjectMapper &lt;/span&gt;entsprechend konfiguriert werden - ein kompliziertes Spezial-Wissen, genau richtig für eine KI:&lt;/div&gt;&lt;br style=&quot;font-weight: 400;&quot; /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; font-weight: 400; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTizK9PUbHCSTlM-w0jmuPfJ8_w_2dqrcH-vAfPU3P65sIL_YIPpdjznKgi8wFLBlagqYnM1td0jTQUU20HzddDpMLf-lDhSrK79KEUIdoMHrQh_OWArG7WXVgWY4jmUyMgtwJDh-50DgjUcavASx1aGA_V2LR9qbN4lH6Fxc85thDO9SrwPe-mAUeMA/s725/spring_ObjectMapper.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;689&quot; data-original-width=&quot;725&quot; height=&quot;608&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTizK9PUbHCSTlM-w0jmuPfJ8_w_2dqrcH-vAfPU3P65sIL_YIPpdjznKgi8wFLBlagqYnM1td0jTQUU20HzddDpMLf-lDhSrK79KEUIdoMHrQh_OWArG7WXVgWY4jmUyMgtwJDh-50DgjUcavASx1aGA_V2LR9qbN4lH6Fxc85thDO9SrwPe-mAUeMA/w640-h608/spring_ObjectMapper.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/h4&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Problem bei neusten Technologien oder Versionen&lt;/h4&gt;&lt;div&gt;Neue Technologien oder neuste Versionen können der KI unbekannt sein. Weil die Trainingsdaten älter als die Technologie sind, kann die KI uns hier nicht helfen. Im folgenden Beispiel eine Frage zur Konfiguration eines Plugins, um es mit der neuen Spring Boot Version zu verwenden. Spring Boot 3 vom November 2022 ist in den Trainingsdaten von September 2021 unbekannt. Das Angeben der Version in meiner Frage sorgte dafür, dass mich ChatGPT nicht mit alten Informationen in eine falsche Richtung schickt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCNi07ycHmDejzcWCmA2fM6Yfv6j8tX9HftaY1fyojKDpSFS1YiK-yjNrjQg2N_g7pdSEz_0sVJUhJDrts7RPIrzwAInKfGVABT2AnrRHJpcRkDApj2TiBBGUU-4S8OEsakT3HDllxrDWWzY53AjpDbLJQ7WZiHIOz9pD5UoVVOexEWLBtB2KzDmcIpQ/s737/spring_3_not_in_trainings_data.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;187&quot; data-original-width=&quot;737&quot; height=&quot;162&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCNi07ycHmDejzcWCmA2fM6Yfv6j8tX9HftaY1fyojKDpSFS1YiK-yjNrjQg2N_g7pdSEz_0sVJUhJDrts7RPIrzwAInKfGVABT2AnrRHJpcRkDApj2TiBBGUU-4S8OEsakT3HDllxrDWWzY53AjpDbLJQ7WZiHIOz9pD5UoVVOexEWLBtB2KzDmcIpQ/w640-h162/spring_3_not_in_trainings_data.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Test &amp;amp; Maven Probleme&lt;/h2&gt;&lt;div&gt;Die Trainingsdaten der KI umfassen sehr viele Technologien. So hilft ChatGPT auch bei Fragen zum Mockito Test-Framework und zum Maven-Build.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Mockito Mock&lt;/h4&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNfzFNI_72TxZOYZg329pidCacqNtIliMPYzJFLq5qwNDF6Bs6iijqE3F5cjkwrCK9v6o-VS2jt2HlDHTgkTbh8MJDMrsVOfbWPYVCwJVHqPN6aFQ3wO5Fu0sC4hSLL95AsQljsoJysDb9-O9YRP4JV0HydRYHRPlo8Ef0bxqrutZlTsGo3n6izEWMzQ/s729/Mockito_return_input.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;710&quot; data-original-width=&quot;729&quot; height=&quot;624&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNfzFNI_72TxZOYZg329pidCacqNtIliMPYzJFLq5qwNDF6Bs6iijqE3F5cjkwrCK9v6o-VS2jt2HlDHTgkTbh8MJDMrsVOfbWPYVCwJVHqPN6aFQ3wO5Fu0sC4hSLL95AsQljsoJysDb9-O9YRP4JV0HydRYHRPlo8Ef0bxqrutZlTsGo3n6izEWMzQ/w640-h624/Mockito_return_input.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Einen Mock, der einfach einen Eingabeparameter zurück gibt, benötige ich gelegentlich in meinen Tests. Leider kann ich mir die konkrete Lösung nie merken - ich weiß aber, dass die Google-Suche nach diesem Problem aufwändig ist...&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Maven paralleler Build&lt;/h4&gt;&lt;div&gt;Habt ihr ein großes Projekt mit vielen Maven-Modulen? Dann beschleunigt den Build durch paralleles Bauen. ChatGPT empfiehlt diese Tuning Maßnahme, wenn ihr nach einem schnelleren Maven-Build fragt. Im folgenden Frage ich, wie es konkret funktioniert und bekomme eine hilfreiche Antwort.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIwy419OjBFgsaOnPO2aFAVUchHYn7WtVMtYDh72ZAEZv3zHqSsy1_SeTKs8ozLshrGYFK9mdezjL5T84y6McebUWiMiGkJez1kVjtWuwdg_WNNsBeDGYKtezu3Qx23lyRKkDvHYqcMPDC5o4jajiu41lPJ1aV0xW9RcZ5aPdXul49iEd-MvRQ_QHd-Q/s723/Mavan_parallel_build.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;654&quot; data-original-width=&quot;723&quot; height=&quot;578&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIwy419OjBFgsaOnPO2aFAVUchHYn7WtVMtYDh72ZAEZv3zHqSsy1_SeTKs8ozLshrGYFK9mdezjL5T84y6McebUWiMiGkJez1kVjtWuwdg_WNNsBeDGYKtezu3Qx23lyRKkDvHYqcMPDC5o4jajiu41lPJ1aV0xW9RcZ5aPdXul49iEd-MvRQ_QHd-Q/w640-h578/Mavan_parallel_build.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Linux Probleme&lt;/h2&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Datei-Suche mit grep&lt;/h4&gt;&lt;div&gt;Die Linux Shell ist ein mächtiges Werkzeug, dessen Bedienung schwierig zu lernen ist. Müsst ihr beispielsweise eine große Menge von Dateien nach bestimmten Inhalten durchsuchen, hilft die Shell, wenn Ihr das richtige Kommando kennt. Ist euch das Kommando unbekannt, löst die KI das Linux Problem, da sie scheinbar alle Kommandos kennt und ihr das Problem konkret beschreiben könnt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZE5Yfx4hmkI_Ignb35_ZXG-BHozKebfL05OiK5YNZfduT1CGxnBk7_WKqYGvKc9A5PFV8IQqTUdXIO7UTSECQz0VtCVIlfuP-XFvTwcPVkS798bOlmB3TlJ_J4eCe38og6Qy9ZcNHw-h2rW_Vg9W2oa7XrIw6Ly2e0di_Ie5Q5m7UM65x35mBV-K4Yw/s728/linux_shell_grep.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;663&quot; data-original-width=&quot;728&quot; height=&quot;582&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZE5Yfx4hmkI_Ignb35_ZXG-BHozKebfL05OiK5YNZfduT1CGxnBk7_WKqYGvKc9A5PFV8IQqTUdXIO7UTSECQz0VtCVIlfuP-XFvTwcPVkS798bOlmB3TlJ_J4eCe38og6Qy9ZcNHw-h2rW_Vg9W2oa7XrIw6Ly2e0di_Ie5Q5m7UM65x35mBV-K4Yw/w640-h582/linux_shell_grep.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Netzwerk-Tests mit curl&lt;/h4&gt;&lt;div&gt;Zum Analysieren von Netzwerkverbindungen verwende ich die Linux Shell. Kennt Ihr dazu den Befehl curl? Mit welchem Parameter sendet Ihr ein http PATCH statt dem standardmäßigen GET? Und wie setzt man den Proxy für das Firmennetzwerk? Auch für dieses Problem, findet die KI schnell eine Antwort.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvXvOE7wy1lBAkstMj6WumNiHep2kCcdbNoVcQJSy_gaPkGaTk_B1j6ZvNrfjtcKWnI1_CskkLtxQ4mAsGqVIRfLAa8e1A6OqLk15xD64HRwvNfzuF5VhejhK_0sby24ONE6vnjSMPCge39nNF2tBpjxFitqLDwvs3M3plYZxDkRzFntzPMftL4iADKQ/s793/linux_shell_curl.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;793&quot; data-original-width=&quot;722&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvXvOE7wy1lBAkstMj6WumNiHep2kCcdbNoVcQJSy_gaPkGaTk_B1j6ZvNrfjtcKWnI1_CskkLtxQ4mAsGqVIRfLAa8e1A6OqLk15xD64HRwvNfzuF5VhejhK_0sby24ONE6vnjSMPCge39nNF2tBpjxFitqLDwvs3M3plYZxDkRzFntzPMftL4iADKQ/w582-h640/linux_shell_curl.PNG&quot; width=&quot;582&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Programmieren mit KI-Unterstützung macht die Entwicklung schneller. Ich selbst habe in letzter Zeit weniger mit Google beim Programmieren gesucht. Stattdessen setze ich mehr auf KI bzw. ChatGPT. Die Antwortzeiten und Stabilität von ChatGPT sind leider nicht immer gut. Das kostenpflichtige ChtatGPT Plus sollte zuverlässiger sein. Die Alternativen GitHub Copilot oder Bing Chat muss ich noch evaluieren.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ich schließe mit einem Zitat von Albert Einstein. Dieses passt zu meiner Feststellung, dass die KI nur dann helfen kann, wenn wir Problem und Kontext gut beschrieben haben:&lt;/div&gt;&lt;div&gt;&lt;i&gt;„Wenn ich eine Stunde habe, um ein Problem zu lösen, dann beschäftige ich mich 55 Minuten mit dem Problem und 5 Minuten mit der Lösung.“&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/04/chatgpt-coding.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEizV6JPtZpH-brV6ylfnt9hTLEY92FubEPlW32GcNQ-BxBi86-Q6GlgWwLDBd4agW53rgebKkHwU70dYIJu61jGg2GnGLDogRauMaxL99UOksUjmwJZ-wtk6zciKgQNZrQxrT7YyVKgCaM8pnCXBJTvZ2_FLrt22sL-C-fcyes5Kr4BF8K2lxCwo0D7WA=s72-w640-h364-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-2961573373405405028</guid><pubDate>Sat, 02 Dec 2023 22:26:00 +0000</pubDate><atom:updated>2023-12-02T23:26:42.142+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">Java</category><title>Java 21: Die wichtigsten Features seit Version 17</title><description>&lt;p&gt;&lt;b&gt;Java 21, die neue Version mit verlängertem Support, ist da! Hier stelle ich die wichtigsten Features vor:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Interface SequencedCollection,&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Record Patterns,&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Neuerungen bei switch und&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;das Highlight virtuelle Threads&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; font-weight: bold; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjeRQwLH4cGaNGGIzdO08XFpTunr2Ig34yI961pbSfWzHMDs27fpiP60ibJeeqpEFYxDGpMHw1MAaNjEovs0SEWnaNnHCE-zny6zYlIrWaW1WcABhVXTFFsk-HjNacLZ9htpozd_Yqz3BJHwWHC9FkP4jatqxMtslOXQVaA_frKyYebAITczV0gxcWb_DqM&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;3654&quot; data-original-width=&quot;3278&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjeRQwLH4cGaNGGIzdO08XFpTunr2Ig34yI961pbSfWzHMDs27fpiP60ibJeeqpEFYxDGpMHw1MAaNjEovs0SEWnaNnHCE-zny6zYlIrWaW1WcABhVXTFFsk-HjNacLZ9htpozd_Yqz3BJHwWHC9FkP4jatqxMtslOXQVaA_frKyYebAITczV0gxcWb_DqM=w287-h320&quot; width=&quot;287&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Falls Ihr noch Java 11 verwendet, schaut euch hier die Features von Java 17 an:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/09/java-12-bis-15-features.html&quot;&gt;java-17-features.html&lt;/a&gt;&lt;/div&gt;&lt;div&gt;🎓 Auf Udemy findet ihr meinen &lt;a href=&quot;https://www.udemy.com/course/java_17_features/?referralCode=56AA2D304F964694C264&quot; target=&quot;_blank&quot;&gt;kostenloses Online-Kurs zu Java 21&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/3mR4jCVtgRk&quot; width=&quot;320&quot; youtube-src-id=&quot;3mR4jCVtgRk&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Java 21 at YouTube in English&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Interface SequencedCollection&lt;/h2&gt;&lt;div&gt;Die neuen &lt;span style=&quot;font-family: courier;&quot;&gt;Sequenced&lt;/span&gt;-Interfaces erweitern Listen-Implementierungen um den Direktzugriff auf das erste und letzte Element. Am Beispiel einer&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;ArrayList&lt;/span&gt;&amp;nbsp;zeige ich hier die neuen, selbsterklärenden Methoden:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15,0pt;&quot;&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;// Mutable list created.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: black;&quot;&gt;String&lt;/span&gt;&amp;gt; &lt;span style=&quot;color: black;&quot;&gt;list &lt;/span&gt;= &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;ArrayList&amp;lt;&amp;gt;(&lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;1st&quot;&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;2nd&quot;&lt;/span&gt;, &lt;span style=&quot;color: #067d17;&quot;&gt;&quot;3rd&quot;&lt;/span&gt;));&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Read first &amp;amp; last element in list: %s &amp;amp; %s&quot;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;        &lt;/span&gt;.formatted(&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.getFirst(), &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.getLast()));&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;reversedList &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.reversed();&lt;br /&gt;&lt;span style=&quot;color: black;&quot;&gt;reversedList&lt;/span&gt;.addFirst(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;4th&quot;&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Reversed order with new first element: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;reversedList&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Changed list: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.removeFirst();&lt;br /&gt;&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.removeLast();&lt;br /&gt;&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.addFirst(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;begin&quot;&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;.addLast(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;end&quot;&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Changed list: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: black;&quot;&gt;list&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Log-Ausgabe zum Code:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;INFO: Read first &amp;amp; last element in list: 1st &amp;amp; 3rd&lt;br /&gt;INFO: Reversed order with new first element: [4th, 3rd, 2nd, 1st]&lt;br /&gt;INFO: Changed list: [1st, 2nd, 3rd, 4th]&lt;br /&gt;INFO: Changed list: [begin, 2nd, 3rd, end]&lt;/span&gt;&lt;/p&gt;&lt;div&gt;Die eingesetzten Methoden kommen mit dem Interface &lt;span style=&quot;font-family: courier;&quot;&gt;SequencedCollection&lt;/span&gt;. Seit Java 21 gibt es analoge Interfaces als Spezialisierung von &lt;span style=&quot;font-family: courier;&quot;&gt;Map &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;Set&lt;/span&gt;, schaut euch dazu den &lt;a href=&quot;https://openjdk.org/jeps/431&quot; target=&quot;_blank&quot;&gt;JEP 431&lt;/a&gt; an.&lt;/div&gt;&lt;p&gt;Trotz der komfortablen Methoden der &lt;span style=&quot;font-family: courier;&quot;&gt;SequencedCollection&lt;/span&gt;&amp;nbsp;kann es in folgenden Fällen zu &lt;span style=&quot;font-family: courier;&quot;&gt;RuntimeExceptions &lt;/span&gt;kommen:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;NoSuchElementException&lt;/span&gt;&amp;nbsp;- wenn die Liste leer ist und ihr das erste oder letzte Element lesen oder entfernen wollt.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UnsupportedOperationException&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;&lt;/span&gt;- wenn die Liste unveränderlich ist und ihr das erste oder letzte Element entfernen oder hinzufügen wollt. Eine unveränderliche Liste wird z. B. so erzeugt &lt;span style=&quot;font-family: courier;&quot;&gt;List.of(&quot;test&quot;)&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;. Im Code-Beispiel erzeuge ich daher eine veränderliche &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ArrayList&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;NullPointerException&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; - wenn die Listenimplementierung das Hinzufügen von &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; nicht erlaubt.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Auch aus Clean Code Sicht ein tolle Erweiterung, da die neuen teils Parameter-losen Methoden leicht verständlich sind. Sie vermeiden auch Bugs bzgl. falscher Indexierung beim Zugriff auf das letzte Element einer Liste: &lt;span style=&quot;font-family: courier;&quot;&gt;list.size()&lt;/span&gt; vs. &lt;span style=&quot;font-family: courier;&quot;&gt;(list.size() - 1).&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Pattern Matching bei switch&lt;/h2&gt;&lt;div&gt;&lt;div&gt;Mit Java 21 ist das Pattern Matching in &lt;span style=&quot;font-family: courier;&quot;&gt;switch&lt;/span&gt; Blöcken fester Bestandteil der Sprache,&amp;nbsp;siehe dazu &lt;a href=&quot;https://openjdk.org/jeps/441&quot;&gt;JEP 441&lt;/a&gt;. Pattern Matching in&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;switch&amp;nbsp;&lt;/span&gt;funktioniert analog zum Pattern Matching nach&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;instanceof.&lt;/span&gt;&amp;nbsp;Das&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;L&lt;/span&gt;abel definiert mehr als eine einfache Konstante. Es kann sowohl Casten als auch Boolean-Ausdrücke auswerten. Die Auswertung eines Boolean-Ausdrucks im &lt;span style=&quot;font-family: courier;&quot;&gt;case &lt;/span&gt;benötigt zusätzlich das neue Keyword &lt;span style=&quot;font-family: courier;&quot;&gt;when&lt;/span&gt; .&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;null&amp;nbsp;&lt;/span&gt;als&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;case&amp;nbsp;&lt;/span&gt;Label ist jetzt möglich und macht &lt;span style=&quot;font-family: courier;&quot;&gt;null&lt;/span&gt;-Checks vor dem &lt;span style=&quot;font-family: courier;&quot;&gt;switch&lt;/span&gt; überflüssig. Hier ist ein Beispiel mit allen genannten Features:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15,0pt;&quot;&gt;&lt;span style=&quot;color: black;&quot;&gt;BaseStream stream &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;DoubleStream&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #1750eb;&quot;&gt;1.1&lt;/span&gt;);&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;switch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;stream&lt;/span&gt;) {&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;case null &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;null is now a possible case.&quot;&lt;/span&gt;);&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;IntStream &lt;/span&gt;is &lt;span style=&quot;color: #0033b3;&quot;&gt;when &lt;/span&gt;is.isParallel() -&amp;gt;&lt;br /&gt;            &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Expression in case.&quot;&lt;/span&gt;);&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;DoubleStream &lt;/span&gt;ds -&amp;gt; &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Casted in case.&quot;&lt;/span&gt;);&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;default &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: #0033b3;&quot;&gt;throw new &lt;/span&gt;IllegalStateException();&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Record Patterns&lt;/h2&gt;&lt;div&gt;Pattern Matching ist auch mit Records möglich. Das wurde mit &lt;a href=&quot;https://openjdk.org/jeps/440&quot; target=&quot;_blank&quot;&gt;JEP 440&lt;/a&gt; in Java 21 eingeführt. Mehr zu Records findet ihr &lt;a href=&quot;https://agile-coding.blogspot.com/2021/09/java-12-bis-15-features.html&quot; target=&quot;_blank&quot;&gt;hier&lt;/a&gt;. Das Besondere am Record Patterns ist der direkte Zugriff auf die Record-Attribute beim Casten (siehe &lt;span style=&quot;font-family: courier;&quot;&gt;instanceof&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;case &lt;/span&gt;unten). Im folgenden Code Beispiel gibt es 2 verschiedene Records. Beim &lt;span style=&quot;font-family: courier;&quot;&gt;Pair&lt;/span&gt;-Record caste ich im &lt;span style=&quot;font-family: courier;&quot;&gt;if &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;und&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt; switch&amp;nbsp;&lt;/span&gt;Statement jeweils auf die beiden Attribute und verkürze ihren Namen dabei auf &lt;span style=&quot;font-family: courier;&quot;&gt;k&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;v&lt;/span&gt;.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15,0pt;&quot;&gt;&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15,0pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;record &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Pair&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;key, &lt;span style=&quot;color: black;&quot;&gt;Integer &lt;/span&gt;value){}&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;record &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Single&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;Double &lt;/span&gt;element){}&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;private static void &lt;/span&gt;&lt;span style=&quot;color: #00627a;&quot;&gt;recordPatterns&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;mixedList &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;List&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;Single(&lt;span style=&quot;color: #1750eb;&quot;&gt;1.1&lt;/span&gt;), &lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;new &lt;/span&gt;Pair(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;2nd&quot;&lt;/span&gt;, &lt;span style=&quot;color: #1750eb;&quot;&gt;2&lt;/span&gt;));&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;for &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;Object entry &lt;/span&gt;: &lt;span style=&quot;color: black;&quot;&gt;mixedList&lt;/span&gt;) {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;if &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;entry &lt;/span&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;instanceof &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Pair&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;k, &lt;span style=&quot;color: black;&quot;&gt;Integer &lt;/span&gt;v))&lt;br /&gt;            &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Record has: %s %d&quot;&lt;/span&gt;.formatted(k, v));&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;switch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;entry&lt;/span&gt;) {&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Single &lt;/span&gt;s -&amp;gt; &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;Record: &quot; &lt;/span&gt;+ s);&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;case &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;Pair&lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;String &lt;/span&gt;k, &lt;span style=&quot;color: black;&quot;&gt;Integer &lt;/span&gt;v) -&amp;gt; &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.finest(&lt;span style=&quot;color: #067d17;&quot;&gt;&quot;...&quot;&lt;/span&gt;);&lt;br /&gt;            &lt;span style=&quot;color: #0033b3;&quot;&gt;default &lt;/span&gt;-&amp;gt; &lt;span style=&quot;color: #0033b3;&quot;&gt;throw new &lt;/span&gt;IllegalStateException();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Log-Ausgabe zum Code:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;INFO: Record: Single[element=1.1]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;INFO: Record has: 2nd 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Virtuelle Threads&lt;/h2&gt;&lt;div&gt;Endlich - virtuelle Threads sind fester Bestandteil von Java! &lt;a href=&quot;https://openjdk.org/jeps/444&quot; target=&quot;_blank&quot;&gt;JEP 444&lt;/a&gt; integriert die leichtgewichtigen Threads in die Java Plattform. Praktischer Weise unterscheidet sich das Erstellen virtueller Threads kaum vom Erstellen klassischer Threads:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15,0pt;&quot;&gt;&lt;span style=&quot;color: #0033b3;&quot;&gt;try &lt;/span&gt;(&lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;executorService &lt;/span&gt;= &lt;b&gt;&lt;span style=&quot;color: black;&quot;&gt;Executors&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;/b&gt;()) {&lt;br /&gt;    &lt;span style=&quot;color: black;&quot;&gt;executorService&lt;/span&gt;.submit(() -&amp;gt; {&lt;br /&gt;        &lt;span style=&quot;color: #0033b3;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;virtualThread &lt;/span&gt;= &lt;span style=&quot;color: black;&quot;&gt;Thread&lt;/span&gt;.&lt;span style=&quot;font-style: italic;&quot;&gt;currentThread&lt;/span&gt;();&lt;br /&gt;        &lt;span style=&quot;color: #871094; font-style: italic;&quot;&gt;log&lt;/span&gt;.info(virtualThread.threadId() + &lt;span style=&quot;color: #067d17;&quot;&gt;&quot; : &quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;                &lt;/span&gt;+ virtualThread.&lt;b&gt;isVirtual&lt;/b&gt;());&lt;br /&gt;    });&lt;br /&gt;} &lt;span style=&quot;color: #0033b3;&quot;&gt;catch &lt;/span&gt;(&lt;span style=&quot;color: black;&quot;&gt;ExecutionException &lt;/span&gt;| &lt;span style=&quot;color: black;&quot;&gt;InterruptedException &lt;/span&gt;e) {&lt;br /&gt;    &lt;span style=&quot;color: #0033b3;&quot;&gt;throw new &lt;/span&gt;RuntimeException(e);&lt;br /&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die Log-Ausgabe zum Code sieht so aus:&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;INFO: 22 : true&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Mit der neuen Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;isVirtual&lt;/span&gt;&amp;nbsp;überprüfen wir, ob wirklich ein virtueller Thread erzeugt wurde. Der hier gezeigt Thread loggt nur seine eigene ID und ob er virtuell ist.&lt;/li&gt;&lt;li&gt;Zum Erzeugen neuer Threads wird ein &lt;span style=&quot;font-family: courier;&quot;&gt;ExecutorService&lt;/span&gt; mit &lt;span style=&quot;font-family: courier;&quot;&gt;Executors.newVirtualThreadPerTaskExecutor&lt;/span&gt; instanziiert. Dieser Service erzeugt neue virtuelle Threads mit der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;submit&lt;/span&gt;. Somit können virtuelle Threads analog zu klassischen Thread erzeugt werden - am &lt;span style=&quot;font-family: courier;&quot;&gt;ExecutorService&lt;/span&gt; Interface hat sich nichts geändert.&lt;/li&gt;&lt;li&gt;Das hier verwendete &lt;span style=&quot;font-family: courier;&quot;&gt;try-with&lt;/span&gt; bedient das &lt;span style=&quot;font-family: courier;&quot;&gt;AutoCloseable&lt;/span&gt; Interface, um den &lt;span style=&quot;font-family: courier;&quot;&gt;ExecuterService&lt;/span&gt; automatisch zu schließen.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/sScSaClusOc&quot; width=&quot;320&quot; youtube-src-id=&quot;sScSaClusOc&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;i&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Virtual Threads at YouTube in English&lt;/i&gt;&lt;/div&gt;&lt;/i&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Server Anwendungen profitieren von&amp;nbsp;dem nun möglichen Thread-pro-Request Stil. So kann beispielsweise eine mit dem Spring Framework gebaute API, für jeden Request einen eigenen virtuellen Thread erzeugen. Das ermöglicht eine nahezu optimale Hardware-Ausnutzung, wie wir es bisher nur von reaktiven Anwendungen kannten.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;p&gt;2 Jahre nach Java 17 gibt es ein neues LTS (Long Term Support) Release. Dieses erweitert die Sprache Java um wenige, aber gute Sprach-Features. Virtuelle Threads sind für mich das Highlight. Sie helfen unter der Last vieler, parallel eingehender Requests eine bessere Hardware-Nutzung zu erreichen - das spart Geld und Strom!&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2023/09/java-21.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEjeRQwLH4cGaNGGIzdO08XFpTunr2Ig34yI961pbSfWzHMDs27fpiP60ibJeeqpEFYxDGpMHw1MAaNjEovs0SEWnaNnHCE-zny6zYlIrWaW1WcABhVXTFFsk-HjNacLZ9htpozd_Yqz3BJHwWHC9FkP4jatqxMtslOXQVaA_frKyYebAITczV0gxcWb_DqM=s72-w287-h320-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-1673755101324557105</guid><pubDate>Fri, 03 Nov 2023 16:44:00 +0000</pubDate><atom:updated>2023-11-03T17:44:33.796+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>CronJobs mit Spring</title><description>&lt;p&gt;&lt;b&gt;Mit Spring können zeitgesteuerte Aufgaben in Java Code integriert werden. CronJobs wie wir sie in Linux kennen, definieren wir mit Spring einfach per Annotation. In diesem Artikel zeige ich wie das geht und wie Spring die CronJobs entsprechend unserer Definition ausführt.&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHcQpZhfHNwJ9INzOCXrONxV1tgeK-zMA-wPouysRiuahg6rZSZQ4pkZOEN6cBMwF_ZFurwo5sLoIa67Vbd9nerm2I0jyXJ6HJMKtzi0agzjIQ2MLP7Zzz9k4Gnwsx-pDxm5E9b9aEffzZ/s640/stopwatch-259303_640.jpg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;428&quot; data-original-width=&quot;640&quot; height=&quot;268&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHcQpZhfHNwJ9INzOCXrONxV1tgeK-zMA-wPouysRiuahg6rZSZQ4pkZOEN6cBMwF_ZFurwo5sLoIa67Vbd9nerm2I0jyXJ6HJMKtzi0agzjIQ2MLP7Zzz9k4Gnwsx-pDxm5E9b9aEffzZ/w400-h268/stopwatch-259303_640.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Was ist ein CronJob?&lt;/h2&gt;&lt;p&gt;Unter CronJob verstehen wir die zeitlich gesteuerte Ausführung eines Kommandos zur Erledigung einer Aufgabe bzw. eines Jobs. Das Kommando wird durch einen bestimmten Zeitpunkt oder eine zeitliche Bedingung angestoßen.&lt;/p&gt;&lt;p&gt;Typische Beispiele für durch CronJobs gestartete Aufgaben sind:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Regelmäßiges Aufräumen der Datenbank - z. B. um veraltete Daten zu löschen oder DSGVO konform persönliche Daten nach einer definierten Zeit zu löschen.&lt;/li&gt;&lt;li&gt;Wöchentlicher Versand von Newslettern oder Werbung per Email&lt;/li&gt;&lt;li&gt;Nächtliche Datenbank-Backups&lt;/li&gt;&lt;li&gt;Monatliches Erstellen von Rechnungen (z.B. Telefon-Rechnung)&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Das Betriebssystem Linux bietet crontab zum Erstellen von CronJobs an. Mit crontab könnten wir eine Spring Anwendung starten, die täglich neue Angebots-Daten veröffentlicht. Spring bietet uns Spring die Möglichkeit, das ganze ohne Linux und crontab zu tun. Eine ausführliche Erklärung zu CronJobs und ihre Verwendung mit Linux findet ihr hier:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://www.ionos.de/digitalguide/hosting/hosting-technik/cronjob/&quot;&gt;https://www.ionos.de/digitalguide/hosting/hosting-technik/cronjob/&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/eHYNiiEfKyw&quot; width=&quot;320&quot; youtube-src-id=&quot;eHYNiiEfKyw&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Englisches Video zum Artikel&lt;/i&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;CronJobs mit Spring&lt;/h2&gt;&lt;p&gt;Das Spring Framework bietet Entwickler eine Scheduling-Funktionalität zum Erstellen von CronJobs. Scheduling ist Teil der Spring Kern-Funktionalitäten, daher werden keine weiteren Build-Dependencies bzw. Bibliotheken benötigt. Einen einzelnen CronJob legen wir einfach in einer einzigen Klasse an und konfigurieren per Annotation:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;@EnableScheduling&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class ScheduledJobs {&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Logger log = Logger.getLogger(&quot;ScheduledJobs&quot;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;@Scheduled(cron = &quot;1 * * * * *&quot;)&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private void logEvery1stSecond() {&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;log.log(Level.INFO,&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;CronJob executed every 1st second&quot; +&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;of any minute at any day&quot;);&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableScheduling&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; aktiviert die Spring Scheduling Fähigkeit für die komplette Spring Anwendung. Sie sollte zusammen mit der&lt;/span&gt;&lt;b style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Annotation gesetzt werden. Sie muss nicht zusammen mit den CronJob-Methoden in derselben Klasse sein.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;@Scheduled&lt;/b&gt; markiert eine Methode, so dass sie vom Spring Scheduler entsprechend ihrer Konfiguration ausgeführt wird. Hier verwende ich eine einfache CronJob-Konfiguration, welche die Methode in der ersten Sekunden jeder beliebigen Minute ausführt.&lt;br /&gt;&lt;/span&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;cron = &quot;1 * * * * *&quot;&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt; definiert durch die Sterne&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;(&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;*)&lt;/span&gt;, dass Minute, Stunde, Tag, Monat und Wochentag beliebig sind&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Das cron-Attribut&lt;/h3&gt;&lt;p&gt;Das cron-Attribute der &lt;span style=&quot;font-family: courier;&quot;&gt;Scheduled &lt;/span&gt;Annotation ist ähnlich dem Linux crontab aufgebaut. In Spring hat es 6 Leerzeichen-separierte Werte und somit dieses Format:&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;Sekunde&amp;gt; &amp;lt;Minute&amp;gt; &amp;lt;Stunde&amp;gt; &amp;lt;Tag des Monats&amp;gt; &amp;lt;Monat&amp;gt; &amp;lt;Tag der Woche&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Weitere Beispiele für das cron-Attribut:&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled(cron = &quot;1-10 */2 * * * Mon-Fri&quot;)&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;1-10 legt fest, dass die annotierte Methode in den Sekunden 1 bis 10 aufgerufen wird. Das Minuszeichen definiert eine Spanne und legt hier fest, dass die Methode nur an den Werktagen Montag bis Freitag in einer Woche ausgeführt wird.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;*/2 legt fest, dass der CronJob nur jede 2. Minute ausgeführt wird. Würde man statt dem Stern eine Zahl festlegen, z.B. 5/2 so würde der Job ab der 5. Minute jede 2 Minute ausgeführt.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled(cron = &quot;0 0 0 25 12 ?&quot;, &lt;b&gt;zone &lt;/b&gt;= &quot;Europe/Berlin&quot;)&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Dieser CronJob wird einmal im Jahr am ersten Weihnachtstag, dem 25.12. um 0:00 Uhr ausgeführt.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Mit dem &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;zone &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Attribut kann man noch die Zeitzone festlegen, also hier die deutsche Zeitzone.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Der Wochentag kann durch ein Fragezeichen als irrelevant markiert werden.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled(cron = &quot;11,22,33,44,55 * * * * *&quot;)&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die Komma-separierte Liste definiert in welchen Sekunden dieser CronJob ausgeführt wird.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled(cron = &quot;1-10,15,30,45 * * * * ?&quot;)&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Kombinationen sind auch möglich.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Relative Abstände zwischen CronJobs&lt;/h3&gt;&lt;div&gt;Statt mit genauen Zeitangaben (cron-Attribut) können mit &lt;span style=&quot;font-family: courier;&quot;&gt;@Schedule&lt;/span&gt; annotierte Methoden auch mit relativen Zeitspannen zwischen den einzelnen Ausführungen gestartet werden:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;@Autowired&lt;/b&gt; IdGenerator idGenerator;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Scheduled(&lt;b&gt;fixedDelay = 1000, initialDelay = 5000&lt;/b&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private void startJobAfterCompletionOfPreviousSchedule() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;String jobId = idGenerator.generateId();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.log(Level.INFO, &quot;Job completed: &quot; + jobId));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Wenn CronJobs mit Spring definiert und ausgeführt werden, können sie auf den Spring IoC Container und somit alle Beans zugreifen. Hier wurde eine &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;IdGenerator&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Bean mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Autowired&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; injiziert. Weitere Infos zu Beans und Dependency Injektion findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/kernkonzepte-von-spring-beans-und.html&quot;&gt;kernkonzepte-von-spring.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;initialDelay&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt; legt fest wie viele Millisekunden nach Start der Spring Anwendung der CronJob zum ersten Mal startet.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;fixedDelay&amp;nbsp;&lt;/b&gt;definiert wie viele Millisekunden gewartet wird, bevor der CronJob erneut nach Ende der vorherigen CronJobs-Ausführung startet. Im Code-Beispiel oben ist zwischen den Ausführungen des CronJobs immer 1 Sekunde bzw. 1000 Millisekunden Pause.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;fixedRate &lt;/b&gt;&lt;/span&gt;zeige ich im Zusammenhang mit asynchroner Ausführung von CronJobs. Das Attribut&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;fixedRate &lt;/span&gt;legt fest wie viele Millisekunden Abstand zwischen dem Start zweier CronJobs ist.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Asynchrone Ausführung von CronJobs&lt;/span&gt;&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; annotierte Methoden werden grundsätzlich im scheduling-Thread ausgeführt. In meinem Beispiel-Code verwende ich immer eine Logger-Instanz, da diese den Namen des Threads loggt. Das verdeutlicht den Unterschied zu asynchron ausgeführten CronJobs. Startet unsere Anwendung viele zeitlich überlappende CronJobs, kann es besser bzw. performanter sein die CronJobs asynchron auszuführen. Ohne zeitliche Überlappung ist die asynchrone Ausführung der CronJobs nicht notwendig, da jede CronJob-Ausführung im scheduling-Thread unabhängig vom main-Thread und der restlichen Anwendung stattfindet.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;@EnableAsync&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableScheduling&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class AsyncScheduledJobs {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Logger log = Logger.getLogger(&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;AsyncScheduledJobs&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Scheduled(&lt;b&gt;cron = &quot;${scheduled.special_seconds}&lt;/b&gt;&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;@Async&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void logAtSpecialSeconds() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;/span&gt;log.log(Level.INFO, &quot;Job async executed&quot;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Scheduled(&lt;b&gt;fixedRateString = &quot;${scheduled.fixed-rate}&quot;,&lt;br /&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;initialDelayString = &quot;${scheduled.initial-delay}&quot;&lt;/b&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;@Async&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void startJobOften() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; white-space: pre;&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;log.log(Level.INFO, &quot;Job async executed&quot;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Um CronJobs asynchron durch den Spring Scheduler auszuführen, muss zur &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableScheduling&lt;/span&gt; Annotation auch die&amp;nbsp;&lt;b style=&quot;font-family: courier;&quot;&gt;@EnableAsync&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt; in einer Konfiguration-Klasse gesetzt werden.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Jede &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; annotierte Methode, die asynchron zu anderen CronJobs ausgeführt werden soll, benötigt die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;@Async&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Annotation. Danach startet sie der Spring Scheduler in einem separaten Thread.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die String-Attribute (inklusive cron-Attribut) der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Scheduled&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Annotation, können aus den Spring Properties Dateien gelesen werden. Dazu schreibt man den Properties-Schlüssel in &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;${...}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Spring geht alle Property Definitionen durch, um den Wert passend zum Schlüssel zu finden. Meine &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;i&gt;application.properties&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Datei zu diesem Beispiel sieht so aus:&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;scheduled.special_seconds=11,22,33,44,55 * * * * ?&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;scheduled.fixed-rate=2500&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;scheduled.initial-delay=4000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;i&gt;Anmerkung&lt;/i&gt;: Definiert ihr mehrere CronJobs mit zeitlicher Überlappung, starten einige Ausführungen möglicherweise nicht, weil der scheduling-Thread gerade ausgelastet ist. Die asynchrone Ausführung kann dies verhindern. Startet meinen JUnit-Test&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ScheduledJobsTest&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; um zu sehen,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wie einige Ausführungen übersprungen werden. Die Ursache dessen ist, dass der scheduling-Thread durch die Methode&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;startJobAfterCompletionOfPreviousSchedule &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;phasenweise blockiert ist.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;p&gt;Die Scheduling Fähigkeit von Spring ist sehr gut ins Spring Framework integriert und einfach zu benutzen. Falls ihr Linux CronJobs kennt, versteht ihr alles schnell.&amp;nbsp;&lt;/p&gt;&lt;p&gt;In der Praxis verwende ich die Scheduling Fähigkeit von Spring, um alte Daten aus der Datenbank zu löschen. Dazu verwende ich einfach die vorhandene Beans, die JPA Entitäten und die Datenbank Connection innerhalb des CronJobs.&lt;/p&gt;&lt;p&gt;Den kompletten Code findet ihr in GitHub:&lt;br /&gt;&lt;a href=&quot;https://github.com/elmar-brauch/beans/tree/master/src/main/java/de/bsi/bean/schedule&quot;&gt;https://github.com/elmar-brauch/beans/tree/master/src/main/java/de/bsi/bean/schedule&lt;/a&gt;&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2021/03/spring-cronjobs.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHcQpZhfHNwJ9INzOCXrONxV1tgeK-zMA-wPouysRiuahg6rZSZQ4pkZOEN6cBMwF_ZFurwo5sLoIa67Vbd9nerm2I0jyXJ6HJMKtzi0agzjIQ2MLP7Zzz9k4Gnwsx-pDxm5E9b9aEffzZ/s72-w400-h268-c/stopwatch-259303_640.jpg" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-1691111154237793645</guid><pubDate>Wed, 13 Sep 2023 20:06:00 +0000</pubDate><atom:updated>2023-09-13T22:06:11.429+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">AI</category><title>Copilot Evaluierung bei der Telekom</title><description>&lt;p&gt;&lt;b&gt;Hebt KI die Software-Entwicklung auf ein neues Level? Sind EntwicklerInnen mit KI Support schneller als ohne? Diesen Fragen stellen wir uns bei der Deutschen Telekom IT GmbH. Dazu führten mein Team und ich eine Evaluierung von GitHub Copilot durch. Ich stelle euch hier die Ergebnisse und Meinungen des Teams vor.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgC-grOdPl1sJAdi5oD1E-HT1WWmHtezgb2bJsA5QAbimOcZPopofggFP_rHXaJ3sTPNuChx62XlyhnkQuYbd5-9d8d1wpkn9pV6WvruBhtjT0SVyesdUmD1nRyq0G_Q6y2qcQ7fxkYMOuGCjFsRW-XMoRf_GdxIwJaCq5Un32G1uisL9aXQtLa-AsZwhcq&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;357&quot; data-original-width=&quot;357&quot; height=&quot;148&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgC-grOdPl1sJAdi5oD1E-HT1WWmHtezgb2bJsA5QAbimOcZPopofggFP_rHXaJ3sTPNuChx62XlyhnkQuYbd5-9d8d1wpkn9pV6WvruBhtjT0SVyesdUmD1nRyq0G_Q6y2qcQ7fxkYMOuGCjFsRW-XMoRf_GdxIwJaCq5Un32G1uisL9aXQtLa-AsZwhcq=w200-h148&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/b&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Warum Copilot?&lt;/h2&gt;&lt;div&gt;Copilot integriert sich in die Entwicklungsumgebung der Programmierenden. Es analysiert den vorhandenen Sourcecode und macht mittels KI unterstützter Text-Completion Vorschläge für weiteren Code. Diesen können die ProgrammiererInnen per Tastendruck übernehmen. Das beschleunigt insbesondere das Schreiben von sich wiederholendem Code.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/h4wXW8VQGdA&quot; width=&quot;320&quot; youtube-src-id=&quot;h4wXW8VQGdA&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Copilot in der Praxis&lt;/i&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;ChatGPT hilft EntwicklerInnen bei technischen Fragestellungen. Dazu unterbrechen wir das Schreiben von Code in der IDE, öffnen im Browser die Chat-KI und stellen unsere Frage oder Anforderung. Mit der Antwort von ChatGPT wechseln wir in die IDE und probieren sie aus. Im Unterschied zu ChatGPT unterstützt Copilot die Entwickelnden direkt beim Schreiben von Code in der IDE. Ich sehe die beiden KI Tools nicht als Konkurrenten sondern als Werkzeuge für unterschiedliche Zwecke. Mehr zu ChatGPT für EntwicklerInnen findet Ihr hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2023/04/chatgpt-coding.html&quot;&gt;chatgpt-coding.html&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Ziel und Rahmen der Copilot Evaluierung&lt;/h2&gt;&lt;div&gt;Vor der konzernweiten Einführung eines neuen, kostenpflichtigen Tool findet meist eine Evaluierung statt. Konkret war das Ziel im Evaluierungsteam herauszufinden, ob die tägliche Arbeit in der Software-Entwicklung beschleunigt wird und wenn ja, um wie viel.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ich selbst bin Software Entwickler und Architekt im Scrum-Team Asteroid. Das Asteroid Team baut &lt;a href=&quot;https://agile-coding.blogspot.com/2021/10/scs.html&quot; target=&quot;_blank&quot;&gt;Microservices und Self-Contained Systems&lt;/a&gt; als Teil des &lt;a href=&quot;https://geschaeftskunden.telekom.de/&quot; target=&quot;_blank&quot;&gt;Geschäftskunden-Portal der Deutschen Telekom&lt;/a&gt;. Im Sinne von DevOps ist unser Team sowohl für die Entwicklung als auch für den Betrieb unserer Systeme verantwortlich. Den Schwerpunkt unser täglichen Arbeit bildet aber die Entwicklung. Unser Technologie-Stack besteht im wesentlichen aus Java, Spring und Vue.js. Als Entwicklungsumgebung verwenden wir größtenteils IntelliJ.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Durchführung der Evaluierung&lt;/h2&gt;Als Scrum-Team schätzen wir den Aufwand jeder UserStory bevor wir sie bearbeiten. Vor der Evaluierung hatte unser Team bereits 25 zweiwöchige Sprints abgeschlossen. &lt;br /&gt;Zu jedem Sprint erfassten wir folgende Daten:&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Entwicklungskapazität: Wie viele Story Points kann das Team wahrscheinlich im kommenden Sprint abschließen.&lt;/li&gt;&lt;li&gt;Burndown-Charts: Wie viele Story Points hat das Team im vergangenen Sprint tatsächlich abgeschlossen.&lt;/li&gt;&lt;li&gt;Anzahl der Software-EntwicklerInnen im jeweiligen Sprint. Die Größe unseres Teams hatte sich in den 25 Sprints verändert, z. B. durch Fluktuation, Neueinstellungen, Urlaube oder Schulungen.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Zu Beginn des 26. Sprints erhielten alle EntwicklerInnen die Copilot-Lizenz und installierten das Plugin innerhalb eines Tages in ihrer IDE. Da wir eine 30-tägige Evaluierungs-Lizenz hatten, konnten wir Copilot in 2 kompletten Sprints verwenden.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;8 von 9 EntwicklerInnen machten in unserem Team bei der Copilot Evaluierung mit. 1 Entwickler entschied sich dagegen, da er Copilot bzw. GitHub hinsichtlich der Daten-Sicherheit misstraute.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Ergebnisse durch Scrum-Metriken&lt;/h2&gt;&lt;div&gt;Unser ScrumMaster lieferte die folgenden Mess-Ergebnisse:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Im ersten Sprint mit Copilot erreichten wir 38 Story Points.&lt;/li&gt;&lt;li&gt;Im zweiten Spint mit Copilot erreichten wir 28 Story Points.&lt;/li&gt;&lt;li&gt;Der Durchschnittswert an Story Points in den 25 vorherigen Sprints ist 23.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Beide Copilot Sprints waren also überdurchschnittlich erfolgreich.&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Die Top 5 Sprints vor Copilot hatten als Ergebnis 48, 40, 37, 34 und 33 Story Points.&lt;/li&gt;&lt;li&gt;Der erste Copilot Sprint war also unser drittbester Sprint von insgesamt 27 Sprints.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Aus Evaluierungssicht hatten wir Seiteneffekte in beiden Sprints, so dass die zuvor genannten Ergebnisse leider relativiert werden müssen:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Verglichen mit den vorherigen Sprints hatte das Asteroid Team mehr Entwickelnde in der Evaluierungs-Phase als zuvor. Mit den vorhandenen Daten könnten wir die Story Points jedes Sprints ins Verhältnis zur Anzahl der Entwickelnden setzen. Da neue EntwicklerInnen aber Einarbeitungszeit benötigen, lässt sich dieser Seiteneffekt trotzdem nicht ganz eliminieren.&lt;/li&gt;&lt;li&gt;Ein weiteres Rauschen war die Bearbeitung von Bugs und anderen operativen Tasks. Den Aufwand dafür messen wir nicht in Story Points. In den beiden Evaluierungs-Sprints hatten wir relative wenige Bugs, während wir in den vorherigen Sprint durch einen großen Go-Live relativ viel ungeplante Arbeit hatten.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Interviews mit Software-EntwicklerInnen zu Copilot&lt;/h2&gt;&lt;div&gt;Um den Wert von Copilot besser einzuschätzen, habe ich neben den Messungen Interviews mit allen Team-KollegInnen durchgeführt. Als erstes haben wir die Interviews vom Hersteller selbst überprüft. GitHub selbst erstellte die Umfrageergebnisse im folgenden Bild links. Auf der rechten Seite beantworten die Entwickelnden im Asteroid Team die selben Fragen:&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjxzg8wFEjVvE68kqmH814jdQcw46k7z_77T_EMX1JFfMd64avMzo-1Q1ToNJT21wwUzMeMQ8yk_VDilpgtSltj7yi_jNi_yJfsgLUUB1wky1a2m2ztkT-HoDZLZG-mDzm8Bj2MHIXhwiAOFeecwoj7ASDoHGoyRGmRNk0PEhkbpxvp2p1etucRqVdiC3BW&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1678&quot; data-original-width=&quot;2781&quot; height=&quot;386&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjxzg8wFEjVvE68kqmH814jdQcw46k7z_77T_EMX1JFfMd64avMzo-1Q1ToNJT21wwUzMeMQ8yk_VDilpgtSltj7yi_jNi_yJfsgLUUB1wky1a2m2ztkT-HoDZLZG-mDzm8Bj2MHIXhwiAOFeecwoj7ASDoHGoyRGmRNk0PEhkbpxvp2p1etucRqVdiC3BW=w640-h386&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Vergleich GitHub Copilot &quot;Werbung&quot; mit Erfahrung in meinem Team&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Zwischen der Umfrage des Herstellers und der Erfahrung in unserem Team gibt es signifikante Unterschiede. Allerdings bestätigt unsere Umfrage die Ergebnisse zu den wichtigen Fragen nach höherer Produktivität und Geschwindigkeit.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Da bei unserer Evaluierung die Frage nach dem Nutzen in der täglichen Arbeit eines Scrum-Teams im Fokus stand, habe ich noch 4 andere Fragen gestellt:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Using it? - Verwendest Du Copilot wirklich in der täglichen Arbeit?&lt;/li&gt;&lt;li&gt;Is helpful in your Daily Work? - Hilft Copilot Dir bei Deiner täglichen Arbeit in der Software-Entwicklung?&lt;/li&gt;&lt;li&gt;Makes Copilot you faster? - Macht Dich Copilot bei Deiner Arbeit insgesamt schneller? Hintergrund neben dem reinen Entwickeln verwenden wir auch viel Zeit für Tests, Betriebsaufgaben (DevOps-Team), Scrum-Rituale etc. Das eigentliche Programmieren bei dem uns Copilot unterstützt ist nur ein Teil unserer täglichen Arbeit.&lt;/li&gt;&lt;li&gt;What impact do you expect in future? - Welchen Effekt auf Deine Arbeit erwartest Du durch Copilot in der Zukunft?&lt;/li&gt;&lt;/ul&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgxlVzjOhDlLPAv18DDDOyv66rDBdPVIL7uwpL0_YH10XENH5Wkznh4zE5PMr1wGUQgTgrTvx5g3HUZmKUFNpfkxTE7vTAP9MRK-4yy6Ju0bOWgoim3L6_WC5Ht-KLua1qqzCZm0mXTsx4ZKL0xNhSMvJTUIJ69EjVmvXlo4eKhvO8ZH6BBU1dASxa8FxM1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1468&quot; data-original-width=&quot;3810&quot; height=&quot;246&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgxlVzjOhDlLPAv18DDDOyv66rDBdPVIL7uwpL0_YH10XENH5Wkznh4zE5PMr1wGUQgTgrTvx5g3HUZmKUFNpfkxTE7vTAP9MRK-4yy6Ju0bOWgoim3L6_WC5Ht-KLua1qqzCZm0mXTsx4ZKL0xNhSMvJTUIJ69EjVmvXlo4eKhvO8ZH6BBU1dASxa8FxM1=w640-h246&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Interview zu Copilot&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Zusammenfassung der Antworten:&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Alle TeilnehmerInnen verwendeten Copilot in der täglichen Arbeit während der Evaluierung.&lt;/li&gt;&lt;li&gt;Für fast alle war Copilot hilfreich. Insbesondere bei ähnlichem Code oder sich wiederholendem Code wurde die Nützlichkeit festgestellt.&lt;/li&gt;&lt;li&gt;Einige berichten, dass Copilot bei komplexen Aufgaben scheiterte.&lt;/li&gt;&lt;li&gt;Fast alle waren durch Copilot zumindest ein wenig schneller.&lt;/li&gt;&lt;li&gt;Die meisten erwarten in Zukunft einen noch größeren Effekt auf die tägliche Arbeit durch Copilot.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit und Ausblick&lt;/h2&gt;Copilot erzielt ein gutes Evaluierungsergebnis.&amp;nbsp;Wir empfehlen die Verwendung von Copilot.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;GitHub Copilot kostet im September 2023 19$ pro Benutzer pro Monat. Basierend auf den Evaluierungsergebnissen nehme ich an, dass die meisten EntwicklerInnen durch Copilot mindestens 1 Stunde pro Monat schneller sind. Die Stundensätze von Developern liegen in den meisten Länder deutlich über 19$, daher sehe ich hier eine positive Kosten-Nutzen-Rechnung.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In naher Zukunft erwarte ich weitere Features in Copilot, welche die Entwicklung beschleunigen und die Qualität verbessern, siehe &lt;a href=&quot;https://githubnext.com/projects/copilot-labs/&quot; target=&quot;_blank&quot;&gt;GitHub Labs&lt;/a&gt;. Daher empfehle ich jedem zumindest das Ausprobieren von GitHub Copilot.&lt;/div&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/09/copilot-evaluation.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgC-grOdPl1sJAdi5oD1E-HT1WWmHtezgb2bJsA5QAbimOcZPopofggFP_rHXaJ3sTPNuChx62XlyhnkQuYbd5-9d8d1wpkn9pV6WvruBhtjT0SVyesdUmD1nRyq0G_Q6y2qcQ7fxkYMOuGCjFsRW-XMoRf_GdxIwJaCq5Un32G1uisL9aXQtLa-AsZwhcq=s72-w200-h148-c" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Valencia, Provinz Valencia, Spanien</georss:featurename><georss:point>39.4699075 -0.3762881</georss:point><georss:box>11.159673663821152 -35.5325381 67.780141336178843 34.7799619</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-1861563592442262526</guid><pubDate>Sat, 09 Sep 2023 11:11:00 +0000</pubDate><atom:updated>2023-09-09T13:11:11.471+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Spring-Basics</category><category domain="http://www.blogger.com/atom/ns#">Thymeleaf</category><title>Model-View-Controller mit Spring und Thymeleaf</title><description>&lt;p&gt;&lt;b&gt;Thymeleaf ist eine moderne Template Engine, um Server-seitig html zu generieren. Hier zeige ich, wie es in einem Spring MVC Projekt eingesetzt wird. Dabei demonstriere ich verschiedene Thymeleaf Ausdrücke (if, loop, usw.), Lesen des Modells und Nutzung von CSS oder JavaScript.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Model-View-Controller mit Spring und Thymeleaf&lt;/h2&gt;&lt;div&gt;Betrachten wir das bekannte Design Pattern MVC (siehe auch &lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/rest-json-apis-in-java-leicht-gemacht.html&quot;&gt;rest-json-apis-in-java-leicht-gemacht&lt;/a&gt;), dann können wir die einzelnen Bestandteile mit dem hier vorgestellten Technologie-Stack implementieren:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Das Modell (&lt;b&gt;Model&lt;/b&gt;) wird mit einfachen Java Objekten realisiert (POJO).&lt;/li&gt;&lt;li&gt;Die Ansicht (&lt;b&gt;View&lt;/b&gt;) wird mittels Thymeleaf in html, css und JavaScript implementiert.&lt;/li&gt;&lt;li&gt;Der &lt;b&gt;Controller &lt;/b&gt;wird als Spring Bean vom Typ &lt;span style=&quot;font-family: courier;&quot;&gt;@Controller&lt;/span&gt; umgesetzt.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIR3M591fSIKR-iN6aMf5n_8U1YOcTkDdgyzNatTgIqLObtbfvnBYtilpj-eusZPCIvTzbsbNm5Bbua-Oq9QKEwKd9ibAiCiKFu-jsrupMoQ6zwsY86khptsLJET7PWNfUg7M6kDE0L0Cn/s469/mvc.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;225&quot; data-original-width=&quot;469&quot; height=&quot;308&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIR3M591fSIKR-iN6aMf5n_8U1YOcTkDdgyzNatTgIqLObtbfvnBYtilpj-eusZPCIvTzbsbNm5Bbua-Oq9QKEwKd9ibAiCiKFu-jsrupMoQ6zwsY86khptsLJET7PWNfUg7M6kDE0L0Cn/w640-h308/mvc.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Thymeleaf (&lt;a href=&quot;https://www.thymeleaf.org/&quot;&gt;https://www.thymeleaf.org/&lt;/a&gt;) ist eine Template Engine deren Templates in html geschrieben werden. Dynamische Stellen im html werden dann durch Thymeleaf Ausdrücke definiert. Z.B. blenden wir mit folgendem &lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;if&lt;/span&gt;&lt;/b&gt;-Ausdruck eine Nachricht (bzw. das ganze &lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;/b&gt;-Tag) ein oder aus:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;p &lt;b&gt;th:if=&quot;${#lists.isEmpty(items)}&quot;&lt;/b&gt;&amp;gt;Die Liste ist leer.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;itmes&lt;/b&gt; &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;ist eine Liste von &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Objekten im Modell, das vom Controller an die Thymeleaf View übergeben wurde. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;ist der XML Namensraum (Namespace) von Thymeleaf, der weiter oben im selben Dokument definiert sein muss.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Variable Ausdrücke, die Thymeleaf zur html Generierung auswertet, werden unter anderem mit &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;${...}&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;markiert. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;#lists&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist ein von Thymeleaf bereitgestelltes Hilfs-Objekt für Listen-Operationen, wir nutzen hier die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;isEmpty &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Methode, um das &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;lt;p&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;-Tag nur dann einzublenden, wenn die Liste &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;items &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;leer ist.&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/zKz40V2b8Uc&quot; width=&quot;320&quot; youtube-src-id=&quot;zKz40V2b8Uc&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;YouTube Video zum Artikel&lt;/i&gt;&lt;/div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Thymeleaf ins Java Projekt integrieren&lt;/h3&gt;&lt;div&gt;Thymeleaf befindet sich als Java-Bibliothek im Maven Repository und kann somit als Dependency in jedes Maven oder Gradle Projekt geladen werden. Ein bestehendes Spring Boot Projekt (siehe &lt;a href=&quot;https://agile-coding.blogspot.com/2020/09/microservices-mit-spring-boot-erstellen.html&quot;&gt;microservices-mit-spring-boot-erstellen&lt;/a&gt;) kann durch Hinzufügen der von Spring Boot gemanagten Thymeleaf Dependency ergänzt werden:&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;artifactId&amp;gt;spring-boot-starter-thymeleaf&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Spring Controller&lt;/h2&gt;&lt;div&gt;Der Controller für die Thymeleaf View wird in Spring analog zu einem RestController erstellt (siehe auch&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/rest-json-apis-in-java-leicht-gemacht.html&quot;&gt;rest-json-apis-in-java-leicht-gemacht&lt;/a&gt;). Der wesentliche Unterschied ist, dass wir in jeder Controller-Methode, die nächste anzuzeigende View festlegen müssen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Unsere Demo Web-Anwendung soll die folgenden UseCases umsetzen:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Liste mit allen erstellten Dingen (Items) anzeigen.&lt;/li&gt;&lt;li&gt;Neues Ding erstellen und der Liste hinzufügen.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Der Spring MVC Controller für diese Anwendung sieht so aus:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.stereotype.Controller;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.ui.Model;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.web.bind.annotation.GetMapping;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.web.bind.annotation.PostMapping;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.web.bind.annotation.RequestParam;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;import org.springframework.web.servlet.ModelAndView;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;@Controller&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class ItemController {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private List&amp;lt;Item&amp;gt; items = new ArrayList&amp;lt;&amp;gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private static final String MODEL_KEY_ITEMS = &quot;items&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private static final String VIEW_NAME_LIST = &quot;item-list&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private static final String VIEW_NAME_CREATE = &quot;item-create&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@GetMapping(path = {&quot;/&quot;, &quot;/item&quot;})&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public &lt;b&gt;ModelAndView&lt;/b&gt; showItems(&lt;b&gt;ModelAndView mav&lt;/b&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;/span&gt;mav.setViewName(VIEW_NAME_LIST);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;mav.addObject(MODEL_KEY_ITEMS, items);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return mav;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@GetMapping(&quot;/item-create&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public String showCreateItem() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;/span&gt;&lt;b&gt;return VIEW_NAME_CREATE;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@PostMapping(&quot;/item&quot;)&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public String createNewItem(&lt;b&gt;Model model&lt;/b&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;@RequestParam String itemname,&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@RequestParam String itemid) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;var item = new Item();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;item.setName(itemname);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;item.setId(itemid);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;items.add(item);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;model.addAttribute(MODEL_KEY_ITEMS, items);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return VIEW_NAME_LIST;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Den klassischen MVC Controller annotieren wir in Spring mit &lt;span style=&quot;font-family: courier;&quot;&gt;@Controller&lt;/span&gt;. Dadurch wird der &lt;span style=&quot;font-family: courier;&quot;&gt;ItemContoller &lt;/span&gt;als Bean im Spring IoC Container bekannt. Eingehende Requests werden vom Dispatcher Servlet entsprechend dem registrierten Handler Mapping an den richtigen Controller geschickt.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;@PostMapping&lt;/span&gt; kennen wir schon aus diesem Artikel&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/rest-json-apis-in-java-leicht-gemacht.html&quot;&gt;rest-json-apis-in-java-leicht-gemacht&lt;/a&gt;. Das Spring Handler Mapping basiert auf den hier konfigurierten Pfaden, so dass eingehendet Request entsprechend ihres Typs und URL-Pfades an die jeweilige Methode des Controllers geschickt werden. &lt;br /&gt;Die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;showItems &lt;/span&gt;bearbeitet hier eingehende GET Requests für 2 verschiedene Pfade - generell können beliebig viele Pfade bzw. eine Liste von Pfaden auf eine Methode gemappt werden.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RequestParam&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;mappt die Daten innerhalb des POST Formulars auf den annotierten Parameter in der Methode&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;createNewItem&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Das Formular stelle ich in einem zweiten Artikel zu Thymeleaf html Templates vor.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;: Die Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;createNewItem &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;hat den Parameter &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, der automatisch eine Instanz des &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Interface bereitstellt. Diese &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz kann dann mit den Daten des Modells befüllt werden und in der View ausgelesen werden (siehe Kapitel weiter unten). &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;ist vergleichbar mit einer &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, daher kann eine &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz auch mit der Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;asMap &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;als &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Map &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz verarbeitet werden.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ModelAndView&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;: Die Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;showItems &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;hat den Parameter &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;mav &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;vom Typ &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ModelAndView&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ModelAndView &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Klasse ist ein Kombination aus dem &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Model &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;und der anzuzeigenden &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;View&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;mav.addObject&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; werden Objekte dem Modell innerhalb der Klasse &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ModelAndView &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;hinzugefügt. Mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;setView &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;oder &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;setViewName &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird die anzuzeigende View definiert. Im Beispiel hier setze ich immer den String &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;viewName&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, dessen Wert dem Namen der Thymeleaf html-Templates mit oder ohne Dateiendung &quot;.html&quot;&amp;nbsp;entsprechen muss.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Wenn die Methode einfach nur einen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;zurückgibt (siehe &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;showCreateItem&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;), versucht Spring eine passende View zu finden und diese anzuzeigen. Findet Spring keine View, kommt es zu einem &quot;Internal Server Error&quot;.&lt;br /&gt;Hier unterscheiden sich &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RestController&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; vom &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Controller&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Der RestController interpretiert den Sting als den Inhalt der Response und nicht als die nächste anzuzeigende View. Gibt man im RestController ein&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;View&amp;nbsp;&lt;/span&gt;Objekt zurück, so zeigt auch dieser die View an.&lt;br /&gt;Ihr könnt das einfach testen, indem ihr in unserem Demo Projekt einfach mal die &lt;span style=&quot;font-family: courier;&quot;&gt;ItemController &lt;/span&gt;Annotation ändert.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;POJO Modell Klasse&lt;/h2&gt;&lt;div&gt;Die Modell Klassen sind einfache POJO (Plain Old Java Object) Klasse. Das Besondere daran ist, dass es keine Besonderheiten gibt. 😄&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Daher zeige ich euch hier einfach kurz die Item Klasse bestehend aus Attributen, Getter-, Setter-Methoden und sonst nichts.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public class Item {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;private String id, name;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;public String getId() { return id; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;public void setId(String id) { this.id = id; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;public String getName() { return name; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;public void setName(String name) { this.name = name; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Thymeleaf View&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Nachdem wir in den vorherigen Kapiteln über den Controller und das Modell gelesen haben, kommen wir nun zur Ansicht (View), die mit Thymeleaf umgesetzt wird.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Controller und Modell könnten wir auch mit beliebigen anderen View Technologien kombinieren, wie zum Beispiel&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;JSP - JavaServer Pages :&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;a href=&quot;https://www.oracle.com/java/technologies/jspt.html&quot;&gt;https://www.oracle.com/java/technologies/jspt.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Groovy Markup Templates :&amp;nbsp;&lt;a href=&quot;http://groovy-lang.org/templating.html#_the_markuptemplateengine&quot;&gt;http://groovy-lang.org/templating.html#_the_markuptemplateengine&lt;/a&gt;&lt;/li&gt;&lt;li&gt;JSF - Java Server Faces :&amp;nbsp;&lt;a href=&quot;https://www.oracle.com/java/technologies/javaserverfaces.html&quot;&gt;https://www.oracle.com/java/technologies/javaserverfaces.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Es gibt noch viele mehr, hier möchte ich mich aber ausschließlich auf Thymeleaf konzentrieren.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;HTML, CSS und JavaScript Dateien richtig ablegen&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;In unserem Spring Boot Thymeleaf Projekt gibt es Standard-Verzeichnisse für HTML-, CSS- und JavaScript-Dateien. Diese nutzen wir hier auch, um das Tutorial einfach zu halten.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Im &lt;i&gt;src/main/resources&lt;/i&gt; Verzeichnis, wo sich auch die Spring &lt;i&gt;application.properties&lt;/i&gt; Datei befindet, erstellen wir 2 Unterordner: &lt;i&gt;static &lt;/i&gt;und &lt;i&gt;templates&lt;/i&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;i style=&quot;font-weight: bold;&quot;&gt;templates&lt;/i&gt;&amp;nbsp;ist der Standard Ordner für alle Thymeleaf Templates, die in Form von html Dateien gespeichert werden.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;i&gt;&lt;b&gt;static &lt;/b&gt;&lt;/i&gt;wird für statische Dateien, wie JavaScript- und CSS-Dateien verwendet. Es empfiehlt sich mit weiteren Unterverzeichnissen eine Ordnung zu schaffen. Ich habe daher hier die beiden Unterverzeichnisse &lt;i&gt;js &lt;/i&gt;für JavaScript-Dateien und &lt;i&gt;css &lt;/i&gt;für Cascading Style Sheets angelegt.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die JavaScript und CSS Dateien sind unabhängig von Thymeleaf und sehen mit anderen View-Technologien genau so aus. Daher gehe ich hier nicht weiter auf diese Dateien ein - ihr könnt sie aber gerne in meinen GitHub Projekt zusammen mit dem anderen Code genauer anschauen:&lt;br /&gt;&lt;a href=&quot;https://github.com/elmar-brauch/thymeleaf&quot;&gt;https://github.com/elmar-brauch/thymeleaf&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsYGcPqiwBvHy0qtwF2GT3iv_qWRinZctBCS0bP1QN_dK3LgCHHJJgW4v33YTnwjg9PPfL3Prcxh5DESuTtwZED-XYQHIZyHuVxHhYv4UMxnxW6cEHX3Ak3RT8amea3uTRRfIod8meRj5q/s262/folders.PNG&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;262&quot; data-original-width=&quot;235&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsYGcPqiwBvHy0qtwF2GT3iv_qWRinZctBCS0bP1QN_dK3LgCHHJJgW4v33YTnwjg9PPfL3Prcxh5DESuTtwZED-XYQHIZyHuVxHhYv4UMxnxW6cEHX3Ak3RT8amea3uTRRfIod8meRj5q/s0/folders.PNG&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Verzeichnis-Struktur Thymeleaf&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Thymeleaf Templates&lt;/h3&gt;&lt;div&gt;Normale html-Dateien könnten auch von Thymeleaf ausgespielt werden, so dass man die html-Dateien auch Schritt für Schritt in Thymeleaf Templates umwandeln kann. In meinem GitHub Projekt findet ihr Thymeleaf Templates, die mehr Funktionen nutzen. Hier im Blog Teil 1 möchte ich es aber auf wenige Elemente beschränken. Im Teil 2, einem künftigen Post, zeige ich dann weitere Thymleaf Funktionen.&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Also hier die vereinfachte Template Datei &quot;&lt;/span&gt;&lt;i style=&quot;font-family: times;&quot;&gt;src/main/resources/templates/item-list.html&lt;/i&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot;:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&amp;lt;!DOCTYPE HTML&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;head&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, &lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;initial-scale=1, shrink-to-fit=no&quot; /&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;title&amp;gt;Spring Boot Thymeleaf Demonstration&amp;lt;/title&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;link rel=&quot;stylesheet&quot; th:href=&quot;@{/css/main.css}&quot; /&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;script type=&quot;text/javascript&quot; th:src=&quot;@{/js/main.js}&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;/head&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;body&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;main role=&quot;main&quot; class=&quot;container&quot;&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;div class=&quot;starter-template&quot;&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;h1&amp;gt;Ding Sammlung&amp;lt;/h1&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;p th:if=&quot;${#lists.isEmpty(items)}&quot;&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;Ding Sammlung ist leer...&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/p&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;ol th:unless=&quot;${#lists.isEmpty(items)}&quot;&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&amp;lt;li th:each=&quot;item : ${items}&quot; th:text=&quot;${item.name}&quot;&amp;gt;&amp;lt;/li&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/ol&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;h2&amp;gt;&amp;lt;a th:href=&quot;@{/item-create}&quot;&amp;gt;Erstelle Ding&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/div&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/main&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;/body&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;/html&amp;gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Für Infos zu html verweise ich auf&amp;nbsp;&lt;a href=&quot;https://wiki.selfhtml.org/wiki/HTML&quot;&gt;https://wiki.selfhtml.org/wiki/HTML&lt;/a&gt;.&lt;br /&gt;Die Thymeleaf Ausdrücke erkennt man hier am Prefix &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot;:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;xmlns:th=&quot;http://www.thymeleaf.org&quot;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist die Definition des XML Namespaces für Thymeleaf. Das wird benötigt, damit die folgenden Thymeleaf Ausdrücke als solche erkannt werden.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:href=&quot;@{/css/main.css}&quot;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; wird hier eine Hyper-Referenz (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) auf die verwendete CSS Datei &lt;i&gt;main.css&lt;/i&gt; gemacht. Der Pfad &lt;i&gt;/css/main.css&lt;/i&gt; muss sich im Spring &lt;i&gt;resource &lt;/i&gt;Folder befinden. Links zu URLs oder Dateien werden immer mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@{...} &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;definiert.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:src=&quot;@{/js/main.js}&quot;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; referenziert hier die JavaScript Quellen-Datei (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) &quot;&lt;i&gt;/js/main.js&lt;/i&gt;&quot;. Es funktioniert analog zur &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:href&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Hyper-Referenz.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;a th:href=&quot;@{/item-create}&quot;&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:href&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; kann auch für Links (in a-Tags) zu anderen Views genutzt werden, die dann wieder über den Spring Controller ausgespielt werden, siehe &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ItemController&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Klasse &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;showCreateItem&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Methode.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:if=&quot;${#lists.isEmpty(items)}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;ist ein Thymeleaf &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;-Ausdruck zum Ein- oder Ausblenden des zugehörigen Tags. Die Erklärung dieses Ausdrucks findet ihr bereits im ersten Abschnitt dieses Blog-Posts&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. &lt;br /&gt;Hier möchte ich noch mal darauf eingehen, wo die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;items &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Liste her kommt. Schauen wir uns noch mal unseren Controller an. Dort sehen wir, dass eine Liste mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Objekten zum Modell per Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;addAttribute &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;hinzugefügt wurde. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;addAttribute &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;verwendet als Schlüssel den Wert &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot;, daher können wir in Thymeleaf mit&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;items &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;auf den Wert im Modell, also die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;List&amp;lt;Item&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Instanz zugreifen.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:unless=&quot;${#lists.isEmpty(items)}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;bzw. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:unless&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist die negierte Form vom vorherigen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:if.&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Es entspricht also exakt diesen Ausdruck:&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:if=&quot;${not #lists.isEmpty(items)}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:each=&quot;item : ${items}&quot;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist eine for-each Schleife, wie wir sie auch aus Java kennen. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;${items}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; greift auf die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;List&amp;lt;Item&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Instanz in unserem Modell zu. Mit&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:each&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; iteriert Thymeleaf über alle Element in der Liste. Innerhalb der Tags (im Beispiel&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) im Schleifen Tag (im Beispiel&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) können wir mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;${item}&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; auf die einzelnen Listenelemente zugreifen.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;th:text=&quot;${item.name}&quot;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;wird verwendet um einen Text in einem Tag auszuspielen. Der Text wird aus der Variablen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;item &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;bzw. deren Attribute &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;name &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;ausgelesen.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Im Browser sieht die Seite dann so aus, wenn man &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt; aufruft und ja, das css für die Seite gewinnt keinen Schönheitswettbewerb 🙈&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrb2WBAfOT7ri16l7nv9d7KynGXRdnrT2LeTgqrMeGOifWzXt3JcT-9C0i_flN_Z_jmXY69NL3hTfrKSG21Cdkz5kRe7TnjojC9U6_YUGxwJEMsd5pnkvnr9_4gZRJx0pAEHDYTUsfMvGp/s946/list_page.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;679&quot; data-original-width=&quot;946&quot; height=&quot;460&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrb2WBAfOT7ri16l7nv9d7KynGXRdnrT2LeTgqrMeGOifWzXt3JcT-9C0i_flN_Z_jmXY69NL3hTfrKSG21Cdkz5kRe7TnjojC9U6_YUGxwJEMsd5pnkvnr9_4gZRJx0pAEHDYTUsfMvGp/w640-h460/list_page.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit &amp;amp; Ausblick&lt;/h2&gt;&lt;p&gt;In diesem Blog-Post haben wir gesehen, wie man mit Spring MVC Web-Applikationen bauen kann. Die html Seiten haben wir mit Thymeleaf Templates erstellt. Variable Elemente wurden vom Controller ins Modell geschrieben und im html-Template mit Thymeleaf Ausdrücken ausgelesen.&lt;/p&gt;&lt;p&gt;Im &lt;a href=&quot;https://agile-coding.blogspot.com/2020/11/thymeleaf-2.html&quot;&gt;2. Teil zu Thymeleaf&lt;/a&gt; stelle ich folgende Aspekte vor:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Schreiben ins Modell bzw. html Formulare&lt;/li&gt;&lt;li&gt;JavaScript in Thymeleaf Templates&lt;/li&gt;&lt;li&gt;Iternationalisierung (i18n) bzw. mehrere Sprachen mit Spring&lt;/li&gt;&lt;li&gt;Wiederverwendung von Thymeleaf Templates - z.B. dasselbe html head-Tag in mehreren Seiten anzeigen&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Den kompletten Code inklusive dem Code für Teil 2 findet ihr hier in GitHub:&lt;br /&gt;&lt;a href=&quot;https://github.com/elmar-brauch/thymeleaf&quot;&gt;https://github.com/elmar-brauch/thymeleaf&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Weiterführende Informationen zu Thymeleaf findet ihr hier:&lt;br /&gt;&lt;a href=&quot;https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html&quot;&gt;https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIR3M591fSIKR-iN6aMf5n_8U1YOcTkDdgyzNatTgIqLObtbfvnBYtilpj-eusZPCIvTzbsbNm5Bbua-Oq9QKEwKd9ibAiCiKFu-jsrupMoQ6zwsY86khptsLJET7PWNfUg7M6kDE0L0Cn/s72-w640-h308-c/mvc.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-4134953032943625605</guid><pubDate>Fri, 11 Aug 2023 15:54:00 +0000</pubDate><atom:updated>2023-08-12T20:59:37.499+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">agile</category><category domain="http://www.blogger.com/atom/ns#">Clean Code</category><category domain="http://www.blogger.com/atom/ns#">Microservice</category><title>Performanz Tuning: Strategien und Tipps aus der Praxis</title><description>&lt;p&gt;&lt;b&gt;Performanz-Problemen sind meist schwierig zu lösen. Hier zeige ich euch, wie wir unser letzten Problem analysiert, gemessen und gelöst haben. Unter dem Zeitdruck des bevorstehenden Go-Lives wählten wir Strategien, um Risiken durch Seiteneffekte zu vermeiden.&lt;/b&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZzduLXqOUxCluYu8s3V9tdygtqC8yz6N4pUgqHsOPQv4jlL4x-jL0VksOPxPcRMmjXZ4MkbkJFfzVQufGJQ05g1AQWBK3DRSeRCtYrSrecpati0861mpZ46E-nw-O0Oj-QtLVt6YU9VLUnaspn3e_VwERxAe-hczw4lluCyGRTclHEk6ObFcjPHdKlLyU/s1280/snail-48182_1280.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;701&quot; data-original-width=&quot;1280&quot; height=&quot;219&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZzduLXqOUxCluYu8s3V9tdygtqC8yz6N4pUgqHsOPQv4jlL4x-jL0VksOPxPcRMmjXZ4MkbkJFfzVQufGJQ05g1AQWBK3DRSeRCtYrSrecpati0861mpZ46E-nw-O0Oj-QtLVt6YU9VLUnaspn3e_VwERxAe-hczw4lluCyGRTclHEk6ObFcjPHdKlLyU/w400-h219/snail-48182_1280.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Analyse von Performanz-Problemen&lt;/h2&gt;&lt;div&gt;Aus Kundensicht ist unser System eine Browser-Anwendung. Wenn die Seiten im Browser langsam laden, haben wir ein Performanz-Problem. Die Diskussion, was langsam bedeutet und ob langsame Ladezeiten in manchen UseCases akzeptabel sind, ignoriere ich hier. Unsere Situation war eindeutig, statt Ladezeiten von 1-2 Sekunden dauerte es in einigen Fällen mehr als 10 Sekunden.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Logs sind ein guter Startpunkt für die Analyse. Eventuell habt Ihr einen Log-Eintrag am Anfang und Ende der Anfrage. Mit den Zeitstempeln dieser beiden Log-Einträge könnt Ihr die Bearbeitungsdauer der Anfrage überprüfen. Das bestätigt objektiv euer Preformanz-Problem.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Wo treten Performanz-Probleme auf?&lt;/h3&gt;&lt;div&gt;In unserem Fall betraf das Performanz-Probleme nur UseCases die mit der ShoppingCart API zu tun hatten. Damit war klar, wo die genaue Analyse beginnt. Sollten die Probleme überall im Gesamtsystem auftreten, würde ich am ersten System in der Kette mit der Ursachenforschung beginnen.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Wann treten die Performanz-Probleme auf?&lt;/h3&gt;&lt;div&gt;Die Performanz-Probleme traten nicht immer auf. Wir haben dann herausgefunden, dass es einen Zusammenhang zwischen Performanz und Menge der Produkte im ShoppingCart gibt. Damit konnten wir das Problem reproduzieren und zur Lösungsfindung übergehen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die Reproduzierbarkeit ist beim Beheben von Performanz-Problemen sehr wichtig. Das fachliche Verhalten der Anwendung darf sich nicht ändern, muss aber schneller werden. Also testen wir vorher und messen dabei die Performanz. Nach dem Tuning testen wir erneut und messen die Performanz. Blieb das getestete Verhalten gleicht, zeigt&amp;nbsp;der Vergleich der Performanz-Messungen, ob unsere Tuning Maßnahmen erfolgreich sind. Ohne Reproduzierbarkeit können wir nicht beurteilen, ob wir überhaupt an der richtigen Stelle getunt haben.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Arbeit strukturieren &amp;amp; priorisieren&lt;/h2&gt;&lt;div&gt;Performanz-Probleme eskalieren schnell, wenn wichtige UseCases zu langsam oder im schlimmsten Fall nicht benutzbar sind. Geratet auf keinen Fall in Panik und tunt mit allen EntwicklerInnen überall. Die Gefahr von Seiteneffekten oder neuen Bugs, darf beim Tunen nicht unterschätzt werden. Daher müssen Performanz-Tunings genau so strukturiert wie andere Arbeiten ablaufen.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Überlegt bei Bedarf mit dem ganzen Team, was Ursachen und Lösungen sein könnten. Erstellt Tickets bzw. Arbeitspakete für die einzelnen Ideen. Diese priorisiert ihr anschließend, so dass ihr sie nach einander abarbeiten könnt. Damit könnt ihr für jede Maßnahme einzelnen prüfen, ob der gewünschte Tuning-Erfolg eingetreten ist.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Grundlage für unsere Maßnahmen-Priorisierung war der geschätzte Aufwand, das Risiko von Seiteneffekten bzw. Bugs und das Tuning-Potential. Mit hoher Priorität bearbeitet ihr Tickets mit geringem Aufwand und Risiko bei möglichst großem Potential zur Beschleunigung eurer Anwendung. Mehr CPU in euren Server könnte beispielsweise so eine Maßnahme sein (wenn ihr die laufenden Kosten nicht betrachtet). Tickets mit hohem Aufwand und Risiko bei schwer abschätzbarem Tuning Potential sollten am Ende eurer priorisierten Liste stehen. Komplette Technologie-Wechsel wären Beispiele dafür, also beispielsweise Python durch Java ersetzen oder die vorhandene relationale Datenbank durch ein bisher ungenutzte NoSQL Datenbank austauschen.&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Schnell genug? Dann beende das Tuning.&lt;/h3&gt;&lt;div&gt;Wenn das System schnell genug ist, stellt die Tuning-Maßnahmen ein oder bewertet sie neu. Prüft dazu nach jedem erfolgreichen Performanz-Tuning Change im Master Branch, ob euer System schnell genug ist. Ist das der Fall, beendet das Performanz-Tuning, um die Risiken von ungewollten Seiteneffekten zu minimieren. Tunings sind meist komplexe Änderungen. Je mehr Tuning-Maßnahmen ihr bündelt und abliefert, desto größer ist das Risiko versehentlich neue Fehler einzubauen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;EntwicklerInnen sind beim Tuning in ihrem Element und haben meist weitere Verbesserungs-Ideen, die sie gerne ausprobieren wollen. Widersteht diesem Drang. Plant die Arbeit stattdessen im Rahmen eurer normalen Prozesse. Auf diese Weise vermeidet ihr unnötige Risiken zum Erreichen einer Performanz, die im Moment keiner erwartet. Liefert weitere Tunings im Rahmen eurer kontinuierlichen Release-Strategie einzeln aus, so könnt ihr im Fehlerfall leicht zurückrollen.&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Performanz-Tunings implementieren&lt;/h2&gt;&lt;div&gt;Im folgenden erkläre ich unsere Performanz-Tuning Strategien bzw. Regeln, die wir in der Praxis angewendet haben. Es geht dabei um das Allgemeine Vorgehen, welches unabhängig von Programmiersprachen oder Technologie-Stacks ist.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Erst messen, dann tunen!&lt;/b&gt;&lt;/h3&gt;&lt;div&gt;Stellt euch folgendes vor:&lt;/div&gt;&lt;div&gt;Beim Lesen des Codes findet Ihr eine Methode, die eine Liste von Objekten durchsucht. Ihr erkennt direkt, dass die Suche einen linearen Aufwand hat. Deshalb baut ihr die Suche so um, dass der Aufwand konstant ist, indem ihr mit Indexen sucht. Danach liefert ihr die beschleunigte Software aus. Für den Kunden bzw. Benutzer ist aber alles genau so langsam wie vorher. Warum?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In diesem Beispiel wurde die Regel &quot;Erst messen, dann tunen&quot; ignoriert. Beim Messen hättet ihr feststellen können, dass die Methode bei der Gesamtverarbeitungsdauer nicht ins Gewicht fällt, da die meiste Zeit z. B. beim Interagieren mit der Datenbank verbraucht wird.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Zum Messen könnt Ihr nach Performanz-Plugins für eurer IDE suchen. Eine einfache Alternative sind zusätzliche Log-Zeilen in eurem Code, z. B. so:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public List&amp;lt;Price&amp;gt; findPrices(List&amp;lt;String&amp;gt; ids) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;var result = new ArrayList&amp;lt;Price&amp;gt;();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;doOtherCrazyStuff();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;for (var id : ids) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;var before = System.currentTimeMillis();&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;result.add(requestPriceAtPcm(id));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;log.debug(&quot;requestPriceAtPcm in ms: &quot;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;+ (System.currentTimeMillis() - before));&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;doMoreCrazyStuff();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return result;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hier hatte ich die Vermutung, dass die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;requestPriceAtPcm &lt;/span&gt;zu häufig aufgerufen wird und zu lange dauert. Daher messe ich die Zeit und logge sie für jeden Aufruf. Um das konkrete Tuning &quot;Beschleunigen der Methode&quot; oder &quot;Parallelisieren der for-Schleife&quot; geht es hier noch nicht. Mit der Messung finde ich zuerst heraus, ob Tunings an dieser Stelle überhaupt lohnen. Lohnen sie nicht, wie beim Anfangsbeispiel mit dem linearen Aufwand einer Suche, so stelle ich dieses Tuning zurück und schaue mir andere Stellen mit Tuning-Potential an.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Nach dem Tunen erneut messen&lt;/h3&gt;&lt;div&gt;Euer Performanz-Tuning ist nur dann erfolgreich, wenn die Logik anschließend messbar schneller ist. Wie zuvor erwähnt sind Tunings häufig riskante Anpassungen am Code. Unter Zeitdruck sollten sie nur dann ausgeliefert werden, wenn sich die Anpassung wirklich lohnt. Dazu müsst Ihr nach dem Tuning erneut messen, ob die Anwendung wirklich schneller geworden ist.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ist die Anwendung nicht schneller, solltet ihr eure Code-Änderungen zurückstellen bzw. nicht im Master-Branch committen. Denn an je mehr Tuning-Schrauben ihr gleichzeitig dreht, desto schwieriger erkennt ihr was wirklich hilft. Außerdem steigt mit jeder Änderung die Wahrscheinlichkeit für neue Seiteneffekte bzw. Bugs.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Tune immer nur eine Stelle&lt;/h3&gt;&lt;div&gt;Diese Überschrift bzw. Regel passt zur vorherigen Beschreibung. Hier ein anderes Beispiel:&lt;/div&gt;&lt;div&gt;Euer System besteht aus mehreren Microservices und es ist zu langsam. Jetzt könntet ihr gleichzeitig an Tunings an allen Microservices arbeiten. Das kann funktionieren, es kann aber auch schief gehen. Gegebenenfalls wird euer System schneller, ihr wisst aber nicht an welchem Microservice, die Tunings erfolgreich waren, da an allen optimiert wurde. Mit gutem Logging erkennt ihr es vermutlich doch, aber die Arbeitsweise war nicht effizient.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Im Idealfall loggt jeder Microservice die Verarbeitungsdauer eingehender Requests. Diese Logs zeigen dann, in welchem Microservice das Gesamt-System die meiste Zeit benötigt. Auf diese Microservices fokussiert ihr euch zuerst - hier gibt es das größte Tuning-Potential.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Keine Anfrage ist die schnellste Anfrage&lt;/h3&gt;&lt;div&gt;Beispiel: Euer Server hat eine häufig benutzte Methode, ihr tunt sie von zwei Sekunden auf eine Sekunde. Gute Arbeit - hilft aber nichts, weil der Benutzer immer noch lange warten muss. Woran kann es liegen?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Zum Server gibt es immer einen Client. Wenn der Client 10 unnötige Anfragen stellt, kann Server-seitiges Tuning das Problem nicht lösen. Schaut euch auch den Client kritisch an:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Ist die Anfrage an dieser Stelle wirklich nötig?&amp;nbsp;&lt;/li&gt;&lt;li&gt;Können mehrere Anfragen zu einer zusammengefasst werden?&amp;nbsp;&lt;/li&gt;&lt;li&gt;Könnte ein Client-seitiger Cache eingebaut werden?&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;Die schnellste Antwort bekommt die Anfrage, die nicht abgeschickt wurde.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Dazu ein Beispiel aus meiner Praxis: Für jede Darstellung des Einkaufwagen im Online-Shopping-Portal, wurden die Preise aller Produkte im Einkaufwagen neu abgefragt. Diese Logik wurde gebaut, um dem Kunden stets die aktuellen Preise zu zeigen. Je mehr Produkte im Einkaufswagen waren, desto länger musste der Kunde warten bis alle Preise abgefragt waren.&lt;/div&gt;&lt;div&gt;&amp;nbsp; &lt;br /&gt;Funfact: Die Preise ändern sich nur einmal am Tag und zwar jeden Morgen um 8 Uhr. Unsere performantere Lösung für dieses Problem war ein Preis-Cache einzubauen (Mehr Infos zu Caches, siehe &lt;a href=&quot;https://agile-coding.blogspot.com/2022/03/cache.html&quot;&gt;cache.html&lt;/a&gt;). Dieser Cache wird jeden morgen um 8 Uhr geleert und neu befüllt. So muss kein Kunde mehr auf das Laden der aktuellen Preise warten - außer dem Kunden der genau um 8 Uhr morgens kommt...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Durch den Cache schicken nur noch zur einmaligen täglichen Befüllung des Caches Anfragen an die Preis API. Für die meisten Kunden ist damit das Abfragen der Preise nicht mehr nötig, da die Preis-Daten schon im Cache sind. Wir sparen also nicht nur die Preis-Anfragen ein, sondern eliminieren damit auch für den Kunden die Wartezeit auf die Preise und haben ein messbares Performanz-Tuning implementiert.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Verwende realistische Testdaten während der Entwicklung.&lt;/h3&gt;&lt;div&gt;Ein- und Ausgabedaten beeinflussen die Performanz. Iterieren wir beispielsweise über eine kleine Datenmenge, fällt die Verarbeitungsdauer der einzelnen Datenelemente kaum ins Gewicht. Ein inperformanter Algorithmus innerhalb der Schleife, kann hinsichtlich der Gesamtdauer für kleine Datenmengen akzeptabel sein. Bei großen Datenmengen beeinflusst der inperformante Algorithmus in der Schleife die Gesamtdauer aber erheblich.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unrealistisch kleine Testdatenmengen waren in meinem Fall die Ursache des spät entdeckten Performanz-Problems. Realistische Testdaten der Clients unserer ShoppingCart API erhielten wir leider sehr spät, so dass wir kurz vor dem Go-Live des Gesamtsystems tunen mussten. Mit realistischen, großen Datenmengen zu Beginn der Entwicklung erkennen wir Performanz-Probleme direkt (Eventuell entstehen sie gar nicht, da direkt passende Lösungen implementiert werden). Bearbeiten wir Performanz-Probleme in frühen Entwicklungsphasen mit weniger Zeitdruck, lösen wir auch die Auswirkungen von Seiteneffekten entspannter. Dennoch empfehle ich die hier gezeigten Regeln zum disziplinierten Performanz-Tuning - es ist auch ohne Zeitdruck schwierig genug.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Mit den hier gezeigten Performanz-Tuning Strategien schafften wir einen erfolgreichen Go-Live unseres neuen Shopping-Portals.&amp;nbsp;Dazu lieferte mein Team eine performante ShoppingCart API.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Als Zusammenfassung meine Tuning-Regeln:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Erst messen, dann tunen!&lt;/li&gt;&lt;li&gt;Nach dem Tunen erneut messen&lt;/li&gt;&lt;li&gt;Tune immer nur eine Stelle&lt;/li&gt;&lt;li&gt;Keine Anfrage ist die schnellste Anfrage&lt;/li&gt;&lt;li&gt;Verwende realistische Testdaten während der Entwicklung&lt;/li&gt;&lt;li&gt;Schnell genug? Dann beende das Tuning.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/08/performance-tuning.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZzduLXqOUxCluYu8s3V9tdygtqC8yz6N4pUgqHsOPQv4jlL4x-jL0VksOPxPcRMmjXZ4MkbkJFfzVQufGJQ05g1AQWBK3DRSeRCtYrSrecpati0861mpZ46E-nw-O0Oj-QtLVt6YU9VLUnaspn3e_VwERxAe-hczw4lluCyGRTclHEk6ObFcjPHdKlLyU/s72-w400-h219-c/snail-48182_1280.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-5808730053124940164</guid><pubDate>Sat, 01 Jul 2023 13:15:00 +0000</pubDate><atom:updated>2023-07-01T15:15:11.466+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">reactive</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Reaktive REST-Webservices mit Spring WebFlux</title><description>&lt;p&gt;&lt;b&gt;Mit Spring WebFlux entwickeln wir deutlich performanterer Web-Anwendungen und REST-Services. WebFlux ist Teil vom Spring Reactor Projekt. Es ist die moderne, reaktive Alternative zu Spring MVC. In diesem Artikel baue ich mit Mono und Flux eine reaktive API.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Unterschied zwischen WebFlux und Spring MVC&lt;/h2&gt;&lt;div&gt;&lt;p&gt;Das Reactor Projekt bildet die Grundlage des reaktiven Stacks in Spring. Es bietet eine Event-basierte, nicht blockierende Architektur, so dass darauf aufbauende Anwendungen mehr Leistung aus ihren CPU-Ressourcen herausholen.&lt;/p&gt;&lt;p&gt;Spring WebFlux ist Teil des reaktiven Stacks. Es ist das reaktive Gegenstück zu Spring MVC im klassischen Servlet Stack.&lt;br /&gt;Weitere Infos zu Spring MVC findet ihr in meinem Blog:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html&quot;&gt;spring-mvc-thymeleaf.html&lt;/a&gt;&lt;br /&gt;Weitere Details zum Spring Reactor Projekt findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://spring.io/reactive&quot;&gt;https://spring.io/reactive&lt;/a&gt;&lt;br /&gt;Von dort stammt die folgende Gegenüberstellung zur besseren Einordnung der einzelnen Komponenten aus dem Servlet und reaktivem Stack.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/s1039/spring_reactive_servlet.PNG&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;787&quot; data-original-width=&quot;1039&quot; height=&quot;484&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/w640-h484/spring_reactive_servlet.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Gegenüberstellung Reactive und Servlet Stack&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Im ersten Blog-Artikel zur reaktiven Programmierung stellte ich den Spring WebClient vor und zeigte, dass dieser deutlich performanter ist als der klassische HTTP Client:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/01/reactive-webclient.html&quot;&gt;reactive-webclient.html&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hier zeige ich die Vorteile der reaktiven Programmierung anhand einer mit Spring WebFlux geschriebene Serverseite. Konkret entwickle ich eine REST-API und schicke Mono und Flux als Antwort an einen aufrufenden Spring WebClient.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/TaJKHpfQAxk&quot; width=&quot;481&quot; youtube-src-id=&quot;TaJKHpfQAxk&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;i&gt;Video zur Umstellung von Spring MVC auf WebFlux&lt;/i&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Design Pattern: Publisher-Subscriber&lt;/h3&gt;&lt;div&gt;Die Kommunikation zwischen Client und Server funktioniert in Spring WebFlux nach dem Publisher und Subscriber Entwurfsmuster. Serverseitig agiert Spring WebFlux als Publisher und publiziert Daten als Mono oder Flux, sobald diese verfügbar sind. Clientseitig meldet sich der Spring WebClient beim Publisher an (subscribe) und wartet dann in einem separaten Thread auf die Daten der Server-Antwort. Der Haupt-Thread auf Client-Seite kann ist nicht blockiert und läuft weiter, weil das Warten in einem anderen Clientseitigen Thread geschieht. Dadurch haben wir eine Nachrichten-basierte, nicht blockierende Kommunikation, so wie in der reaktiven Programmierung benötigt. Weitere Details zum Design Pattern findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern&quot;&gt;Wikipedia_Publish_Subscribe&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Was ist ein Mono?&lt;/h3&gt;&lt;div&gt;Wenn die Serverseite mit Spring WebFlux einen oder keinen Datensatz als Ergebnis liefert, dann verwenden wir als Antworttyp Mono. Dazu ein Beispiel aus der analogen Welt:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Im Restaurant bestellt der Gast (Clientseite) ein Hauptgericht (Mono). Die Küche (Serverseite) fängt nun an das Gericht zu kochen. Sie bringt es dem Gast auf einem Teller (Mono), wenn es fertig ist. In der Zwischenzeit kann der Gast noch andere Dinge tun: Trinken, Reden, Handy spielen usw. - der Gast ist also nicht blockiert (reaktiv), während er auf die Nachricht Hauptgang ist fertig wartet.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Was ist ein Flux?&lt;/h3&gt;&lt;div&gt;Liefert die Serverseite eine beliebig große Menge an Datensätzen als Ergebnis, so ist der Antworttyp ein Flux. Ein Flux ist ein Datenstrom der vom Server zum Client fließt. Die Flussgeschwindigkeit kann sich dabei beliebig ändern. Dazu wieder ein Beispiel aus der analogen Gastronomie:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Im Sterne-Restaurant kocht die Küche (Serverseite) ein 9 Gänge Menu (Flux) für die Gäste (Clientseite). Dabei kommen die einzelnen Gänge getrennt von einander beim Gast an, manche Gänge kommen schneller hintereinander andere brauchen etwas länger (Datenstrom). Wie schon beim Mono-Beispiel ist der Gast im Restaurant nicht blockiert bzw. reaktiv, da er sich in der Zwischenzeit beliebig beschäftigen kann.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Mono als Antwort einer REST-API&lt;/h2&gt;&lt;div&gt;Eine reaktive REST-API schickt den Datensatz nach dem Publisher-Subscriber Prinzip an den Client. Wenn es höchstens ein Datensatz ist, verwenden wir serverseitig einen Mono. Im folgenden wird ein Spring &lt;span style=&quot;font-family: courier;&quot;&gt;RestController &lt;/span&gt;gezeigt, der ein Mapping für eine GET Methode hat:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RestController&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class ReactiveEmployeeController {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@ResponseStatus(HttpStatus.OK)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@GetMapping(&quot;/employee/mono&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public &lt;b&gt;Mono&amp;lt;Employee&amp;gt;&lt;/b&gt; receiveMono() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono&amp;lt;Employee&amp;gt; result = &lt;b&gt;Mono.fromSupplier&lt;/b&gt;(&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;() -&amp;gt; generateEmployee())&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.&lt;b&gt;doOnSuccess&lt;/b&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;employee -&amp;gt; log.info(&quot;Mono published: &quot; + employee));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.info(&quot;Returning Mono.&quot;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return result;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RestController&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;erkläre ich hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/rest-json-apis-in-java-leicht-gemacht.html&quot;&gt;rest-apis-in-java.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Die Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;generateEmployee&amp;nbsp;&lt;/span&gt;ist ausgeblendet, da sie lediglich eine &lt;span style=&quot;font-family: courier;&quot;&gt;Employee &lt;/span&gt;Instanz erstellt. In meiner Demo wartet die Methode einige Sekunden bevor sie als Ergebnis eine&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee &lt;/span&gt;Instanz liefert. Das Warten simuliert eine lang dauernde Suche nach einem Employee in einer großen Datenbank. Je länger die Such- oder Rechenoperationen zum Beschaffen des Ergebnisses dauern und je mehr Anfragen ankommen, desto mehr profitieren wir hinsichtlich Performance von der reaktiven Programmierung.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono&amp;lt;Employee&amp;gt;&amp;nbsp;&lt;/span&gt;ist der Rückgabetyp unserer GET Methode.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Den HTTP Response-Code definiere ich mit der Annotation&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;@ResponseStatus(HttpStatus.OK)&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;return&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono.fromSupplier&lt;/span&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&lt;/b&gt;antwortet dem Client direkt, schickt aber noch nicht den eigentlichen Datensatz. Der Parameter der Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;fromSupplier&lt;/span&gt;&amp;nbsp;ist ein Lambda Ausdruck bzw. ein &lt;span style=&quot;font-family: courier;&quot;&gt;Supplier&lt;/span&gt;. Der &lt;span style=&quot;font-family: courier;&quot;&gt;Supplier&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; berechnet den eigentlichen Datensatz (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) und publiziert ihn dann durch den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;an den Client.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;doOnSuccess &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;hat als Parameter einen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Consumer&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, den ich hier nur zum Loggen verwende. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;doOnSuccess &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird ausgeführt, wenn der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;erfolgreich seinen Datensatz publiziert hat.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Supplier&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Consumer&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;sind&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;funktionale Interfaces, die in Java 8 eingeführt wurden.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Wie man clientseitig mit dem reaktiven Spring &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; einen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;abfragt und einen Lambda Ausdruck als &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Consumer&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;zur Verarbeitung des Datensatzes registriert (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;), zeige ich in&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/01/reactive-webclient.html&quot;&gt;reactive-webclient.html&lt;/a&gt;.&amp;nbsp;&lt;span style=&quot;font-family: times;&quot;&gt;Daher hier ohne weitere Erklärungen der Code der Clientseite:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient&amp;nbsp;reactiveClient = WebClient.builder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.baseUrl(&quot;http://localhost:8080&quot;).build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;reactiveClient.get().uri(&quot;/employee/mono&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.retrieve().bodyToMono(Employee.class)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.subscribe(employee -&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;log.info(&quot;Response received: &quot; + employee);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;log.info(&quot;Request send. &quot;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;+&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;Data published in Mono will be handled in another thread.&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Folgende 4 Log-Einträge werden vom Client und Server durch den Logger (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;log.info&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) in dieser Reihenfolge geschrieben:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-21 21:46:&lt;b&gt;12&lt;/b&gt;.255&amp;nbsp; INFO [&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;main] Client&amp;nbsp; &amp;nbsp;: Request send. Data published in Mono will be handled in another thread.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-21 21:46:&lt;b&gt;12&lt;/b&gt;.492&amp;nbsp; INFO [ctor-http-nio-4] ReactiveEmployeeController&amp;nbsp; &amp;nbsp; &amp;nbsp;: Returning Mono.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-21 21:46:&lt;b&gt;14&lt;/b&gt;.515&amp;nbsp; INFO [ctor-http-nio-4] ReactiveEmployeeController&amp;nbsp; &amp;nbsp; &amp;nbsp;: Mono published: Employee(id=-772...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-21 21:46:&lt;b&gt;14&lt;/b&gt;.654&amp;nbsp; INFO [ctor-http-nio-3] Client&amp;nbsp; &amp;nbsp;: Response received: Employee(id=-772...)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Beachtet die verschiedenen clientseitig eingesetzten Threads:&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;main&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;und&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ctor-http-nio-3&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Aufgrund der geringen Last wurde serverseitig nur ein Thread&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ctor-http-nio-4&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;verwendet, das können bei höherer Last mehr sein. Die Reihenfolge der Logeinträge zeigt, dass die Threads weder client- noch serverseitig blockiert wurde. Bei einer synchronen, blockierenden Kommunikation wären, die ersten beiden Log-Einträge am Ende:&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Clientseitig, weil der Log-Eintrag &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Request send...&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot; erst nach erhaltener Antwort vom Server geschrieben wird - vorher ist der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;main &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Thread blockiert.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Serverseitig, weil zuerst das Ergebnis (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz) berechnet wird und erst danach &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Returning...&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot; geloggt werden würde. (Inhaltlich machen die geloggten Texte bei einer synchronen Kommunikation natürlich keinen Sinn.)&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Flux als Antwort einer REST-API&lt;/h2&gt;&lt;div&gt;Wie zuvor erwähnt ist der Unterschied zwischen Mono und Flux die Anzahl der Datensätze, welche der reaktive Server publiziert. Beim Flux können es beliebig viele sein, es ist also eine Art Stream. Im folgenden Beispiel werde ich serverseitig die Datensätze im Flux aus einem Stream beziehen. Das &lt;span style=&quot;font-family: courier;&quot;&gt;Stream &lt;/span&gt;Interface wurde in Java 8 eingeführt und wird zum Beispiel hier vorgestellt:&amp;nbsp;&lt;a href=&quot;https://ertan-toker.de/java-streams-tutorial-and-examples/&quot;&gt;https://ertan-toker.de/java-streams-tutorial-and-examples/&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@ResponseStatus(HttpStatus.OK)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping(path =&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;/employee/flux&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;produces = MediaType.APPLICATION_NDJSON_VALUE&lt;/b&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public &lt;b&gt;Flux&amp;lt;Employee&amp;gt;&lt;/b&gt; receiveFlux() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Supplier&amp;lt;Employee&amp;gt; supplier = () -&amp;gt; generateEmployee();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return &lt;b&gt;Flux.fromStream&lt;/b&gt;(Stream.generate(supplier))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;.doOnNext&lt;/b&gt;(employee -&amp;gt; log.info(&quot;Flux emits: &quot; + employee));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Flux&amp;lt;Employee&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist der Rückgabetyp unserer Methode. Damit der Client erkennt, dass die Antwort ein reaktiver Datenstrom (Flux) ist, muss der Content Type für unsere HTTP Response entsprechend als &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;application/x-ndjson&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&quot; definiert&amp;nbsp;werden. Das mache ich in der Annotation &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; mit:&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;produces = MediaType.APPLICATION_NDJSON_VALUE&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Ansonsten habe ich &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;@ResponseStatus&lt;/span&gt;&amp;nbsp;bereits im Mono Beispiel weiter oben erklärt.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Supplier&amp;lt;Employee&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Instanz ist ein Lambda Ausdruck, der die Methode&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;generateEmployee &lt;/span&gt;aufruft. Diese Methode wird auch im Mono Beispiel verwendet und erklärt.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Stream.generate&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;erstellt mit Hilfe der&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Supplier&amp;lt;Employee&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Instanz einen Datenstrom vom Typ &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Stream&amp;lt;Employee&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Supplier &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz befüllt also kontinuierlich den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Stream &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;mit neu generierten &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Objekten bzw. Daten. Dieses Beispiel ist ein endloser Datenstrom. In der Praxis könnte ein Stream aus einer endlichen Liste erstellt werden oder eine reaktive Datenbank-Abfrage, die mit längerer Rechenzeit immer mehr Ergebnisse liefert. Die reaktive Datenbank-Abfrage stelle ich hier vor:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/04/spring-data-reactive.html&quot;&gt;spring-data-reactive.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Flux.fromStream&lt;/span&gt; ist eine von vielen Möglichkeit eine Flux Instanz zu erstellen. Schaut euch einfach die abstrakte Klasse &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Flux &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;im Quellcode an, um weitere Möglichkeiten zu sehen.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Analog zu Mono (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.doOnSuccess&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) gibt es auch beim Flux diverse Methode, um in den reaktiven Datenstrom einzugreifen. Mit&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.doOnNext&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;greift Ihr jedes im Flux publizierte Objekt zu - ich logge hier einfach nur das publizierte Objekt.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die Clientseite sieht beim Flux vergleichbar zum Mono aus:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;Flux&amp;lt;Employee&amp;gt; employees = reactiveClient.get().uri(&quot;/employee/flux&quot;)&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.retrieve().&lt;b&gt;bodyToFlux&lt;/b&gt;(Employee.class);&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;employees.subscribe(employee -&amp;gt; {&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;log.info(&quot;Part of response received: &quot; + employee);&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;});&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;reactiveClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ist dieselbe Instanz von &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, die zuvor im Mono Beispiel verwendet wurde.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;bodyToFlux &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird anstatt &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;bodyToMono &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;verwendet und legt fest, dass die Antwort des Servers ein Datenstrom ist.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Jedes vom Server durch den Flux publizierte &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Objekt wird in einem &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Consumer &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;verarbeitet. Der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Consumer &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;bindet sich mit der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;subscribe &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Methode&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;als Lambda Funktion an den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Flux&amp;lt;Employee&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Hier ein kleiner Ausschnitt aus den client- und serverseitigen Logs:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:31.258 INFO [ctor-http-nio-4] &lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveEmployeeController: Flux emits: Employee(id=-835...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:31.447 INFO [ctor-http-nio-3] Client: &lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Parts of response received: Employee(id=-835...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:33.353 INFO [ctor-http-nio-4] &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;ReactiveEmployeeController: Flux emits: Employee(id=-526...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:33.358 INFO [ctor-http-nio-3] Client:&amp;nbsp;&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Parts of response received: Employee(id=-526...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:35.363 INFO [ctor-http-nio-4] &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;ReactiveEmployeeController: Flux emits: Employee(id=494...)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;2023-02-23 21:08:35.367 INFO [ctor-http-nio-3] Client: &lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Parts of response received: Employee(id=494...)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/div&gt;Der Client bzw. ein Thread auf der Clientseite verarbeitet immer dann einen Datensatz, wenn der Datensatz vom Server publiziert wurde. Damit haben wir mit einem HTTP Request einen Datenstrom zwischen Client und Server aufgebaut, der effizient und Nachrichten-basiert in Threads verarbeitet wird. Diese Konstellation ist für mich ein Highlight der reaktiven Programmierung! 🎆&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die langsameren Alternativen in der klassischen Programmierung bzw. bei der Verwendung von synchronen Requests sind:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;langes Warten auf eine riesige Antwort mit allen Datensätzen&lt;/li&gt;&lt;li&gt;oder viele kleine Abfragen (Pagination), um die Datensätze in kleineren Teilmengen abzufragen.&lt;br /&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Ich habe gezeigt, wie man mit reaktiver Programmierung und Spring WebFlux eine Client-Server-Kommunikation nach dem Publisher und Subscriber Entwurfsmuster aufbauen kann. Sowohl die Client- als auch die Serverseite sind nicht blockierend und damit besonders performant auf aktuellen Rechnern mit mehreren Kernen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die Unterschiede zwischen Mono (höchstens ein Datensatz als Antwort) und Flux (beliebig viele Datensätze als Antwort) habe ich anhand von Beispielen erklärt. Wie immer findet ihr den kompletten Code mit JUnit-Tests, die als Clientseite fungieren, in GitHub:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/webflux&quot;&gt;https://github.com/elmar-brauch/webflux&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Weiterführendes zum Einsatz von Datenbank in Spring Reactive findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/04/spring-data-reactive.html&quot; style=&quot;font-family: times;&quot;&gt;spring-data-reactive.html&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2021/02/webflux.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/s72-w640-h484-c/spring_reactive_servlet.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-4188571464546380558</guid><pubDate>Thu, 18 May 2023 13:38:00 +0000</pubDate><atom:updated>2023-05-18T15:38:42.670+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">reactive</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Spring WebClient der reaktive HTTP Client im Performance-Vergleich zum RestTemplate</title><description>&lt;p&gt;&lt;b&gt;Spring Reactive ist der moderne, reaktive Technologie Stack von Spring. Es ist die skalierbare, resiliente, responsive und Event-basierte Alternative zum klassischen Servlet Stack - dem bisherigen Standard in jedem Spring Web Projekt. Teil des reaktiven Stacks ist Spring WebFlux und dessen WebClient zum Verschicken von HTTP Requests. In diesem Artikel zeige ich, dass der WebClient unter Last deutlich schneller als der klassische Spring RestTemplate ist.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Spring WebFlux&lt;/h2&gt;&lt;p&gt;Das Reactor Projekt bildet die Grundlage des reaktiven Stacks in Spring. Es bietet eine Event-basierte, nicht blockierende Architektur, so dass darauf aufbauende Anwendungen mehr Leistung aus ihren CPU-Ressourcen herausholen.&lt;/p&gt;&lt;p&gt;Spring WebFlux ist Teil des reaktiven Stacks und damit das reaktive Gegenstück zu Spring MVC im klassischen Servlet Stack. &lt;br /&gt;Weitere Infos zu Spring MVC findet ihr in meinem Blog: &lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html&quot;&gt;spring-mvc-thymeleaf.html&lt;/a&gt;&lt;br /&gt;Weitere Details zum Spring Reactor Projekt findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://spring.io/reactive&quot;&gt;https://spring.io/reactive&lt;/a&gt;&lt;br /&gt;Von dort stammt auch die Gegenüberstellung von Servlet und reaktivem Stack. Diese hilft die einzelnen Komponenten besser einzuordnen.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/s1039/spring_reactive_servlet.PNG&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;787&quot; data-original-width=&quot;1039&quot; height=&quot;484&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/w640-h484/spring_reactive_servlet.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Gegenüberstellung Reactive und Servlet Stack&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Hier konzentriere ich mich auf den Vergleich der HTTP Clients aus beiden Stacks:&amp;nbsp;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient:&lt;/span&gt;&amp;nbsp;Aus dem reaktiven Stack als Teil der Spring WebFlux Bibliothek&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate:&lt;/span&gt;&amp;nbsp;Aus dem Servlet Stack als Teil der Spring Web Bibliothek&lt;/li&gt;&lt;/ul&gt;Beim reaktiven Programmieren geht es nicht nur um die Verwendung einer anderen Art von HTTP-Kommunikation. Sondern geht es auch darum, dass wir wann immer möglich auf CPU blockierende Operationen verzichten und eine alternative, nicht blockierende Operation verwenden. Daher gibt es auch &lt;a href=&quot;https://agile-coding.blogspot.com/2021/04/spring-data-reactive.html&quot; target=&quot;_blank&quot;&gt;reaktive Repositories&lt;/a&gt; für den nicht blockierenden Zugriff auf Datenbank als Teil von Spring Data. Spring Data in der klassischen Form, habe ich in diesem Blog-Artikel vorgestellt:&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/keine-ahnung-von-mongodb-dann-nimm.html&quot;&gt;mongodb-und-spring-data.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;HTTP Requests mit WebClient und RestTemplate&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;RestTemplate&lt;/h3&gt;&lt;div&gt;HTTP Requests mit dem &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate&lt;/span&gt; kennt Ihr vermutlich schon. Das &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;ist eine elegante und einfache Form um HTTP REST Requests zu verschicken. Im Code sieht das z.B. so aus:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; String responseBody =&amp;nbsp;&lt;/span&gt;new RestTemplate().getForEntity(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;http://someurl.de/something&quot;,&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;String.class)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.getBody();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In diesem Code Beispiel erzeuge ich eine neue Instanz des &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate&lt;/span&gt;, um dann einen HTTP GET Request abzuschicken (&lt;span style=&quot;font-family: courier;&quot;&gt;getForEntity&lt;/span&gt;). In der Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;getForEntity&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;definiere ich die URL und den Klassen-Typ (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String.class&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) in den ich den Body der Response (&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.getBody()&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;) automatisch geparst haben möchte. Statt &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;würde man hier besser fachliche Klassen verwenden, die passend zur Response sind (z.B. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Employee, Customer, Car, Item&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; etc.).&lt;br /&gt;Das &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;kann noch viel mehr, z.B. Header mitschicken, in komplexere Typen als String parsen und unterstützt natürlich auch alle anderen HTTP Methoden (POST, PUT etc.). Weitere Infos zum &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;findet ihr z.B. hier:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/rest-template&quot;&gt;https://www.baeldung.com/rest-template&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDF-3cME999bO7yX2APHK5bDFUAeIisX-FlYSkLxLJtrpSDtLvaRucgD-xQ4X79bvE4BS3XM9bx93zDfyYVZCUHXt3R8X7AZ9_Yg_u6TJBJNSR52yBzVePoOFMg_qkamCOtm1_dpbV-jd6/s630/RestTemplate.png&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;508&quot; data-original-width=&quot;630&quot; height=&quot;517&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDF-3cME999bO7yX2APHK5bDFUAeIisX-FlYSkLxLJtrpSDtLvaRucgD-xQ4X79bvE4BS3XM9bx93zDfyYVZCUHXt3R8X7AZ9_Yg_u6TJBJNSR52yBzVePoOFMg_qkamCOtm1_dpbV-jd6/w640-h517/RestTemplate.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;RestTemplate verschickt 3 synchrone Requests - Haupt-Thread blockiert bis Response ankommt.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;WebClient&lt;/h3&gt;&lt;div&gt;Um den WebClient verwenden zu können, müssen wir die Spring WebFlux Bibliothek zu unserem Projekt hinzufügen. Bei Spring Boot Projekten funktioniert das mit Gradle so:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;implementation &#39;org.springframework.boot:spring-boot-starter-webflux&#39;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Mit Maven funktioniert es analog:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;artifactId&amp;gt;spring-boot-starter-webflux&amp;lt;/artifactId&amp;gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Ansonsten müssen wir nichts weiter tun, um den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;zu nutzen. Mit dem &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;sieht der zuvor mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;gezeigte GET Request so aus:&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient client =&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient.create(&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;http://someurl.de/something&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String responseBody =&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.get().retrieve().toEntity(String.class)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.block().getBody();&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird mittels Builder instanziiert &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;(WebClient.create)&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Dann wird der abzuschickende Request spezifiziert - hier im Beispiel minimal mit der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;get()&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Methode. Ab&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;retrieve()&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; wird festgelegt, wie die erwartete Response aussieht. Mit&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt; toEntity(String.class)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird festgelegt, dass der Body der Response in einen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;geparst werden soll. Der eigentliche HTTP Request wird hier im Beispiel&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;mit der Methode&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;block()&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;blockierend verschickt.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Sobald die HTTP Response angekommen ist, wird der Response-Body als &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Instanz mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;getBody()&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; ausgelesen.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Wenn wir den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;so verwenden, haben wir keinen Vorteil gegenüber dem &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, da der HTTP Request synchron verarbeitet wird. Das Besondere am &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;ist die nicht blockierende, Event-gesteuerte (und damit asynchrone) HTTP Request und Response Verarbeitung. Diese sieht dann so aus:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono&amp;lt;String&amp;gt; responseMono =&amp;nbsp;client.get().retrieve()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.bodyToMono(String.class);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;responseMono.subscribe(responseBodyAsString -&amp;gt; {&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; // Bei erfolgreichen Requests, enthält&amp;nbsp;&lt;/span&gt;&lt;/span&gt;responseBodyAsString&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; //&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;den HTTP Response Body als String und&amp;nbsp;&lt;/div&gt;&lt;/span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;// kann hier im Lambda z.B. so verarbeitet werden:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;System.out.println(responseString);&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;client &lt;/span&gt;ist hier dieselbe &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;Instanz wie im vorherigen Code-Beispiel.&lt;/li&gt;&lt;li&gt;Mit der Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;bodyToMono&lt;/span&gt;&amp;nbsp;legen wir fest, dass als Antwort auf diesen Request ein&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;erwartet wird. Ein &lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;ist ein Datenstrom, der höchstens eine Instanz vom definierten Datentyp (hier &lt;span style=&quot;font-family: courier;&quot;&gt;String.class&lt;/span&gt;) übertragen wird. Die Alternative zum &lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;ist der &lt;span style=&quot;font-family: courier;&quot;&gt;Flux &lt;/span&gt;für Datenströme, die beliebig viele Instanzen des definierten Datentyps enthalten. Würden wir statt &lt;span style=&quot;font-family: courier;&quot;&gt;String&lt;/span&gt;, einen Datentyp wie&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Customer&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;Employee&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;Item&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Car&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; etc. verwenden, &lt;/span&gt;wird klar, dass man besser&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Flux &lt;/span&gt;bei Antworten in Listen-Form verwendet.&lt;br /&gt;In diesem Blog-Artikel werde ich &lt;span style=&quot;font-family: courier;&quot;&gt;Flux &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;Mono &lt;/span&gt;nicht weiter betrachten, obwohl diese Typen sehr wichtig für die Performance in der reaktiven Programmierung sind!&lt;/li&gt;&lt;li&gt;Statt der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;block&lt;/span&gt; verwenden wir beim reaktiven Programmieren die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;subscribe&lt;/span&gt;. &lt;span style=&quot;font-family: courier;&quot;&gt;subscribe &lt;/span&gt;schickt den Request ab und meldet einen Lambda-Ausdruck an, der die Antwort verarbeitet, wenn diese ankommt. Damit ist &lt;span style=&quot;font-family: courier;&quot;&gt;subscribe &lt;/span&gt;eine nicht blockierende Methode und der Thread, der mit der &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;Instanz den Request abgeschickt hat, läuft einfach weiter. Im Lambda Ausdruck findet eine asynchrone Verarbeitung der Response statt. Hier im Beispiel wird der Response Body einfach mit &lt;span style=&quot;font-family: courier;&quot;&gt;System.out&lt;/span&gt; in die Konsole geschrieben. Die Response könnte aber auch in einer Datenbank gespeichert werden oder auf andere sinnvolle Weise verarbeitet werden.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDKw0buM2zlxcwgmshblgH7qex7rCQArd9R2WV9VwNvEnAYXT4Te_zvS1lU1ZY1JJU7k5k4LQ_uPJ4h6Diqk6I9s0SvtwCuLGDxqjCVU0019NVuK40mZSLgZGix0NzCW9qxrx0sjO8ENTD/s732/WebClient.png&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;524&quot; data-original-width=&quot;732&quot; height=&quot;458&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDKw0buM2zlxcwgmshblgH7qex7rCQArd9R2WV9VwNvEnAYXT4Te_zvS1lU1ZY1JJU7k5k4LQ_uPJ4h6Diqk6I9s0SvtwCuLGDxqjCVU0019NVuK40mZSLgZGix0NzCW9qxrx0sjO8ENTD/w640-h458/WebClient.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;WebClient verschickt 3 asynchrone Requests - Haupt-Thread wartet nicht auf die Responses.&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Web-Client mit Proxy&lt;/h3&gt;&lt;div&gt;In Firmennetzwerken müssen meist Proxies für Internet-Verbindungen eingerichtet werden. Das könnt Ihr zum Beispiel so machen:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;reactor.netty.http.client.HttpClient httpClient = HttpClient.create()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.proxy(proxy -&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;proxy&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.host(&quot;localhost&quot;).port(3128)&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.type(Proxy.HTTP).&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;nonProxyHosts(&quot;*.intranet.de&quot;)&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.responseTimeout(Duration.ofSeconds(30));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;			&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient proxiedClient = WebClient.builder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.clientConnector(new ReactorClientHttpConnector(httpClient))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.baseUrl(&quot;http://someurl.needs/proxy&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.build();&lt;/span&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Statt des &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;Default ClientConnector erzeugt ihr einen eigenen basierend auf dem Netty &lt;span style=&quot;font-family: courier;&quot;&gt;HttpClient&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;Im Netty &lt;span style=&quot;font-family: courier;&quot;&gt;HttpClient &lt;/span&gt;wird der Proxy mit &lt;span style=&quot;font-family: courier;&quot;&gt;type&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;host&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;port &lt;/span&gt;und optional &lt;span style=&quot;font-family: courier;&quot;&gt;nonProxyHosts &lt;/span&gt;definiert. Im &lt;span style=&quot;font-family: courier;&quot;&gt;HttpClient &lt;/span&gt;können noch weitere Einstellungen, wie z. B. Timeouts konfiguriert werden.&lt;/li&gt;&lt;li&gt;Ansonsten wurde hier nur der WebClient-Builder anstelle der zuvor gezeigten &lt;span style=&quot;font-family: courier;&quot;&gt;create&amp;nbsp;&lt;/span&gt;Methode verwendet.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Performance-Vergleich WebClient und RestTemplate&lt;/h2&gt;&lt;div&gt;Mich haben die Performance-Unterschiede zwischen &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;interessiert. Dazu habe ich den &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;einfach ausprobiert und die Zeiten gestoppt. Da beim einfachen Ausprobieren die zu erwartenden Ergebnisse schnell sichtbar wurden, habe ich keine wissenschaftlichen Benchmark-Tests durchgeführt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Der &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;setzt auf eine asynchrone, Event-gesteuerte Kommunikation, die eingehende Nachrichten (Responses) erst dann aktiv in Threads verarbeitet, wenn diese wirklich angekommen sind - in der Zwischenzeit wird die CPU dabei nicht blockiert bzw. nicht aufgehalten. &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;kommuniziert im selben Thread synchron und blockiert damit den Thread. Da man heute fast immer mehrere CPUs und Kerne zur Verfügung hat und bei der Kommunikation mit Systemen aufgrund der Übertragungsdauer Wartezeiten entstehen, habe ich vorher schon erwartet, dass der &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;in den meisten Tests schneller ist.&lt;br /&gt;Das zeigt dann auch der Test-Code, den ich zu diesem Artikel geschrieben habe. &lt;br /&gt;Ihr findet den Code hier und könnt damit eigene Performance-Tests machen:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/webflux&quot;&gt;https://github.com/elmar-brauch/webflux&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Requests gegen normale REST Webservices&lt;/h3&gt;&lt;div&gt;In meinem ersten Performance-Vergleich habe ich drei Spring Beans erstellt, die jeweils den gleichen HTTP Request gegen einen REST-Service irgendwo im Internet schicken - wie gesagt ich mache keinen wissenschaftlichen Benchmark-Test. Die drei Beans sind so aufgebaut:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ClassicBackendApiClient &lt;/span&gt;Bean schickt einen GET Request mittels einer&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;Instanz an einen REST-Service im Internet. Das funktioniert genau so wie im vorherigen Abschnitt RestTemplate gezeigt. Die Response wird dann zusammen mit einer Request-Id geloggt. Der Test-Ablauf ist im ersten Sequenz-Diagramm zum &lt;span style=&quot;font-family: courier;&quot;&gt;RestTemplate &lt;/span&gt;gezeigt.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&amp;nbsp;&lt;/span&gt;Bean erstellt zum Verschicken jedes einzelnen REST-Requests einen eigenen Thread mittels &lt;span style=&quot;font-family: courier;&quot;&gt;CompletableFuture.runAsync&lt;/span&gt;. Im Thread wird die &lt;span style=&quot;font-family: courier;&quot;&gt;ClassicBackendApiClient&amp;nbsp;&lt;/span&gt;Bean zum Verschicken und Loggen des Requests wiederverwendet. Im Code sieht das so aus:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Component&lt;br /&gt;public class ThreadedClassicBackendApiClient {&lt;br /&gt;&amp;nbsp; @Autowired ClassicBackendApiClient client;&lt;br /&gt;&lt;br /&gt;&amp;nbsp; public void callApi(final int requestId) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; CompletableFuture.runAsync(() -&amp;gt; client.callApi(requestId));&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;Weitere Infos zum &lt;span style=&quot;font-family: courier;&quot;&gt;CompletableFuture &lt;/span&gt;aus Java 8, findet ihr zum Beispiel in diesem Buch:&lt;br /&gt;&lt;a href=&quot;https://www.amazon.de/gp/product/3836277379/ref=as_li_tl?ie=UTF8&amp;amp;tag=agilecoding-21&amp;amp;camp=1638&amp;amp;creative=6742&amp;amp;linkCode=as2&amp;amp;creativeASIN=3836277379&amp;amp;linkId=652a8639e129272730643783d63035e0&quot; target=&quot;_blank&quot;&gt;Buch: Java ist auch eine Insel (zu Java 14)&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt; Bean verschickt den gleichen REST-Request wie die anderen 2 Beans nur diesmal mit einer &lt;span style=&quot;font-family: courier;&quot;&gt;WebClient &lt;/span&gt;Instanz. Der Code dazu sieht aus wie im vorherigen Abschnitt WebClient gezeigt - dort zeigt auch das zweite Sequenz-Diagramm&amp;nbsp;den Test-Ablauf.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;Der Performance-Vergleich ist in der JUnit 5 Testklasse &lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveVsClassicClientTest&amp;nbsp;&lt;/span&gt;gemacht. Das ist kein richtiger Unit-Test, da ich aber den Anwendungs-Code vom Performance-Test Code trennen wollte, habe ich mich für die Implementierung des Testablaufs und die Zeitmessung in dieser Datei entschieden:&amp;nbsp;&lt;a href=&quot;https://github.com/elmar-brauch/webflux/tree/master/src/test/java/de/bsi/webflux/client&quot;&gt;https://github.com/...&lt;/a&gt;&lt;/div&gt;&lt;div&gt;(Zum Thema JUnit 5 Tests habe ich diesen Artikel geschrieben: &lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/JUnit5andSpringBootTest.html&quot;&gt;JUnit5andSpringBootTest.html&lt;/a&gt;)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In der Testklasse kann man das Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;NUMBER_OF_CALLS&lt;/span&gt;&amp;nbsp;anpassen, um Anzahl der Requests festzulegen, die jede Bean abschicken soll. Das Ergebnis der Zeitmessung wird dann einfach geloggt. Für die beiden Beans&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; und&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;verwende ich Awaitility, um solange zu warten bis alle Threads eine Antwort bekommen haben. Awaitility zum Testen von Threads hatte ich in diesem Blog-Artikel erklärt:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/09/automatisiertes-junit-testing-von.html&quot;&gt;junit-testing-von-threads.html&lt;/a&gt;&lt;br /&gt;Hier noch der Code des JUnit Tests zum Messen der Performance:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Slf4j&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;class ReactiveVsClassicClientTest {&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private static final int NUMBER_OF_CALLS = 50;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void useRestTemplate(@Autowired ClassicBackendApiClient client) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.info(&quot;RestTemplate API calls took {} ms&quot;,&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;measureCalls(client));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void useRestTemplateInThreads(&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;@Autowired ThreadedClassicBackendApiClient client) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.info(&quot;RestTemplate in Thread API calls took {} ms&quot;,&lt;br /&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;measureCalls(client));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void useWebClient(@Autowired ReactiveBackendApiClient client) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.info(&quot;WebClient API calls took {} ms&quot;,&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;measureCalls(clientBean));&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;private long measureCalls(BackendApiClient clientBean) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;/span&gt;long before = System.currentTimeMillis();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;/span&gt;for (int i = 1; i &amp;lt; NUMBER_OF_CALLS + 1; i++)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;clientBean.callApi(i);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;Awaitility.await().until(clientBean::isQueueEmpty);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return System.currentTimeMillis() - before;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Alle Beans implementieren das Interface&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;BackendApiClient&amp;nbsp;&lt;/span&gt;mit der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;callApi&lt;/span&gt;. Daher konnte ich die Logik zum Messen der Zeit und das Abschicken aller Requests in einer &lt;span style=&quot;font-family: courier;&quot;&gt;for&lt;/span&gt;-Schleife in die Hilfs-Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;measureCalls&lt;/span&gt;&amp;nbsp;auslagern.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Slf4j&lt;/span&gt; ist eine Annotation von Lombok, die den Logger im Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;log &lt;/span&gt;bereitstellt. Schaut euch für weitere Details die Webseite von Lombok an:&amp;nbsp;&lt;a href=&quot;https://projectlombok.org/&quot;&gt;https://projectlombok.org/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Wie &lt;span style=&quot;font-family: courier;&quot;&gt;@Autowired&lt;/span&gt; funktioniert habe ich hier erklärt:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/kernkonzepte-von-spring-beans-und.html&quot;&gt;kernkonzepte-von-spring.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Testergebnisse&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;So kommen wir nun endlich zu den Testergebnissen 😉&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;NUMBER_OF_CALLS = 2&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Auf meinem Rechner schwanken die Zeiten zwischen 0,8 und 3 Sekunden für alle Beans.&lt;br /&gt;Meist waren asynchron arbeitenden Beans minimal schneller. Wenn man allerdings mit so wenigen Requests arbeitet, kann man keine ernsthaften Aussagen zu Performance-Unterschieden machen.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;NUMBER_OF_CALLS = 15&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; Bean Zeiten zwischen 4 und 5,2 Sekunden&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Bean Zeiten zwischen 1,2 und 2,4 Sekunden&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;Bean Zeiten zwischen 2 und 3 Sekunden&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Es fällt also schon bei 15 Requests auf, dass die asynchrone Verarbeitung durch&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;oder&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Bean&amp;nbsp;&lt;/span&gt;schneller ist als die synchrone Verarbeitung durch die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ClassicBackendApiClient&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Bean.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;NUMBER_OF_CALLS = 150&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Bean braucht nun circa 30 Sekunden. Wenn man das mit den vorherigen Zeiten vergleicht, erkennt man einen linearen Zusammenhang. Jeder weitere Request verlängert die Gesamtdauer, da alle Request nacheinander ausgeführt werden.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;Bean Zeiten zwischen 7 und 10 Sekunden&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;Bean Zeiten zwischen 3 und 5,5 Sekunden&lt;/li&gt;&lt;li&gt;Die asynchrone Verarbeitung wird nun im Vergleich zur synchronen erheblich schneller.&lt;/li&gt;&lt;li&gt;Außerdem fällt auf, das sich bei dieser Request-Anzahl auch messbare Unterschiede zwischen der nicht blockierenden, reaktiven Programmierung (&lt;span style=&quot;font-family: courier;&quot;&gt;ReactiveBackendApiClient&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;) und der im jeweiligen Thread blockierenden, klassischen Programmierung (&lt;span style=&quot;font-family: courier;&quot;&gt;ThreadedClassicBackendApiClient&lt;/span&gt;) zeigen.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;NUMBER_OF_CALLS = 500&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Weitere Messungen mit noch größeren Request-Anzahlen zeigen, dass die Unterschiede immer größer werden. Das RestTemplate ist auch beim Einsatz in Threads durch die Anzahl der verfügbaren Threads und Kerne limitiert, während die reaktive Programmierung durch das nicht blockierende Warten auf eingehende Nachrichten (&lt;span style=&quot;font-family: courier;&quot;&gt;subscribe&lt;/span&gt;) seine Vorteile voll ausspielen kann. Bei Tests mit 500 Requests war&amp;nbsp;der &lt;b&gt;WebClient um circa Faktor 5 schneller als das RestTemplate in Threads.&lt;/b&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW5VvJ8SByhdKU-KHo8IvFoATBFXsUsp440Uwk-M4qZ26IfFRemEABURDTgw5cC228yGZMES0Vk3drm9xe5L0jXYGO1i115_4eNal97Fz0p2xq7lrBp7jkr9jTSwU-FWsMAy3xERGRApsY/s559/WebClient_Test-Messungen.PNG&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;302&quot; data-original-width=&quot;559&quot; height=&quot;346&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW5VvJ8SByhdKU-KHo8IvFoATBFXsUsp440Uwk-M4qZ26IfFRemEABURDTgw5cC228yGZMES0Vk3drm9xe5L0jXYGO1i115_4eNal97Fz0p2xq7lrBp7jkr9jTSwU-FWsMAy3xERGRApsY/w640-h346/WebClient_Test-Messungen.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Performance-Testergebnisse als Liniendiagramm&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Die reaktive Programmierung ist ein neues Entwicklungs-Paradigma, welches seine Vorteile insbesondere bei hoher Last durch viele Anfragen oder großen Datenabfragen ausspielt. Mit meinem WebClient Performance Test habe ich gezeigt,&amp;nbsp;dass der reaktive WebClient klassischen HTTP Clients bei vielen parallelen Anfragen überlegen ist.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Auch beim Abfragen großer Datenmengen kann der WebClient seine Überlegenheit zeigen, wenn die Server-Seite mit einen Flux antwortet. Für weiterführende Infos zur reaktiven Programmierung mit Spring schaut euch gerne meinen &lt;a href=&quot;https://www.udemy.com/course/reactive-spring/?referralCode=E1C590359F1ACD459B0B&quot; target=&quot;_blank&quot;&gt;Udemy Kurs&lt;/a&gt; an.&lt;/div&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2021/01/reactive-webclient.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8RGAEXw1h5Nf9qE6JVZ6P35ymeE98Nyxw1njKLAGZ2ZeZdKyZJcSniS7hMtEDVPuKVuKgexYQCAVT9wT7XJwhns1NfpJI8qHeFd8jBMi-0H82RXGo0tszSuxyCDReWyjAGgTVRk_II-7y/s72-w640-h484-c/spring_reactive_servlet.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-3640039399829841803</guid><pubDate>Sat, 01 Apr 2023 19:42:00 +0000</pubDate><atom:updated>2023-04-01T21:42:48.417+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Security</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Authentifizierung in Web-Anwendungen mit Spring Security 6</title><description>&lt;p&gt;&lt;b&gt;Spring Security hilft uns beim Authentifizieren und Autorisieren von Benutzern in Java Web-Anwendungen. Da Spring Security ein mächtiges und komplexes Framework ist, zeige ich in diesem Einstiegsartikel, wie wir die von Spring Boot vorkonfigurierte Spring Security Authentifizierung nutzen und anpassen können.&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;In diesem Artikel demonstriere ich Spring Security 6 passend zum aktuellen Spring Framework in Version 6. Das &lt;a href=&quot;https://github.com/elmar-brauch/spring-security-webapp&quot; target=&quot;_blank&quot;&gt;Demo Projekt&lt;/a&gt; wurde mit der neuen Spring Boot Version 3 erstellt.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio5TBnHv-r03ji_v2WpLCNDiv58E7B-Q3jd4wjYgJsqdtOIScvUXEuBV6SflHaTS9Ls3kLLo30OaTUiLoEtRptoHmJmmSV6W7KgYn7EdQuWGpmpLbMh6knL5Fr8To2bQLHORxo1au7ptxS/s400/Spring_security_logo.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;400&quot; data-original-width=&quot;400&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio5TBnHv-r03ji_v2WpLCNDiv58E7B-Q3jd4wjYgJsqdtOIScvUXEuBV6SflHaTS9Ls3kLLo30OaTUiLoEtRptoHmJmmSV6W7KgYn7EdQuWGpmpLbMh6knL5Fr8To2bQLHORxo1au7ptxS/s320/Spring_security_logo.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2&gt;Spring Security&lt;/h2&gt;&lt;h3&gt;&lt;div style=&quot;font-size: medium; font-weight: 400;&quot;&gt;Spring Security ist eins mächtiges und flexibel anpassbares Authentifizierungs- und Zugangskontroll-Framework. In der Praxis ist es der Standard zum Absichern von Spring basierten Web-Anwendungen.&lt;/div&gt;&lt;div style=&quot;font-size: medium; font-weight: 400;&quot;&gt;Weitere Infos zum Spring Security Projekt findet man hier:&amp;nbsp;&lt;a href=&quot;https://spring.io/projects/spring-security&quot;&gt;https://spring.io/projects/spring-security&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;225&quot; src=&quot;https://www.youtube.com/embed/E6P7V5vvkus&quot; width=&quot;424&quot; youtube-src-id=&quot;E6P7V5vvkus&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-weight: normal;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-size: small;&quot;&gt;Video zum Blog-Artikel&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/h3&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Spring Security Dependency für das Build-Tool&lt;/h3&gt;&lt;div&gt;Mit Spring Boot kann man Spring Security einfach beim Aufsetzen eines neuen Projektes hinzufügen. Das funktioniert so, wie ich es schon in diesem Artikel gezeigt habe:&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/09/microservices-mit-spring-boot-erstellen.html&quot;&gt;microservices-mit-spring-boot-erstellen.html&lt;/a&gt;&lt;br /&gt;Allerdings kann man Spring Security auch einfach als Maven Dependency zu existierenden Spring oder Spring Boot Projekten hinzufügen, bei Spring Boot Projekten sieht das so aus:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;org.springframework.boot&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring-boot-starter-security&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Da ich zum Bauen der Demo zu diesem Artikel Gradle (&lt;a href=&quot;https://gradle.org/&quot;&gt;https://gradle.org/&lt;/a&gt;) verwendet habe, zeige ich hier auch das Gradle Format:&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;dependencies {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;implementation &#39;org.springframework.boot:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;spring-boot-starter-security&#39;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Den kompletten Code zur Spring Security Demo findet ihr in GitHub:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;a href=&quot;https://github.com/elmar-brauch/spring-security-webapp&quot;&gt;https://github.com/elmar-brauch/spring-security-webapp&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;Authentifizierung mit dem Spring Security Standard Benutzer&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Nach dem Hinzufügen der Spring Security Dependency ist die Spring Boot Web-Anwendung standardmäßig mit Benutzername und Passwort abgesichert. Das sieht man dann in den Log-Einträgen beim Starten der Anwendung:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Starting Servlet engine: [Apache Tomcat/10.1.1]...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...Initializing Spring embedded WebApplicationContext&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...Root WebApplicationContext: initialization completed in 909 ms&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;.s.s.UserDetailsServiceAutoConfiguration :&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;Using generated security password: 0139...&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...Adding welcome page template: index&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;o.s.s.web.DefaultSecurityFilterChain : Will secure any request with&lt;br /&gt;[org.spring...security...WebAsyncManagerIntegrationFilter@e6cffb4,&amp;nbsp;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;org.spring...security...DefaultLoginPageGeneratingFilter@6507a4d2,&lt;br /&gt;...&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;In den Logs steht das automatisch, generierte Passwort für den Standard Benutzer mit dem Benutzernamen &quot;user&quot;: &quot;&lt;b style=&quot;font-family: courier;&quot;&gt;Using generated security password: 0139...&lt;/b&gt;&quot;. Bei jedem Start der Anwendung wird ein neues Passwort generiert.&lt;br /&gt;Außerdem sehen wir noch die Log-Meldung das jeder beliebige Request mit einer Liste von diversen Spring Security Filtern abgesichert wird: &quot;&lt;b style=&quot;font-family: courier;&quot;&gt;DefaultSecurityFilterChain: Will secure any request with...&lt;/b&gt;&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Rufen wir nun im Browser die Web-Application auf (&lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;), werden wir von den Spring Security Filtern automatisch zur Spring Security Login-Seite weitergeleitet. Der Login-Dialog wird im folgenden Screenshot gezeigt.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW3nvDwMwNzu9_p_k0inG82Mq42ainy4gjlXlyY6W5b5yYstJkfMxyQPq3bQOr2aEA8QRQZ8WDqXucginWO3eBoZ0gdxWtAhj2CDfRfjSEqTxdJwUSVyoEm_jbfk2HpsyCwEBUdz6faG3x/s599/Spring_security_login.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;412&quot; data-original-width=&quot;599&quot; height=&quot;440&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW3nvDwMwNzu9_p_k0inG82Mq42ainy4gjlXlyY6W5b5yYstJkfMxyQPq3bQOr2aEA8QRQZ8WDqXucginWO3eBoZ0gdxWtAhj2CDfRfjSEqTxdJwUSVyoEm_jbfk2HpsyCwEBUdz6faG3x/w640-h440/Spring_security_login.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;Nach erfolgreichem Login als Benutzer &quot;user&quot; wird der Request wie gewohnt von unseren Spring Controllern verarbeitet, wie das funktioniert ist z.B. hier gezeigt:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html&quot;&gt;spring-mvc-thymeleaf.html&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Spring Security Filter Chain&lt;/h3&gt;&lt;div&gt;Vereinfacht gesagt, ist Spring Security eine zusätzlich Schicht zwischen dem Client und der Spring Anwendung. Die Spring Security Schicht prüft, ob die Requests des Clients den Security Regeln der Anwendung entsprichen. Security Regeln definieren wir in Form von Filtern. Die Filter bilden eine Kette (Filter Chain). Jeder eingehende Request durchläuft die Filter der Kette entsprechend ihrer Reihenfolge. Dabei prüft jeder Filter, ob dem Request Zugriff auf die Anwendung gewährt wird oder nicht.&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Die Logausgabe listet z.B. den Filter&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;DefaultLoginPageGeneratingFilter&amp;nbsp;&lt;/span&gt;auf. Wenn wir diese Klasse in unserer IDE öffnen, sehen wir, dass die Superklasse das &lt;span style=&quot;font-family: courier;&quot;&gt;Filter &lt;/span&gt;Interface implementiert. In&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;DefaultLoginPageGeneratingFilter&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;gibt es daher auch die&amp;nbsp;Implementierung der Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;doFilter&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. In &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;doFilter &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;wird entschieden, ob die Login Seite angezeigt wird oder nicht. Wenn ihr einen Breakpoint in &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;doFilter &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;setzt und die Anwendung im Debug-Modus startet, seht ihr, dass dieser Filter&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;bei jedem Request&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;benutzt wird.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Standard Benutzer konfigurieren&lt;/h3&gt;&lt;div&gt;Wenn unsere Web-Anwendungen mit einem Benutzer auskommt, können wir den Standard Benutzer auch per Konfiguration anpassen. Dazu können wir in der &lt;i&gt;application.properties&lt;/i&gt; Datei den Benutzernamen und das Passwort selbst definieren:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;# Replace Spring Security default username &quot;user&quot; with:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.security.user.name=admin-user&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;# Replace Spring Securtiy randomly generated password with:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.security.user.password=geheim123&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Nach einem Neustart können wir uns nun als Benutzer &quot;admin-user&quot; mit dem Passwort &quot;geheim123&quot; einloggen.&lt;br /&gt;Spring Boot bietet neben der &lt;i&gt;application.properties&lt;/i&gt; Datei noch einige andere Möglichkeiten, um Konfigurationen vorzunehmen. So können wir das Passwort auch an einer anderen Stelle definieren - weitere Infos dazu findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/boot-features-external-config.html&quot;&gt;https://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/boot-features-external-config.html&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;Mehrere Benutzer und Rollen definieren&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Spring Security unterstützt beliebig viele, verschiedene Benutzer. Diese können sich in einer Datenbank, in einem LDAP-Server oder anderen Identity Provider befinden - es werden alle gängigen Lösungen unterstützt. Alternativ kann man Spring Security auch wie benötigt anpassen. Um diesen Einstiegsartikel möglichst einfach zu halten, stelle ich hier nur eine einfache Benutzer-Verwaltung im Speicher vor. Alles was wir dazu brauchen ist eine einfache Spring Bean, die das Interface&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UserDetailsService&amp;nbsp;&lt;/span&gt;implementiert.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class SecurityConfiguration {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public UserDetailsService users(@Autowired PasswordEncoder pwEnc) &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;{&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; UserDetails user = User.builder()&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.username(&quot;user&quot;)&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .password(pwEnc.encode(&quot;top&quot;))&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .roles(&quot;USER&quot;)&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .build();&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;UserDetails admin = User.builder()&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .username(&quot;admin&quot;)&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .password(pwEnc.encode(&quot;secret&quot;))&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .roles(&quot;USER&quot;, &quot;ADMIN&quot;)&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .build();&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return new InMemoryUserDetailsManager(user, admin);&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public PasswordEncoder passwordEncoder() {&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;return new BCryptPasswordEncoder();&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span&gt;&lt;span&gt;Die&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UserDetailsService &lt;/span&gt;Bean habe ich per &lt;span style=&quot;font-family: courier;&quot;&gt;@Bean&lt;/span&gt; Annotation in einer Spring Konfigurationsklasse (&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;) erstellt. Dieses Vorgehen ist Standard in Spring, siehe dazu auch&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/kernkonzepte-von-spring-beans-und.html&quot;&gt;kernkonzepte-von-spring.html&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Konkret wird die&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UserDetailsService&amp;nbsp;&lt;/span&gt;Bean in Form einer Instanz von &lt;span style=&quot;font-family: courier;&quot;&gt;InMemoryUserDetailsManager &lt;/span&gt;bereitgestellt. Dem Konstruktor-Aufruf kann man dabei beliebig viele Benutzer Objekte übergeben (hier im Beispiel sind es nur 2). Alternativen zur Benutzerverwaltung im Speicher mittels &lt;span style=&quot;font-family: courier;&quot;&gt;InMemoryUserDetailsManager&amp;nbsp;&lt;/span&gt;sind zum Beispiel&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;JdbcUserDetailsManager &lt;/span&gt;oder&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;CachingUserDetailsService&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;Die beiden Benutzer wurden hier einfach im Java-Code erstellt. Benutzername und Password sind hier im Beispiel hardcoded - in der Praxis sollte man Credentials auch beim Verwalten der Benutzer im Speicher aus der Spring Konfiguration lesen.&lt;br /&gt;Die Benutzer (Instanz von &lt;span style=&quot;font-family: courier;&quot;&gt;UserDetails&lt;/span&gt;) werden mittels &lt;span style=&quot;font-family: courier;&quot;&gt;UserBuilder &lt;/span&gt;(&lt;span style=&quot;font-family: courier;&quot;&gt;User.builder()&lt;/span&gt;) erstellt. Das Setzen von &lt;span style=&quot;font-family: courier;&quot;&gt;username&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;password &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;roles &lt;/span&gt;(bzw. der Aufruf der gleichnamigen Builder-Methoden) ist dabei Pflicht - alle anderen &lt;span style=&quot;font-family: courier;&quot;&gt;UserDetails &lt;/span&gt;Attribute sind optional. Rollen (roles) werde ich in einem künftigen Blog-Artikel vorstellen, den Demo-Code dazu findet ihr aber jetzt schon im GitHub-Repository zu diesem Artikel.&lt;/li&gt;&lt;li&gt;Das Passwort muss encodiert im &lt;span style=&quot;font-family: courier;&quot;&gt;UserBuilder &lt;/span&gt;gesetzt werden. Das mache ich mit der Bean &lt;span style=&quot;font-family: courier;&quot;&gt;PasswordEncoder&lt;/span&gt;, die eine einfache Instanz der &lt;span style=&quot;font-family: courier;&quot;&gt;BCryptPasswordEncoder &lt;/span&gt;Implementierungsklasse ist.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Nach einem Neustart der Spring Boot Applikation könnt ihr den Login mit beiden Benutzern testen, indem ihr die URL &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt; jeweils in einem neuen Incognito Browser-Fenster öffnet. Da wir hier noch keine Logout-Funktion implementiert haben, empfehle ich zum Testen den Incognito Modus des Browsers.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;Fazit &amp;amp; nächste Schritte&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;In diesem Blog-Artikel habe ich gezeigt, wie man Spring Security zu einem Spring Boot Projekt hinzufügen kann und was die Standard-Konfiguration von Spring Security mitbringt. In einem kleinen nächsten Schritt haben wir weitere Benutzer für unsere Spring Security abgesicherte Web-Anwendung erstellt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Möchtet ihr tiefer in das Thema einsteigen? Dann schaut euch noch diese Artikel an:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2022/09/spring-security-roles.html&quot;&gt;Spring Security: Login, Logout &amp;amp; Rollen&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2022/10/openid.html&quot;&gt;OpenID Connect mit Spring Boot&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Hinterlasst mir gerne einen Kommentar mit Fragen, Feedback oder Themen-Wünsche zu Spring Security.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den kompletten hier vorgestellten Code mit einer Demo-Webanwendung findet ihr hier:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/spring-security-webapp&quot;&gt;https://github.com/elmar-brauch/spring-security-webapp&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2020/12/spring-security-starter.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio5TBnHv-r03ji_v2WpLCNDiv58E7B-Q3jd4wjYgJsqdtOIScvUXEuBV6SflHaTS9Ls3kLLo30OaTUiLoEtRptoHmJmmSV6W7KgYn7EdQuWGpmpLbMh6knL5Fr8To2bQLHORxo1au7ptxS/s72-c/Spring_security_logo.png" height="72" width="72"/><thr:total>2</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-6372290200671050189</guid><pubDate>Thu, 16 Mar 2023 20:37:00 +0000</pubDate><atom:updated>2023-03-16T21:37:26.073+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">OpenAPI</category><category domain="http://www.blogger.com/atom/ns#">Security</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Erfahrungsbericht Spring Boot 3 Update</title><description>&lt;p&gt;&lt;b&gt;Unser Java-System ist jetzt mit Spring Boot Version 3 im produktiven Einsatz! Hier teile ich meinen Erfahrungsbericht, da sich Systeme in der echten Welt häufig von Demos oder Tutorials unterscheiden.&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Highlights Spring Boot 3&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Warum auf Spring Boot 3 und damit auf die Version 6 des Spring Frameworks updaten?&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Spring 6 ist auf die aktuelle Java LTS Version 17 aktualisiert. Damit modernisiert Spring sich in erster Linie selbst, wovon wir indirekt profitieren.&lt;/li&gt;&lt;li&gt;Native Images mit GraalVM werden offiziell supported. Das ermöglicht unserer Spring Anwendung den blitzschnellen Start als Docker Container, siehe &lt;a href=&quot;https://agile-coding.blogspot.com/2021/06/graalvm.html&quot;&gt;graalvm.html&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Viele direkt oder indirekt verwendete 3rd Party Bibliotheken sind auf neue Versionen aktualisiert. Wir bekommen damit diverse Fixes für Sicherheitslücken.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL6iMGbdlnC8_b01H8OVM2GONs1WsKGwf3PgdbyNfasf5oumvXbLjiLH1gLBwSZQcdErK8kLJFSvQONoyHL3j8ieW48cMXngs9Zx-8ecbD2NpiA7NNSfKBT9N6GYgmtmgf0Rm29-2QbpkM-bLYJ-UEq_5KtwyTsZxcfl2UQ62sz5tu4XSB_jZ4DjFVjg/s5000/Spring_Logo.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;5000&quot; data-original-width=&quot;4998&quot; height=&quot;200&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL6iMGbdlnC8_b01H8OVM2GONs1WsKGwf3PgdbyNfasf5oumvXbLjiLH1gLBwSZQcdErK8kLJFSvQONoyHL3j8ieW48cMXngs9Zx-8ecbD2NpiA7NNSfKBT9N6GYgmtmgf0Rm29-2QbpkM-bLYJ-UEq_5KtwyTsZxcfl2UQ62sz5tu4XSB_jZ4DjFVjg/w200-h200/Spring_Logo.png&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Planung des Spring Boot 3 Updates&lt;/h2&gt;&lt;div&gt;Der &lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide&quot; target=&quot;_blank&quot;&gt;Spring Boot 3.0 Migration Guide&lt;/a&gt; enthält im Wesentlichen diese Schritte:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Update auf Spring Boot 2.7&lt;/li&gt;&lt;li&gt;Update auf Java 17&lt;/li&gt;&lt;li&gt;Update auf Spring Boot 3&lt;/li&gt;&lt;li&gt;Dependencies anpassen&lt;/li&gt;&lt;li&gt;Code anpassen&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Unsere Ausgangssituation war eine Spring Boot Anwendung in Version 2.7.X mit Java 17. Alle wesentlichen Dependency-Versionen managt Spring Boot für uns. Außerdem haben wir automatisierte Ende zu Ende Tests, die nach dem Update und der Beseitigung aller Compile-Fehler die Funktionalität unseres Systems schnell testen. Durch frühes Ausprobieren von Spring Boot 3 an einfachen Demo-Projekten wussten wir, dass die API Code Generatoren und die Spring Security Konfiguration Code-Anpassungen benötigen. Daher planten wir unser Spring Boot 3 Update so:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Umstellung des Maven Swagger Code Generator Plugins auf OpenAPI Code Generator.&lt;/li&gt;&lt;li&gt;Aktivierung des Spring Boot 3 Supports im OpenAPI Code Generator. &lt;br /&gt;&lt;i&gt;Zum Zeitpunkt unserer frühen Migration hatte der Swagger Code Generator Spring Boot 3 bzw. die jakarta Package imports nicht generiert. Hier hätten wir auch warten können.&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Spring Boot Version 3.0.X in Maven Parent POM konfigurieren.&lt;/li&gt;&lt;li&gt;Code Anpassung&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;jakarta &lt;/span&gt;statt &lt;span style=&quot;font-family: courier;&quot;&gt;javax &lt;/span&gt;Imports&lt;/li&gt;&lt;li&gt;Code Anpassung in Spring Security Konfiguration&lt;/li&gt;&lt;/ol&gt;Akzeptanz-Kriterium: Das Spring Boot 3 Update ist erst erfolgreich, wenn:&lt;br /&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;ul&gt;&lt;li&gt;es keine Compile-Fehler gibt&lt;/li&gt;&lt;li&gt;alle JUnit-Tests erfolgreich durchlaufen&lt;/li&gt;&lt;li&gt;die CICD Pipeline alle Ende zu Ende Tests erfolgreich ausgeführt hat&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Praktische Umsetzung des Spring Boot 3 Updates&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;Unser System wird seit über zwei Jahre von 6 EntwicklerInnen gebaut und befindet sich über ein Jahr im produktiven Einsatz. Daher bescherte uns die Umsetzung einige ungeplante Überraschungen. Diese Mehraufwände stelle ich im Folgenden thematisch gruppiert vor.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;REST API Code Generator&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Den Code für REST-APIs generieren wir mittlerweile mit dem OpenAPI Maven Plugin. Das grundsätzliche Vorgehen um den OpenAPI Code Generator mit Spring Boot 3 zu verwenden, stelle ich in diesem Artikel vor:&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/08/openapi-codegen.html&quot;&gt;openapi-codegen.html&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Das Problem in der Praxis war, dass wir vom Swagger Code Generator auf den OpenAPI Code Generator wechseln mussten. Also nicht einfach nur eine neuere Version verwenden, sondern auch die Technologie austauschen. Für APIs in guter Qualität hat das problemlos funktioniert. Da unser System über 10 verschiedene REST-APIs benutzt, haben wir leider auch APIs in schlechter Qualität für die der OpenAPI Code Generator nur mit Spezial-Konfigurationen oder Workarounds funktioniert.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.openapitools&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;artifactId&amp;gt;openapi-generator-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;version&amp;gt;6.2.1&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;id&amp;gt;x-service&amp;lt;/id&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;goal&amp;gt;generate&amp;lt;/goal&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;inputSpec&amp;gt;.../x-api.yml&amp;lt;/inputSpec&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;generatorName&amp;gt;spring&amp;lt;/generatorName&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;configOptions&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;lt;useSpringBoot3&amp;gt;true&amp;lt;/useSpringBoot3&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;/configOptions&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&lt;b&gt;&amp;lt;skipValidateSpec&amp;gt;true&amp;lt;/skipValidateSpec&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;...&lt;/span&gt;&amp;nbsp;&lt;/div&gt;&lt;p style=&quot;background-color: white; margin: 0px; padding: 0px;&quot;&gt;&lt;/p&gt;&lt;ul style=&quot;color: #172b4d; text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Für Code Generierung mit Spring Boot 3 setzen wir &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;useSpringBoot3&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Schlechte API Qualität zwingt uns die Validierung der API Spezifikation im Maven Plugin zu überspringen: &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;skipValidateSpec&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;SOAP API Code Generator&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Unser System verwendet die SOAP API eines Legacy Systems. In unserer Planung hatten wir diese Schnittstelle vergessen. In der Praxis gab es das gleiche Problem wie beim Generieren der REST APIs unser Plugin unterstützt Spring Boot 3 noch nicht.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die Lösung fanden wir im &lt;a href=&quot;https://github.com/highsource/maven-jaxb2-plugin/issues/253&quot; target=&quot;_blank&quot;&gt;GitHub Projekt unseres &lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;a href=&quot;https://github.com/highsource/maven-jaxb2-plugin/issues/253&quot; target=&quot;_blank&quot;&gt;maven-jaxb2-plugin&lt;/a&gt;.&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;Dort wurde ein Plugin aus einem anderen Branch empfohlen. Ohne auf weitere Details der SOAP Code Generierung einzugehen, es sieht jetzt so aus:&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&amp;lt;plugin&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;com.helger.maven&amp;lt;/groupId&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;lt;artifactId&amp;gt;jaxb40-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;lt;version&amp;gt;0.16.1&amp;lt;/version&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;executions&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;execution&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;goals&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;&amp;lt;goal&amp;gt;generate&amp;lt;/goal&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/goals&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/execution&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/executions&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;configuration&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;schemaLanguage&amp;gt;WSDL&amp;lt;/schemaLanguage&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;generatePackage&amp;gt;...&amp;lt;/generatePackage&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;schemaDirectory&amp;gt;...&amp;lt;/schemaDirectory&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;schemaIncludes&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;include&amp;gt;y-service.wsdl&amp;lt;/include&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/schemaIncludes&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/configuration&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;/plugin&amp;gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;p style=&quot;background-color: white; margin: 0px; padding: 0px;&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Code Anpassungen&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Nach dem Spring Boot 3 Dependency Update zeigte der Compiler viele Fehler an. Erwartungsgemäß ersetzen wir dazu&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;javax &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;durch &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;jakarta &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Imports - mit der Text suchen und ersetzen Funktion der IDE geht das recht schnell. Hier ein paar Beispiele:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;strike&gt;import javax.servlet.http.HttpServletRequest;&lt;/strike&gt;&lt;br /&gt;&lt;/span&gt;import &lt;b&gt;jakarta&lt;/b&gt;.servlet.http.HttpServletRequest;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;strike&gt;import javax.annotation.PostConstruct;&lt;/strike&gt;&lt;br /&gt;import &lt;b&gt;jakarta&lt;/b&gt;.annotation.PostConstruct;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;strike&gt;import javax.validation.Valid;&lt;/strike&gt;&lt;br /&gt;import &lt;b&gt;jakarta&lt;/b&gt;.validation.Valid;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Spring 6 führt das Interface &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpStatusCode &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;neu ein. Dieses wird nun von diversen Klassen und Methoden im Spring Framework anstelle des &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;enum HttpStatus&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; verwendet. Das ist eine überschaubare Umstellung, weil &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpStatus &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;das Interface&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpStatusCode&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;implementiert. Trotzdem müsst ihr die Stellen im Code anpassen, welche jetzt nur noch das Interface zur Verfügung haben:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;WebClient.create(&quot;url&quot;).get().retrieve()&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;strike&gt;.onStatus(HttpStatus::isError, ...)...&lt;/strike&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.onStatus(&lt;/span&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;HttpStatusCode::isError&lt;/b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;, ...)...&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Spring Security Code Anpassungen&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Spring Security 6 entfernte Methoden und ersetzte sie durch neue. Dadurch entstehen beim Spring Boot 3 Update Compile Fehler. In &lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/spring-security-starter.html&quot; target=&quot;_blank&quot;&gt;meinem Einsteiger Artikel zu Spring Security 6&lt;/a&gt;&amp;nbsp;zeige und erkläre ich aktuellen Code, daher hier nur die Vorher-Nachher Gegenüberstellung:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;@EnableWebSecurity&lt;/div&gt;&lt;div&gt;// Before @EnableWebSecurity included @Configuration&lt;/div&gt;&lt;div&gt;&lt;b&gt;@Configuration&lt;/b&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;public class SecurityConfiguration {&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;SecurityFilterChain securityFilterChain(HttpSecurity http) {&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;http.&lt;strike&gt;authorizeRequests()&lt;/strike&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;http.&lt;b&gt;authorizeHttpRequests&lt;/b&gt;()&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;.&lt;strike&gt;antMatchers&lt;/strike&gt;(&quot;/secured&quot;).authenticated()&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.&lt;b&gt;requestMatchers&lt;/b&gt;(&quot;/secured&quot;).authenticated()&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.anyRequest().permitAll()&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Wir verwenden für manche API Tests Spring &lt;span style=&quot;font-family: courier;&quot;&gt;MockMvc&lt;/span&gt;. Diese Tests haben nach dem Spring Boot 3 Update das Problem, dass die Spring Security Filter automatisch angewendet werden und Requests wegen fehlender Authentifizierung ablehnen. Hier ein Vorher-Beispiel:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@AutoConfigureMockMvc&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;class MockMvcTest {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Autowired private MockMvc mvc;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;void redirectTest() throws Exception {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;mvc.perform(get(&quot;/secured&quot;))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.andExpect(status().is3xxRedirection());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anstelle des erwarteten Http Status 3XX lehnt die Spring &lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&lt;/span&gt; Bean den Request ab. Ein einfacher Weg die Security Filter zu entfernen, bietet das Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;addFilters&lt;/span&gt; der Annotation&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;@AutoConfigureMockMvc:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@AutoConfigureMockMvc(&lt;b&gt;addFilters = false&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;class MockMvcTest { ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Spring Web-Flow Workaround&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Aus historischen Gründen verwendet unsere Anwendung Spring Web-Flow. Ich empfehle Spring Web-Flow nicht, weil es nach der Version 2.5.1 über 4 Jahre kein Update erhielt. Nach dem Update auf Spring Boot 3 kam es im Web-Flow Code zu Compile Fehlern.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Zum Zeitpunkt dieses Artikels war Spring Web-Flow 3.0.0 passend zu Spring Boot 3 noch nicht im zentralen Maven Repository verfügbar. Um das Spring Boot 3 Update trotzdem durchführen zu können, entschieden wir uns Spring Web-Flow als Milestone Release 3.0.0-M1 zu verwenden. Dazu hinterlegen wir das Spring Milestone Repository als weiteres Maven Repository in der pom.xml:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;lt;groupId&amp;gt;org.springframework.webflow&amp;lt;/groupId&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;artifactId&amp;gt;spring-webflow&amp;lt;/artifactId&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;version&amp;gt;3.0.0-M1&amp;lt;/version&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/div&gt;&lt;div&gt;...&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;div&gt;&amp;lt;repositories&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;repository&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;id&amp;gt;spring-milestones&amp;lt;/id&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;name&amp;gt;Spring Milestones&amp;lt;/name&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;url&amp;gt;https://repo.spring.io/milestone&amp;lt;/url&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;snapshots&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;enabled&amp;gt;false&amp;lt;/enabled&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;/snapshots&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/repository&amp;gt;&lt;/div&gt;&lt;div&gt;&amp;lt;/repositories&amp;gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Ein weiteres Problem lag im Zusammenspiel von Spring Security 6 und Spring Web-Flow 3. Mit Spring Security 5 gab es ein Default Attribute &quot;ROLE_USER&quot; im OpenID Connect Token. Dessen Existenz&amp;nbsp; überprüften wir in der Spring Web-Flow 2.5.1 XML-Konfiguration so:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;secured attributes=&quot;ROLE_USER&quot; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Nachdem Spring Security 6 Update gibt es andere Default Attribute. Eines hat den Namen &quot;&lt;/span&gt;OIDC_USER&lt;span style=&quot;font-family: inherit;&quot;&gt;&quot;. Trotz folgender Anpassung funktionierte die Token Überprüfung per XML-Konfiguration im Web-Flow nicht:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;lt;secured attributes=&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;OIDC_USER&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Der Grund dafür ist, dass Spring Web-Flow den Prefix &quot;ROLE_&quot; erwartet. Diesen Prefix gibt es aber in den Default Attributen des Tokens nicht mehr. Daher habe ich die Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;getRolePrefix()&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; im &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RoleVoter &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;überschrieben, um den Prefix &quot;ROLE_&quot; durch den leeren String &quot;&quot; zu ersetzen. Den &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RoleVoter &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;setze ich im &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;AccessDecisionManager &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;des&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFlowExecutionListener&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, diesen hatten wir schon mit Spring Web-Flow 2.5.1 in einer Bean instanziiert.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public FlowExecutor flowExecutor() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;var listener = new &lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;SecurityFlowExecutionListener();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;listener.setAccessDecisionManager(&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;new ConsensusBased(&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;List.of(&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;new RoleVoter() {&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;@Override&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;public String getRolePrefix() {&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;return &quot;&quot;;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;}&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;}&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; )&lt;/span&gt;);&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return getFlowExecutorBuilder(flowRegistry())&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.addFlowExecutionListener(listener)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Fazit&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;Die Komplexität des Spring Boot 3 Updates hängt von der Größe eurer Anwendung und der Anzahl nicht durch Spring Boot gemanagter Bibliotheken ab. Insbesondere wenn die Bibliotheken dem aktuellen Stand der Technik hinterherhinken, wie in meinem Fall z. B. Spring Web-Flow, wird es kompliziert und dreckig.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die Workarounds, frühe Beta-Versionen oder alternative Bibliotheken (hier OpenAPI statt Swagger Code Generator) zu verwenden, sind riskant. Dank vielen Unit-Tests und guten, automatisierten Ende zu Ende entschieden wir uns für das Update trotz der genannten Risiken. Heute profitieren&amp;nbsp;wir von den Vorteilen der neusten Spring Version.&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/03/spring-boot-3-update.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL6iMGbdlnC8_b01H8OVM2GONs1WsKGwf3PgdbyNfasf5oumvXbLjiLH1gLBwSZQcdErK8kLJFSvQONoyHL3j8ieW48cMXngs9Zx-8ecbD2NpiA7NNSfKBT9N6GYgmtmgf0Rm29-2QbpkM-bLYJ-UEq_5KtwyTsZxcfl2UQ62sz5tu4XSB_jZ4DjFVjg/s72-w200-h200-c/Spring_Logo.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Frankfurt am Main, Deutschland</georss:featurename><georss:point>50.1109221 8.6821267</georss:point><georss:box>21.800688263821158 -26.474123300000002 78.421155936178849 43.8383767</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-7593680691873452942</guid><pubDate>Mon, 06 Mar 2023 05:00:00 +0000</pubDate><atom:updated>2023-03-06T06:00:00.186+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Spring</category><category domain="http://www.blogger.com/atom/ns#">Vue.js</category><title>Vue.js Frontend mit Spring Boot Backend</title><description>&lt;p&gt;&lt;b&gt;Vue.js ist eine schnelle und flexible JavaScript-basierte Frontend-Entwicklungsplattform. Durch intuitive Syntax und umfangreiche Bibliotheken ist es eine der besten Optionen für moderne Frontend-Entwicklung. Hier kombinieren wir es mit einem Spring Boot Backend!&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Frontend und Backend im Zusammenspiel&lt;/h2&gt;&lt;p&gt;Was bauen wir genau?&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Wir programmieren ein modernes Frontend mit Vue.js.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Mit npm bauen wir das Frontend als produktionsbereites, kompaktes Artefakt.&lt;/li&gt;&lt;li&gt;Danach Kopieren wir es in unsere Spring Boot Anwendung, so dass es vom integrierten Application Server (z. B. Tomcat oder Netty) ausgeliefert wird.&lt;/li&gt;&lt;li&gt;Mit Spring Boot implementieren wir das Backend mit REST-API für das Vue.js Frontend.&lt;/li&gt;&lt;/ol&gt;Das folgende Bild zeigt die Struktur unseres Spring Boot Servers mit integriertem Vue.js Frontend. Zuerst lädt der Benutzer die komplette Frontend Anwendung im Browser. Sofern diese mit Vue.js als Single Page Application gebaut wurde, finden anschließend nur noch REST-Requests statt.&amp;nbsp;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLSKp_XHfAou2OTKbjKSRaQEpF8YYWbUiSomAz5uUDVZPrb2PjjkurEqMGZWOSkeEMsdfO1j3mSPwRtZU47rij005A4PmCeRYn2lKml23B5PvNXhcQe-WscYBqtKaUbqbvTWWP9ypcfqIAWiBk29O12kvpOulkMJRTDdS-qu5k6oJdkBtYanHEofEPSw/s613/Vue_js_Spring_Boot.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;401&quot; data-original-width=&quot;613&quot; height=&quot;418&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLSKp_XHfAou2OTKbjKSRaQEpF8YYWbUiSomAz5uUDVZPrb2PjjkurEqMGZWOSkeEMsdfO1j3mSPwRtZU47rij005A4PmCeRYn2lKml23B5PvNXhcQe-WscYBqtKaUbqbvTWWP9ypcfqIAWiBk29O12kvpOulkMJRTDdS-qu5k6oJdkBtYanHEofEPSw/w640-h418/Vue_js_Spring_Boot.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Frontend und Backend Entwicklung&lt;/h3&gt;&lt;div&gt;Dieser Artikel konzentriert sich auf die Integration des Vue.js Frontend in das Spring Boot Backend.&lt;br /&gt;Zum Entwickeln eines Frontend mit Vue.js schaut euch&amp;nbsp;&lt;a href=&quot;https://vuejs.org/&quot;&gt;https://vuejs.org/&lt;/a&gt; an.&lt;br /&gt;Zum Programmieren vom Spring Backend findet ihr viele Artikel hier&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/&quot; target=&quot;_blank&quot;&gt;hier im Blog&lt;/a&gt;&amp;nbsp;oder bei &lt;a href=&quot;https://www.udemy.com/course/reactive-spring/?referralCode=E1C590359F1ACD459B0B&quot; target=&quot;_blank&quot;&gt;Udemy&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Frontend bauen und in Spring Boot integrieren&lt;/h2&gt;&lt;div&gt;Nachdem ihr das Frontend in einem beliebigen Verzeichnis entwickelt und getestet habt, könnt ihr es für den produktiven Einsatz bauen. Öffnet dazu eine Shell im Frontend-Verzeichnis und führt den folgenden, fett markierten Befehl aus:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;PS C:\&amp;lt;path to Vue.js project&amp;gt;\frontend&amp;gt; &lt;b&gt;npm run build&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;gt; frontend@0.0.0 build&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...&lt;br /&gt;vite v4.1.2 building for production...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;✓ 54 modules transformed.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;dist/index.html&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0.39 kB&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;dist/assets/index-638fb41c.js&amp;nbsp; 85.25 kB │ gzip: 33.71 kB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Das Ergebnis des Bauen befindet sich im automatisch erstellten &lt;span style=&quot;font-family: courier;&quot;&gt;\dist&lt;/span&gt; Verzeichnis. Es ist eine&amp;nbsp; transformierte und komprimierte Version des Frontend für den produktiven Einsatz.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Die Integration ins Spring Boot Projekt probieren wir einmalig von Hand aus. Dazu kopieren wir alle Dateien und Verzeichnisse im &lt;span style=&quot;font-family: courier;&quot;&gt;\dist&lt;/span&gt; Verzeichnis in das &lt;span style=&quot;font-family: courier;&quot;&gt;src\main\resources\public&lt;/span&gt; Verzeichnis des Spring Boot Projektes. Das &lt;span style=&quot;font-family: courier;&quot;&gt;public &lt;/span&gt;Verzeichnis erstellen wir vorab, das Verzeichnis&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;resources &lt;/span&gt;sollte im neu generierten Spring Boot Projekt vorhanden sein.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Danach startet wir die Spring Boot Anwendung und öffnet sie im Browser. &lt;i&gt;&lt;u&gt;http://localhost:8080/index.html&lt;/u&gt;&lt;/i&gt; zeigt dann das Frontend.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Frontend und Backend mit Maven bauen&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven Frontend Konfiguration&lt;/h3&gt;&lt;div&gt;Meistens bauen wir aus unserer Spring Boot Anwendung eine ausführbare jar Datei. Dazu verwenden wir das Build Tool Maven. Zuerst muss das Frontend gebaut werden, die Maven Konfiguration pom.xml sieht so:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 18,0pt;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xml version&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;1.0&quot; &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;encoding&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;?&amp;gt;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;project &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xmlns&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=...&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;4.0.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;frontend&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;de.bsi&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;vue&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;build&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;plugins&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;com.github.eirslett&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;frontend-maven-plugin&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;1.12.1&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;executions&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;                    &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;install node and npm&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;install-node-and-npm&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;nodeVersion&lt;/span&gt;&amp;gt;v16.17.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;nodeVersion&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;                    &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;npm install&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;npm&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;                        &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;phase&lt;/span&gt;&amp;gt;generate-resources&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;phase&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;                        &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;arguments&lt;/span&gt;&amp;gt;install&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;arguments&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;                    &lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;npm run build&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;npm&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;arguments&lt;/span&gt;&amp;gt;run build&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;arguments&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;executions&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;plugins&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;build&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;project&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Maven verwendet das Plugin&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;frontend-maven-plugin&amp;nbsp;&lt;/span&gt;zum Bauen der Vue.js Anwendung. Ausführliche Infos dazu gibt es hier: &lt;a href=&quot;https://github.com/eirslett/frontend-maven-plugin&quot;&gt;https://github.com/eirslett/frontend-maven-plugin&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Die hier gezeigt Plugin Konfiguration lädt und installiert zuerst npm und nodejs.&lt;/li&gt;&lt;li&gt;Danach wird &lt;span style=&quot;font-family: courier;&quot;&gt;npm install&lt;/span&gt; ausgeführt, um die Packages für das Vue.js Frontend zu laden.&lt;/li&gt;&lt;li&gt;Im letzten Schritt wird das Frontend mit npm gebaut, so wie&amp;nbsp;wir es im vorherigen Abschnitt von Hand gemacht haben. Das Plugin führt dazu den Befehl &lt;span style=&quot;font-family: courier;&quot;&gt;npm run build&lt;/span&gt; aus.&lt;/li&gt;&lt;li&gt;Die hier gezeigte Maven Konfiguration ist Teil eines &lt;a href=&quot;https://www.baeldung.com/maven-multi-module&quot; target=&quot;_blank&quot;&gt;Maven Multi-Module Projektes&lt;/a&gt;. Deshalb gibt es ein &lt;span style=&quot;font-family: courier;&quot;&gt;parent&lt;/span&gt; Tag.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven Parent Konfiguration&lt;/h3&gt;&lt;div&gt;Das Parent Projekt bildet die Klammer, um das Frontend und Backend Maven Modul. Ohne Maven Parent Projekt, müsstet ihr Frontend und Backend getrennt bauen - also Maven 2 mal starten. Mit Parent Projekt startet ihr nur dessenn Maven Build. Der Build des Parent Projektes steuert die Maven Builds vom frontend und backend Modul.&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 18,0pt;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xml version&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;1.0&quot; &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;encoding&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;?&amp;gt;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;project &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xmlns&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=...&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;4.0.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-starter-parent&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;3.0.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;relativePath&lt;/span&gt;/&amp;gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #8c8c8c; font-style: italic;&quot;&gt;   &lt;/span&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;de.bsi&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;vue&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;vue&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;description&lt;/span&gt;&amp;gt;Demo project for Spring Boot with Vue.js&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;description&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;packaging&lt;/span&gt;&amp;gt;pom&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;packaging&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;   &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;modules&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;module&lt;/span&gt;&amp;gt;frontend&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;module&lt;/span&gt;&amp;gt;&lt;br /&gt;      &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;module&lt;/span&gt;&amp;gt;backend&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;module&lt;/span&gt;&amp;gt;&lt;br /&gt;   &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;modules&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;project&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Das Parent Projekt ist ein Spring Boot Projekt (siehe &lt;span style=&quot;font-family: courier;&quot;&gt;parent&lt;/span&gt; Tag), so dass auch die beiden Module Spring Boot Projekte sind. Für das &lt;span style=&quot;font-family: courier;&quot;&gt;frontend &lt;/span&gt;Modul wäre das nicht nötig.&lt;/li&gt;&lt;li&gt;Der Maven Build baut entsprechend der Reihenfolge im XML erst das &lt;span style=&quot;font-family: courier;&quot;&gt;frontend&lt;/span&gt; und dann das &lt;span style=&quot;font-family: courier;&quot;&gt;backend &lt;/span&gt;Modul. &lt;i&gt;Hinweis: Ein paralleler Build mit mehreren Threads würde hier Fehler produzieren.&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Zur Verdeutlichung die Verzeichnisstruktur sieht in meinem Beispiel so aus:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;i&gt;vue &lt;/i&gt;(&lt;i&gt;pom.xml&lt;/i&gt; für Maven parent Projekt)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;backend &lt;/i&gt;(&lt;i&gt;pom.xml&lt;/i&gt; für Maven Modul Spring Boot Backend)&lt;/li&gt;&lt;li&gt;&lt;i&gt;frontend &lt;/i&gt;(&lt;i&gt;pom.xml&lt;/i&gt; für Maven Modul Vue.js Frontend)&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven Backend Konfiguration&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;Die Maven Konfiguration des Backend Moduls sieht so aus:&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 18,0pt;&quot;&gt;&lt;pre&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xml version&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;1.0&quot; &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;encoding&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;?&amp;gt;&lt;br /&gt;&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;project &lt;/span&gt;&lt;span style=&quot;color: #174ad4;&quot;&gt;xmlns&lt;/span&gt;&lt;span style=&quot;color: #067d17;&quot;&gt;=...&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;4.0.0&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;modelVersion&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;de.bsi&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;vue&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;parent&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;backend&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;0.2&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;backend&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;description&lt;/span&gt;&amp;gt;Backend implemented with Spring Boot Java stack.&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;description&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;properties&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;java.version&lt;/span&gt;&amp;gt;17&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;java.version&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;properties&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-starter-webflux&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;dependencies&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;build&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;plugins&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;org.springframework.boot&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;spring-boot-maven-plugin&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;br /&gt;            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;maven-resources-plugin&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;executions&lt;/span&gt;&amp;gt;&lt;br /&gt;                    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;copy Vue.js frontend content&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;phase&lt;/span&gt;&amp;gt;generate-resources&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;phase&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;copy-resources&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goal&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;goals&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;outputDirectory&lt;/span&gt;&amp;gt;src/main/resources/public&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;outputDirectory&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;overwrite&lt;/span&gt;&amp;gt;true&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;overwrite&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;resources&lt;/span&gt;&amp;gt;&lt;br /&gt;                                &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;resource&lt;/span&gt;&amp;gt;&lt;br /&gt;                                    &amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;directory&lt;/span&gt;&amp;gt;${project.parent.basedir}/frontend/dist&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;directory&lt;/span&gt;&amp;gt;&lt;br /&gt;                                &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;resource&lt;/span&gt;&amp;gt;&lt;br /&gt;                            &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;resources&lt;/span&gt;&amp;gt;&lt;br /&gt;                        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;configuration&lt;/span&gt;&amp;gt;&lt;br /&gt;                    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;execution&lt;/span&gt;&amp;gt;&lt;br /&gt;                &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;executions&lt;/span&gt;&amp;gt;&lt;br /&gt;            &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;plugin&lt;/span&gt;&amp;gt;&lt;br /&gt;        &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;plugins&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;build&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;project&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;backend &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;frontend &lt;/span&gt;Modul haben dasselbe Parent Projekt.&lt;/li&gt;&lt;li&gt;Es gibt ein besonderes Plugin, das &lt;span style=&quot;font-family: courier;&quot;&gt;maven-resources-plugin&lt;/span&gt;. Dieses kopiert das &lt;i&gt;/dist&lt;/i&gt; Verzeichnis aus dem &lt;span style=&quot;font-family: courier;&quot;&gt;frontend &lt;/span&gt;Modul in das &lt;i&gt;src/main/resources/public&lt;/i&gt; Verzeichnis des &lt;span style=&quot;font-family: courier;&quot;&gt;backend &lt;/span&gt;Moduls. Der eingebettete Application Server von Spring Boot liefert dann die statischen Frontend-Dateien aus diesem Verzeichnis aus, wie anfangs gezeigt.&lt;/li&gt;&lt;li&gt;Die restliche Maven Konfiguration des Backends entspricht herkömmlichen Spring Boot Projekten.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Abschluss&lt;/h2&gt;&lt;div&gt;&lt;div&gt;Mit&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;mvn clean package&lt;/span&gt;&amp;nbsp;bauen wir im Verzeichnis mit der parent POM das komplette Projekt. Die jar Datei im&amp;nbsp;&lt;i&gt;backend/target&lt;/i&gt;&amp;nbsp;Verzeichnis enthält danach das Spring Boot Backend und das Frontend.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Das jar File könnten wir dann in einen &lt;a href=&quot;https://agile-coding.blogspot.com/2020/11/java-in-docker.html&quot; target=&quot;_blank&quot;&gt;Docker Container packen&lt;/a&gt;. Dann würde ein Docker Container Frontend und Backend ausliefern, so wie wir es von klassischen Web-Anwendungen kennen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den kompletten Code findet ihr in GitHub:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/vue&quot;&gt;https://github.com/elmar-brauch/vue&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/02/spring-vue-js.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLSKp_XHfAou2OTKbjKSRaQEpF8YYWbUiSomAz5uUDVZPrb2PjjkurEqMGZWOSkeEMsdfO1j3mSPwRtZU47rij005A4PmCeRYn2lKml23B5PvNXhcQe-WscYBqtKaUbqbvTWWP9ypcfqIAWiBk29O12kvpOulkMJRTDdS-qu5k6oJdkBtYanHEofEPSw/s72-w640-h418-c/Vue_js_Spring_Boot.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Budapest, Ungarn</georss:featurename><georss:point>47.497912 19.040235</georss:point><georss:box>19.187678163821154 -16.116015 75.808145836178852 54.196484999999996</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-2610705971766762912</guid><pubDate>Fri, 24 Feb 2023 20:05:00 +0000</pubDate><atom:updated>2023-02-24T21:05:39.637+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Security</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>OpenID Connect mit Spring Boot 3</title><description>&lt;p&gt;&lt;b&gt;Authentifizierung mit OpenID Connect geht einfach dank Spring Boot. Wir bauen den Login Deiner Web-Anwendung mit dem Autorisierungsserver Deiner Firma. Wie wir dazu OpenID Connect mit Spring Boot 3 konfigurieren, zeige ich in diesem Blog-Artikel.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;b&gt;OpenID Connect - Authorization Code Prozess&lt;/b&gt;&lt;/h2&gt;&lt;div&gt;OpenID Connect ist ein Single Sign-On Login-Verfahren. Zur Umsetzung komplexer Anwendungsfälle verwenden größere Firmen meist verteilte Systeme. Damit der Benutzer z. B. beim Online-Shopping den Systemwechsel von Produktseiten zum Einkaufswagen und zur Kasse nicht wahrnimmt, loggt er sich mittels Single Sign-On nur einmal ein. Die beteiligten Systeme authentifizieren den eingeloggten Benutzer anhand seiner Single Sign-On Session.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Detaillierte Informationen über OpenID Connect und Single Sign-On findet ihr &lt;a href=&quot;https://openid.net/&quot; target=&quot;_blank&quot;&gt;hier&lt;/a&gt;. In diesem Artikel fokussiere ich mich auf das Anwendungs-System, welches zur Benutzer-Authentifizierung den firmeneigenen OpenID Identity Provider verwendet. Bevor wir uns die Implementierung mit Spring Boot und Spring Security anschauen, betrachten wir im folgenden Schaubild den vereinfachen OpenID Connect Ablauf.&lt;br /&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhSy2J3KX5hgOLSQ9FeJQ0XvizwjTBfx0SprYGuSaQXj20iFBuqzeeGJS4-5hKJ-svn-L_xRnz-AIAxPNWdfngYz3DcGZZg_W6nwISOxIMpmDZb6V3LctiwjeuVkh4WHFuq-ZvoZPE0IsNQnHhedd5_hj2ExIzUU7x3M27Y5x9YnVmGeVVybWKrexwlzg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1323&quot; data-original-width=&quot;2672&quot; height=&quot;316&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhSy2J3KX5hgOLSQ9FeJQ0XvizwjTBfx0SprYGuSaQXj20iFBuqzeeGJS4-5hKJ-svn-L_xRnz-AIAxPNWdfngYz3DcGZZg_W6nwISOxIMpmDZb6V3LctiwjeuVkh4WHFuq-ZvoZPE0IsNQnHhedd5_hj2ExIzUU7x3M27Y5x9YnVmGeVVybWKrexwlzg=w640-h316&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Der Benutzer ruft eine URL unseres Spring Systems auf, die nur von eingeloggten Benutzern verwendet werden darf.&lt;/li&gt;&lt;li&gt;Der anonyme Benutzer wird von unserem System zum Single Sign-On an den OpenID Provider unserer Firma weitergeleitet.&lt;/li&gt;&lt;li&gt;Nach erfolgreichem Login&amp;nbsp;z. B. mittels Benutzername und Passwort leitet der OpenID Provider den Benutzer an unserem System zurück. Als Beweis für den erfolgreichen Login enthält der Weiterleitungs-Request einen Authorization Code.&lt;/li&gt;&lt;li&gt;Unser System ruft nun den OAuth2 Token Endpunkt des OpenID Providers auf, um den Authorization Code gegen ein OpenID Token einzutauschen. Das OpenID Token ist im JWT Format und enthält Benutzer-Informationen und eine Signatur.&lt;/li&gt;&lt;li&gt;Nach erfolgreicher Validierung des OpenID Tokens gilt der Benutzer als authentifiziert und ist für die ursprünglich aufgerufene URL berechtigt.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Achtung Spoiler: Zur Umsetzung dieses Prozesses konfigurieren wir dank Spring Boot nur URLs und Credentials.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;303&quot; src=&quot;https://www.youtube.com/embed/lKXwxDM9pOk&quot; width=&quot;443&quot; youtube-src-id=&quot;lKXwxDM9pOk&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;i&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Englisches Video zum Artikel: Single Sign-On bei der Deutschen Telekom&lt;/i&gt;&lt;/div&gt;&lt;/i&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Gradle Dependencies für OpenID Connect&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;Die Build-Dependencies managt Spring Boot für uns. Wir fügen nur die richtige Spring Boot Starter Dependency hinzu, um in unserem Projekt OpenID Connect zu nutzen. Mein Demo-Projekt ist eine &lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html&quot; target=&quot;_blank&quot;&gt;Thymeleaf Web-Anwendung&lt;/a&gt;. Die Authentifizierung mit OpenID Connect benötigt&amp;nbsp;nur die fett markierte Dependency - der Rest ist nur für meine Demo.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;dependencies {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;implementation &#39;org.springframework.boot:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;spring-boot-starter-oauth2-client&#39;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;implementation &#39;org.springframework.boot:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;spring-boot-starter-web&#39;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;implementation &#39;org.springframework.boot:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;spring-boot-starter-thymeleaf&#39;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; ...&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die benötigte Spring Security Dependency kommt automatisch mit der&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring-boot-starter-oauth2-client&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;Dependency.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Single Sign-On beim firmeneigenen OpenID Provider&lt;/h2&gt;&lt;div&gt;Den Single Sign-On mit OpenID konfigurieren wir mit Spring Boot:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;in der &lt;i&gt;application.properties&lt;/i&gt; oder &lt;i&gt;application.yml&lt;/i&gt; Datei in Form von Key-Value-Properties&lt;/li&gt;&lt;li&gt;und im Java Code durch die Definition einer&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain &lt;/span&gt;Bean.&lt;br /&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Konfiguration in Spring Properties&lt;/h3&gt;&lt;div&gt;Meine Firma, die Deutsche Telekom AG, hat ein eigenes Identity &amp;amp; Access Management System. (Fast) alle Systeme verwenden dieses IAM-System für Login bzw. Single Sign-On. Dazu agiert dieses IAM-System als OpenID Provider, den ich in der &lt;i&gt;application.yml&lt;/i&gt;&amp;nbsp;Datei als OpenID Connect Schnittstelle konfiguriere.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;spring.security.oauth2.client:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; provider:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; sam:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; issuer-uri: &#39;https://id.provider.de&#39;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; registration:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; sam:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; authorization-grant-type: authorization_code&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; client-id: id&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; client-secret: password&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; redirect-uri: &#39;http://localhost:8080/process-login&#39;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; scope: openid&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;sam&lt;/span&gt; ist der Name des Deutsche Telekom IAM-Systems. Ihr könnt einen beliebige andere Namen verwenden. Wenn ihr &lt;span style=&quot;font-family: courier;&quot;&gt;github&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;google &lt;/span&gt;oder &lt;span style=&quot;font-family: courier;&quot;&gt;facebook &lt;/span&gt;als IAM-System verwendet, braucht ihr nur &lt;span style=&quot;font-family: courier;&quot;&gt;client-id&lt;/span&gt; und &lt;span style=&quot;font-family: courier;&quot;&gt;client-secret&lt;/span&gt; zu setzen, da alles andere für diese wohlbekannten Identity-Provider in Spring Boot vorkonfiguriert ist.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;client-id &amp;amp; client-secret&lt;/span&gt;&amp;nbsp;sind die Credentials meines Systems beim OpenID Provider.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;issuer-uri&lt;/span&gt; zeigt auf den OpenID Connect Discovery Endpunkt des OpenID Providers. Der Discovery Endpunkt zeigt dann z. B. alle weiteren URLs die beim OpenID Connect Verfahren verwendet werden. Diese müssen wir aufgrund der Öffentlichkeit des Discovery Endpunkts aber nicht extra in der Spring Konfiguration hinterlegen. Bei Google als OpenID Provider ist das der&amp;nbsp;Discovery Endpunkt&amp;nbsp;&lt;a href=&quot;https://accounts.google.com/.well-known/openid-configuration&quot;&gt;https://accounts.google.com/.well-known/openid-configuration&lt;/a&gt;&amp;nbsp;- im JSON dieses Endpunktes steht die für Google zu verwendende &lt;span style=&quot;font-family: courier;&quot;&gt;issuer-uri&lt;/span&gt; &lt;span style=&quot;font-family: inherit;&quot;&gt;&quot;https://accounts.google.com&quot;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;scope &lt;/span&gt;und &lt;span style=&quot;font-family: courier;&quot;&gt;authorization-grant-type&lt;/span&gt;&amp;nbsp;setze ich auf die oben gezeigte Werte, um OpenID Connect mit dem Authorization Code Prozess als Single Sign-On Verfahren festzulegen.&lt;/li&gt;&lt;li&gt;Nach erfolgreichem Login am OpenID Provider wird der Benutzer auf die in&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;redirect-uri&lt;/span&gt;&amp;nbsp;konfigurierte URL weitergeleitet. Diese URL muss auf unser System zeigen und mit der Login-Processing-URL übereinstimmen, die wir im nächsten Abschnitt in der&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&amp;nbsp;&lt;/span&gt;&lt;span&gt;Bean&amp;nbsp;&lt;/span&gt;setzen.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Authentifizierung mit SecurityFilterChain Bean&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die gesicherten URL-Pfade und den Login definieren wir im Java Code der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Bean.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableWebSecurity&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class SecurityConfiguration {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;SecurityFilterChain securityFilterChain(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;HttpSecurity http) throws Exception {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;http.authorizeHttpRequests()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.requestMatchers(&quot;/secured&quot;).authenticated()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.anyRequest().permitAll();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;http.oauth2Login(c -&amp;gt; {&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp;c.loginProcessingUrl(&quot;/process-login&quot;);&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;c.loginPage(&quot;/oauth2/authorization/sam&quot;);&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;});&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return http.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableWebSecurity&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpSecurity&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;authorizeHttpRequests&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;requestMatchers&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;authenticated&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;anyRequest &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;permitAll &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;sind in meinem &lt;/span&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2022/09/spring-security-roles.html&quot; style=&quot;font-family: inherit;&quot; target=&quot;_blank&quot;&gt;Artikel zu Spring Security&lt;/a&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; erklärt.&amp;nbsp;Zusammengefasst definiert der nicht fett markierte Java Code, dass die URL http://localhost:8080/secured einen eingeloggten Benutzer verlangt. Alle andern URLs können auch von &lt;/span&gt;anonymen&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;Benutzern aufgerufen werden.&lt;br /&gt;Mit Spring Boot 3 muss &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;gesetzt werden, damit die&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain &lt;/span&gt;Bean erzeugt wird.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Das OpenID Token zur Benutzer-Authentifikation wird mittels OAuth2 vom OpenID Provider geholt. Daher definieren wir mit der Methode&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;oauth2Login &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;einen OAuth2 basierten Login.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Der Lambda Ausdruck als Parameter von&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;oauth2Login&amp;nbsp;&lt;/span&gt;legt weitere Details zum Login fest.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die Login Seite befindet sich beim OpenID Provider. Sie wird von Spring über den OpenID Connect Discover Endpunkt ermittelt. &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;loginPage &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;legt die URL fest von der unser Spring Web-Application an den OpenID Provider Login weiterleitet, wenn sie den Prefix &quot;/oauth2/authorization/&quot; und den Postfix passend zum Key in der application.yml Datei hat - also hier &quot;sam&quot;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;loginProcessingUrl&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;ist die URL, welche den Redirect des OpenID Providers nach erfolgreichem Login verarbeitet. Diese URL muss aus Sicherheitsgründen auch am OpenID Provider konfiguriert sein.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Benutzerdaten im ID Token lesen&lt;/h3&gt;&lt;div&gt;Nach erfolgreichem Login holt Spring Boot automatisch das OpenID Token vom Token-Endpunkt des OpenID Providers. Dazu ist nichts weiter nötig als die zuvor gezeigte Konfiguration. Um die Daten im OpenID Token zu lesen, lassen wir es uns per Annotation von Spring injizieren:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@GetMapping&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public ModelAndView defaultPage(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@AuthenticationPrincipal OidcUser principal&lt;/b&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;String&amp;nbsp;email =&amp;nbsp;principal.getEmail();&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Map&amp;lt;String, Object&amp;gt; attributes = principal.getAttributes();&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die &lt;span style=&quot;font-family: courier;&quot;&gt;defaultPage &lt;/span&gt;Methode befindet sich in einem Spring Controller und verarbeitet HTTP-Requests. Weitere Details zu Spring Controllern findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2020/10/spring-mvc-thymeleaf.html&quot;&gt;spring-mvc-thymeleaf.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@AuthenticationPrincipal&lt;/span&gt;&amp;nbsp;injiziert den eingeloggte Benutzer automatisch als &lt;span style=&quot;font-family: courier;&quot;&gt;OidcUser &lt;/span&gt;Objekt in unsere Methode.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Die &lt;span style=&quot;font-family: courier;&quot;&gt;OidcUser &lt;/span&gt;Klasse repräsentiert die Inhalte des OpenID Tokens. &lt;span style=&quot;font-family: courier;&quot;&gt;OidcUser &lt;/span&gt;bietet diverse Methode zum Auslesen der Daten im ID Token an.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Z. B. liefert &lt;span style=&quot;font-family: courier;&quot;&gt;getEmail &lt;/span&gt;die Email-Adresse des eingeloggten Benutzers.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;getAttributes &lt;/span&gt;liefert alle Attribute im ID Token als Map.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Front-Channel Logout&lt;/h2&gt;&lt;div&gt;In den vorherigen Abschnitten haben wir nur den Single Sign-On Login implementiert. Der Standard Logout von Spring melden den Benutzer nur in der Session der Spring Web-Application ab. Nach dem Standard Logout besteht immer noch eine Single Sign-On Session am OpenID Provider. Deshalb kann der &quot;abgemeldete&quot; Benutzer gesicherte URLs ohne erneuten Login direkt wieder verwenden. Um den Benutzer richtig abzumelden, machen wir im Zuge des Logouts zusätzlich einen Front-Channel Logout am OpenID Provider. Dazu ergänzen wir die&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&amp;nbsp;&lt;/span&gt;&lt;span&gt;Bean wie folgt:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;var rs =&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;new DefaultRedirectStrategy();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;http.logout()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.logoutRequestMatcher(&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;new AntPathRequestMatcher(&quot;/logout&quot;))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.logoutSuccessHandler(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;(request, response, auth)&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;-&amp;gt; rs&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.sendRedirect(&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;request, response, &quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;https://id.provider.de/logout&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;logout &lt;/span&gt;liefert uns den &lt;span style=&quot;font-family: courier;&quot;&gt;LogoutConfigurer&lt;/span&gt;. Mit diesem konfigurieren wir&amp;nbsp;das Logout-Verhalten unser Spring Anwendung.&lt;/li&gt;&lt;li&gt;Für eine einfachere Demo lege ich fest, dass der Logout mit der URL /logout gestartet wird. Damit können auch HTTP GET Requests den Logout starten. Ohne die hier verwendete Methode &lt;span style=&quot;font-family: courier;&quot;&gt;logoutRequestMatcher &lt;/span&gt;wäre der Logout nur mit HTTP POST Requests möglich.&lt;/li&gt;&lt;li&gt;Front-Channel Logout bedeutet, dass wir den Logout auch am OpenID Provider machen. Das könnte wie hier mit einem einfachen Redirect zur Logout-URL des OpenID Providers passieren. Durch &lt;span style=&quot;font-family: courier;&quot;&gt;logoutSuccessHandler &lt;/span&gt;lege ich fest, dass dieser Redirect nach erfolgreichem Logout an unserer Spring Anwendung gestartet wird.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Schaut euch noch mal das Schaubild zum OpenID Connect Prozess an. Trotz der Komplexität dieses Prozesses&amp;nbsp;benötigen wir in unser Spring Boot Anwendung fast keinen eigenen Code. Das ist für mich die große Stärke von Spring Boot - Autokonfiguration und Starter Dependencies schenken uns eine einfache Lösung. Entsprechend eurer Security-Anforderungen könnt ihr diese Standard-Lösung für eure Firma anpassen. Achtet aber darauf, dass der Standard erkennbar bleibt, so dass der Single Sign-On Prozess eurer Anwendung wartbar bleibt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den Code zum Artikel findet ihr bei GitHub:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/spring-security-webapp/tree/openid&quot;&gt;https://github.com/elmar-brauch/spring-security-webapp/tree/openid&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2022/10/openid.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEhSy2J3KX5hgOLSQ9FeJQ0XvizwjTBfx0SprYGuSaQXj20iFBuqzeeGJS4-5hKJ-svn-L_xRnz-AIAxPNWdfngYz3DcGZZg_W6nwISOxIMpmDZb6V3LctiwjeuVkh4WHFuq-ZvoZPE0IsNQnHhedd5_hj2ExIzUU7x3M27Y5x9YnVmGeVVybWKrexwlzg=s72-w640-h316-c" height="72" width="72"/><thr:total>6</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-77863912637665628</guid><pubDate>Sun, 19 Feb 2023 12:46:00 +0000</pubDate><atom:updated>2023-02-19T13:46:49.150+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Java</category><title>Streams vs. Loops in Java</title><description>&lt;p&gt;&lt;b&gt;Java&#39;s Stream API ist eine mächtige Alternative zu Schleifen. Modernere, reaktive Programmierung setzt voll auf Streams. Hier stelle ich Streams und Loops gegenüber, indem ich die gleiche Aufgabe mittels Stream und Schleife löse.&lt;/b&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Java Streams API&lt;/h2&gt;&lt;p&gt;Ein Stream ist ein Datenstrom. Mit der Java Stream API können Objekte in Datenströmen analysiert, bearbeitet, gefiltert und umgewandelt werden. Ich stelle mir Stream gerne als Fließband vor. Die Objekte werden von einer Quelle (z.B. &lt;span style=&quot;font-family: courier;&quot;&gt;List&lt;/span&gt;) auf das Fließband gepackt und durchlaufen verschiedene Stationen, an denen sie bearbeitet werden. Zum Abschluss werden sie in ein Ergebnis gepackt (z.B. neue &lt;span style=&quot;font-family: courier;&quot;&gt;List&lt;/span&gt;), welches dann im weiteren Programmcode benutzt werden kann.&lt;br /&gt;&lt;/p&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9v6tN0iVTkLTpjOpfjxzfBvHT1x-UV3WFeyHGlJ8G0OzpL1SH4ju3cOS47V__wCZ0HbjSTDxYt7Tya3Em2A_IN2DQZoFBQCMRs6uJfdPnjvpVY_ULJDVIET98SbrYd6qHUG2PZaAhLEEG/s1280/factory-35104_1280.png&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;834&quot; data-original-width=&quot;1280&quot; height=&quot;260&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9v6tN0iVTkLTpjOpfjxzfBvHT1x-UV3WFeyHGlJ8G0OzpL1SH4ju3cOS47V__wCZ0HbjSTDxYt7Tya3Em2A_IN2DQZoFBQCMRs6uJfdPnjvpVY_ULJDVIET98SbrYd6qHUG2PZaAhLEEG/w400-h260/factory-35104_1280.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Fließband: Stream der analogen Welt&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Intermediate Operations&lt;/h3&gt;&lt;p&gt;Zum Bearbeiten der Objekte im Stream gibt es sogenannte &quot;intermediate Operations&quot;, die im &lt;span style=&quot;font-family: courier;&quot;&gt;Stream &lt;/span&gt;Interface definiert sind:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;filter &lt;/span&gt;- entfernt Objekte aus dem Stream&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;map &lt;/span&gt;- wandelt Objekte um&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;peek &lt;/span&gt;- erlaubt Bearbeitung des Objekts und lässt es im Stream&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;distinct &lt;/span&gt;- entfernt gleiche Objekte aus dem Stream&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;flatMap &lt;/span&gt;- löst Collections innerhalb des Streams auf&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;sorted &lt;/span&gt;- sortiert die Objekte im Stream&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;skip &lt;/span&gt;- überspring Objekte&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;limit &lt;/span&gt;- begrenzt die Anzahl der bearbeitenden Objekte im Stream&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Die intermediate Operationen können in beliebiger Menge und Reihenfolge auf die Objekte im Stream angewendet werden - dazu zeige ich weiter unten Beispiele.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Terminal Operations&lt;/h3&gt;&lt;p&gt;Terminal Operations beenden die Verarbeitung der Daten im Stream und produzieren ein Ergebnis:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;forEach &lt;/span&gt;- bearbeitet jedes Objekt ein letztes Mal&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;collect &lt;/span&gt;- sammelt die Objekte am Ende des Streams z.B. in einer Liste&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;toArray &lt;/span&gt;- sammelt die Objekte am Ende des Streams in einem Array&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;min / max&lt;/span&gt; - findet das Minimum / Maximum Objekt im Stream.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;count &lt;/span&gt;- zählt die Elemente am Ende des Streams.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;reduce &lt;/span&gt;- reduziert alle Elemente am Ende des Streams zu einem einzelnen Ergebnis&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;anyMatch / allMatch / noneMatch&lt;/span&gt; - liefert ein Boolean Ergebnis bezüglich der Daten im Stream&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;findFirst / findAny&lt;/span&gt; - liefert als Ergebnis des Streams ein Optional mit dem gefunden Element&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Streams erlauben nur eine Terminal Operation, die den Stream beendet. Danach liefert der Stream keine Daten mehr und kann auch nicht mehr benutzt werden.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/6F3Tp1CVWz0&quot; width=&quot;431&quot; youtube-src-id=&quot;6F3Tp1CVWz0&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;i&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Video zum Artikel&lt;/i&gt;&lt;/div&gt;&lt;/i&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Streams vs. Loops Code Beispiele&lt;/h2&gt;&lt;p&gt;Im folgenden zeige ich einige doppelt implementierte Methoden. Eine Version verwendet Schleifen und die&amp;nbsp;andere Streams mit Lambda-Ausdrücken - weitere Infos zu Lambdas findet ihr hier: &lt;a href=&quot;https://agile-coding.blogspot.com/2021/05/lambda.html&quot;&gt;lambda.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Beispiel 1: map &amp;amp; collect&lt;/h3&gt;&lt;p&gt;Zuerst wird die Liste &lt;span style=&quot;font-family: courier;&quot;&gt;itemNames&lt;/span&gt; mit der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;stream()&lt;/span&gt; in einen &lt;span style=&quot;font-family: courier;&quot;&gt;Stream&amp;lt;String&amp;gt;&lt;/span&gt; umgewandelt - in allen folgenden Beispielen ist dies immer der erste Schritt. Danach wandelt die Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;map &lt;/span&gt;jeden String im Stream mit der funktionalen Methode&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;generateItem &lt;/span&gt;in ein Objekt vom Typ &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;um. Die abschließende Terminal Operation &lt;span style=&quot;font-family: courier;&quot;&gt;collect &lt;/span&gt;wandelt den Stream in eine neue &lt;span style=&quot;font-family: courier;&quot;&gt;Item&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;-Liste&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&lt;/span&gt;um und speichert diese im Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;items&lt;/span&gt;, welches in den folgenden Beispielen verwenden wird.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;final &lt;/span&gt;List&amp;lt;String&amp;gt; &lt;span style=&quot;color: #9876aa;&quot;&gt;itemNames &lt;/span&gt;= List.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Ardbeg&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Lagavulin&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Jim Beam&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Teeling&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Jameson&quot;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Johnny Walker&quot;&lt;/span&gt;)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;List&amp;lt;Item&amp;gt; &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;void &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;prepareDemoData&lt;/span&gt;() {&lt;br /&gt;    Function&amp;lt;String&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;Item&amp;gt; generateItem = &lt;br /&gt;            name -&amp;gt; &lt;span style=&quot;color: #cc7832;&quot;&gt;new &lt;/span&gt;Item(name&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;name.length())&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items &lt;/span&gt;= &lt;span style=&quot;color: #9876aa;&quot;&gt;itemNames&lt;/span&gt;.stream()&lt;br /&gt;            .map(generateItem).collect(Collectors.&lt;span style=&quot;font-style: italic;&quot;&gt;toList&lt;/span&gt;())&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;/pre&gt;&lt;p&gt;Im Beispiel mit Schleife muss das Attribut &lt;span style=&quot;font-family: courier;&quot;&gt;items &lt;/span&gt;zu Beginn instanziiert werden.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;void &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;prepareDemoData&lt;/span&gt;() {&lt;br /&gt;    &lt;span style=&quot;color: #9876aa;&quot;&gt;items &lt;/span&gt;= &lt;span style=&quot;color: #cc7832;&quot;&gt;new &lt;/span&gt;ArrayList&amp;lt;&amp;gt;()&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    for &lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;var &lt;/span&gt;name : &lt;span style=&quot;color: #9876aa;&quot;&gt;itemNames&lt;/span&gt;)&lt;br /&gt;        &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.add(&lt;span style=&quot;color: #cc7832;&quot;&gt;new &lt;/span&gt;Item(name&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;name.length()))&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;Beispiel 2: sorted &amp;amp; forEach&lt;/h3&gt;&lt;p&gt;Zum Sortieren der zuvor generierten&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;items-&lt;/span&gt;Liste muss die &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;Klasse in dieser Schleifen-Variante das Interface &lt;span style=&quot;font-family: courier;&quot;&gt;Comparable &lt;/span&gt;implementieren.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;List&amp;lt;Item&amp;gt; &lt;span style=&quot;color: #ffc66d;&quot;&gt;sortAndPrintItems&lt;/span&gt;() {&lt;br /&gt;    Collections.&lt;span style=&quot;font-style: italic;&quot;&gt;sort&lt;/span&gt;(&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    for &lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;var &lt;/span&gt;item : &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;)&lt;br /&gt;        System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;.println(item)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    return &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;Die Stream-Variante verwendet &lt;span style=&quot;font-family: courier;&quot;&gt;sorted &lt;/span&gt;zum Sortieren der Elemente im Stream. Die Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;müsste hier nicht das Interface &lt;span style=&quot;font-family: courier;&quot;&gt;Comparable &lt;/span&gt;implementieren, weil wir den Lambda-Ausdruck &lt;span style=&quot;font-family: courier;&quot;&gt;compareByName &lt;/span&gt;an &lt;span style=&quot;font-family: courier;&quot;&gt;sorted &lt;/span&gt;übergeben. &lt;span style=&quot;font-family: courier;&quot;&gt;Comparator &lt;/span&gt;ist ein funktionales Interface zum Festlegen der Sortierreihenfolge. Die Terminal Operation &lt;span style=&quot;font-family: courier;&quot;&gt;forEach &lt;/span&gt;funktioniert analog zur for-each-Schleife und wird auf jedes Element im Stream angewendet. In beiden Beispielen werden die &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;Objekte in der gleichen Reihenfolge in die Konsole geschrieben. Die ursprüngliche Liste &lt;span style=&quot;font-family: courier;&quot;&gt;items &lt;/span&gt;bleibt allerdings in der Stream-Variante unverändert, da die Sortierung hier nur im Stream passiert und nicht in der Quelle.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;List&amp;lt;Item&amp;gt; &lt;span style=&quot;color: #ffc66d;&quot;&gt;sortAndPrintItems&lt;/span&gt;() {&lt;br /&gt;    Comparator&amp;lt;Item&amp;gt; compareByName = (a&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;b) -&amp;gt; a.name().compareTo(b.name())&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().sorted(compareByName).forEach(System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;::println)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    return &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Beispiel 3: filter &amp;amp; count&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Bestimmt habt ihr schon Elemente in einer Liste gezählt, welche spezielle Bedingungen erfüllen. Die klassische Schleifen-Variante sieht dazu so aus:&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;long &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;countItemsById&lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;int &lt;/span&gt;id) {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;long &lt;/span&gt;counter = &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    for &lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;var &lt;/span&gt;item : &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;)&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;if &lt;/span&gt;(item.id() == id)&lt;br /&gt;            counter++&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    return &lt;/span&gt;counter&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;Mit der Stream API schreiben wir solche Operationen deutlich kompakter. Die &lt;span style=&quot;font-family: courier;&quot;&gt;filter &lt;/span&gt;Methode ersetzt das if-Statement und die Terminal Operation &lt;span style=&quot;font-family: courier;&quot;&gt;count &lt;/span&gt;übernimmt das Zählen.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;long &lt;/span&gt;&lt;span style=&quot;color: #ffc66d;&quot;&gt;countItemsById&lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;int &lt;/span&gt;id) {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().filter(it -&amp;gt; it.id() == &lt;span style=&quot;color: #b389c5;&quot;&gt;id&lt;/span&gt;).count()&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Beispiel 4: filter, peek &amp;amp; findFirst&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Im nächsten Beispiel sind 2 Operationen in einer Methode vermischt:&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Wir suchen das&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;in der Liste, dessen&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;name &lt;/span&gt;Attribut den übergebenen Substring enthält.&lt;/li&gt;&lt;li&gt;Außerdem loggen wir die Position des gefunden &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;in der Liste. Daher verwende ich hier auch die klassische for-Schleife.&lt;/li&gt;&lt;li&gt;Um &lt;span style=&quot;font-family: courier;&quot;&gt;NullPointerExceptions &lt;/span&gt;zu vermeiden, geben wir einen &lt;span style=&quot;font-family: courier;&quot;&gt;Optional &lt;/span&gt;zurück, siehe dazu auch &lt;a href=&quot;https://dzone.com/articles/optional-in-java&quot;&gt;https://dzone.com/articles/optional-in-java&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;Optional&amp;lt;Item&amp;gt; &lt;span style=&quot;color: #ffc66d;&quot;&gt;findBySubstringAndCountPosition&lt;/span&gt;(String substring) {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;for &lt;/span&gt;(&lt;span style=&quot;color: #cc7832;&quot;&gt;int &lt;/span&gt;i = &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;; &lt;/span&gt;i &amp;lt; &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.size()&lt;span style=&quot;color: #cc7832;&quot;&gt;; &lt;/span&gt;i++) {&lt;br /&gt;        &lt;span style=&quot;color: #cc7832;&quot;&gt;var &lt;/span&gt;item = &lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.get(i)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;        if &lt;/span&gt;(item.name().contains(substring)) {&lt;br /&gt;            System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;.println(&lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Loop: Item found at position: &quot; &lt;/span&gt;+ i)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;            return &lt;/span&gt;Optional.&lt;span style=&quot;font-style: italic;&quot;&gt;of&lt;/span&gt;(item)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;        &lt;/span&gt;}&lt;br /&gt;    }&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;return &lt;/span&gt;Optional.&lt;span style=&quot;font-style: italic;&quot;&gt;empty&lt;/span&gt;()&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;Zum Ausführen mehrerer Operationen auf einem Stream-Element verwenden wir die &lt;span style=&quot;font-family: courier;&quot;&gt;peek &lt;/span&gt;Methode. Das if-Statement wird erneut mittels &lt;span style=&quot;font-family: courier;&quot;&gt;filter &lt;/span&gt;Methode umgesetzt.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;Optional&amp;lt;Item&amp;gt; &lt;span style=&quot;color: #ffc66d;&quot;&gt;findBySubstringAndCountPosition1&lt;/span&gt;(String substring) {&lt;br /&gt;    &lt;span style=&quot;color: #cc7832;&quot;&gt;final var &lt;/span&gt;counter = &lt;span style=&quot;color: #cc7832;&quot;&gt;new &lt;/span&gt;ThreadLocal&amp;lt;Integer&amp;gt;()&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    &lt;/span&gt;counter.set(&lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #cc7832;&quot;&gt;    return &lt;/span&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream()&lt;br /&gt;            .peek(i -&amp;gt; &lt;span style=&quot;color: #b389c5;&quot;&gt;counter&lt;/span&gt;.set(&lt;span style=&quot;color: #b389c5;&quot;&gt;counter&lt;/span&gt;.get() + &lt;span style=&quot;color: #6897bb;&quot;&gt;1&lt;/span&gt;))&lt;br /&gt;            .filter(it -&amp;gt; it.name().contains(&lt;span style=&quot;color: #b389c5;&quot;&gt;substring&lt;/span&gt;))&lt;br /&gt;            .peek(i -&amp;gt; System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;.println(&lt;br /&gt;                    &lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;Stream: Item found at position: &quot; &lt;/span&gt;+ &lt;span style=&quot;color: #b389c5;&quot;&gt;counter&lt;/span&gt;.get()))&lt;br /&gt;            .findFirst()&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;p&gt;Da &lt;span style=&quot;font-family: courier;&quot;&gt;peek &lt;/span&gt;einen Lambda-Ausdruck als Eingabe benötigt und Lambda-Ausdrücke nur auf (effektiv) finalen Variablen arbeiten können, packe ich den Integer zum Zählen der Position in ein &lt;span style=&quot;font-family: courier;&quot;&gt;ThreadLocal &lt;/span&gt;Objekt. &lt;br /&gt;In diesem Fall gefällt mir die Schleifen-Variante besser. Generell ist es im Stream nicht möglich auf Elemente an bestimmten Positionen zuzugreifen. Bei Schleifen mit Index &lt;span style=&quot;font-family: courier;&quot;&gt;i&lt;/span&gt;&amp;nbsp;ist der Zugriff auf bestimmte Elemente aus der Liste (oder einem Array) kein Problem.&lt;br /&gt;Die Terminal Operation &lt;span style=&quot;font-family: courier;&quot;&gt;findFirst &lt;/span&gt;vereinfacht die Rückgabe, da sie entweder einen leeren oder einen &lt;span style=&quot;font-family: courier;&quot;&gt;Optional &lt;/span&gt;mit dem gefunden Element zurückgibt. Darum müssen wir uns in der Stream-Variante nicht kümmern.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Beispiel 5: noneMatch, allMatch &amp;amp; anyMatch&lt;/h3&gt;&lt;p&gt;In den letzten Beispielen stelle ich nur die Stream-Variante vor, da es Code-Ausschnitte meines Unit-Test sind.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;assertTrue(&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().noneMatch(it -&amp;gt; it.id() &amp;lt; &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;))&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;assertFalse(&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().allMatch(it -&amp;gt; it.id() &amp;lt; &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;))&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;br /&gt;&lt;/span&gt;assertTrue(&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().anyMatch(it -&amp;gt; it.id() &amp;gt; &lt;span style=&quot;color: #6897bb;&quot;&gt;0&lt;/span&gt;))&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die erste Assertion prüft mit dem &lt;span style=&quot;font-family: courier;&quot;&gt;noneMatch&lt;/span&gt;, dass kein einziges Element im Stream eine Id kleiner 0 hat.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Der &lt;span style=&quot;font-family: courier;&quot;&gt;allMatch &lt;/span&gt;in der zweiten Assertion ist das Gegenteil, daher verwende ich hier &lt;span style=&quot;font-family: courier;&quot;&gt;assertFalse &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;statt &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;assertTrue&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;Mit &lt;span style=&quot;font-family: courier;&quot;&gt;anyMatch &lt;/span&gt;prüfe ich, dass mindestens ein Element eine Id größer 0 hat - in unserer items Liste wäre hier auch &lt;span style=&quot;font-family: courier;&quot;&gt;allMatch &lt;/span&gt;möglich, da alle Ids größer 0 sind, siehe Beispiel 1.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Beispiel 6: collect mit groupingBy&lt;/h3&gt;&lt;p&gt;Im letzten Beispiel verwende ich die &lt;span style=&quot;font-family: courier;&quot;&gt;groupingBy &lt;/span&gt;Methode der Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Collectors &lt;/span&gt;innerhalb der Terminal Operation &lt;span style=&quot;font-family: courier;&quot;&gt;collect&lt;/span&gt;. &lt;span style=&quot;font-family: courier;&quot;&gt;groupingBy &lt;/span&gt;entsprich von der Funktionalität dem GROUP BY Statement aus SQL, siehe&amp;nbsp;&lt;a href=&quot;https://www.w3schools.com/sql/sql_groupby.asp&quot;&gt;https://www.w3schools.com/sql/sql_groupby.asp&lt;/a&gt;.&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().collect(Collectors.&lt;span style=&quot;font-style: italic;&quot;&gt;groupingBy&lt;/span&gt;(Item::id)).forEach(&lt;br /&gt;        (group&lt;span style=&quot;color: #cc7832;&quot;&gt;, &lt;/span&gt;elements) -&amp;gt; System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;.println(group + &lt;span style=&quot;color: #6a8759;&quot;&gt;&quot; : &quot; &lt;/span&gt;+ elements))&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Die &lt;span style=&quot;font-family: courier;&quot;&gt;Item &lt;/span&gt;Objekte innerhalb des Stream sortiert die &lt;span style=&quot;font-family: courier;&quot;&gt;collect &lt;/span&gt;Methode mit Hilfe des&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;Collectors&amp;nbsp;&lt;/span&gt;in eine &lt;span style=&quot;font-family: courier;&quot;&gt;Map&amp;lt;Integer, List&amp;lt;Item&amp;gt;&amp;gt;&lt;/span&gt;&amp;nbsp;ein. Die Konsolen Ausgabe sieht dann so aus:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;6 : [Item[name=Ardbeg, id=6]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;7 : [Item[name=Teeling, id=7], Item[name=Jameson, id=7]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;8 : [Item[name=Jim Beam, id=8]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;9 : [Item[name=Lagavulin, id=9]]&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;13 : [Item[name=Johnny Walker, id=13]]&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Da 2 &lt;span style=&quot;font-family: courier;&quot;&gt;Item&lt;/span&gt;-Objekte die gleiche Id 7 haben, befinden sie sich in der selben Gruppe. Alle anderen Gruppen haben nur ein &lt;span style=&quot;font-family: courier;&quot;&gt;Item&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Parallele Bearbeitung von Streams&lt;/h2&gt;&lt;p&gt;Die parallele Verarbeitung der Daten im Stream kann mit einer einzigen Methode der Stream API gestartet werden:&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.stream().parallel().forEach(System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;::println)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Die Methode &lt;span style=&quot;font-family: courier;&quot;&gt;parallel &lt;/span&gt;wandelt den Stream in einen parallelen Stream um, der von mehreren Threads bearbeitet wird. Das Schöne ist: Wir müssen uns nicht um die Threads kümmern - das macht Java für uns! Alternativ können wir direkt einen parallelen Stream erzeugen:&lt;/p&gt;&lt;pre style=&quot;background-color: #2b2b2b; color: #a9b7c6; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;items&lt;/span&gt;.parallelStream().forEach(System.&lt;span style=&quot;color: #9876aa; font-style: italic;&quot;&gt;out&lt;/span&gt;::println)&lt;span style=&quot;color: #cc7832;&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Streams und Schleifen sind gleich mächtig, verwenden aber unterschiedliche Stile:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Schleifen basieren auf dem imperativen Programmierstil, bei dem jeder Schritt festgelegt wird.&lt;/li&gt;&lt;li&gt;Streams basieren auf der funktionalen Programmierung. Daher bietet die Stream API Methoden an, deren Implementierung wir nicht kennen müssen. Wir beschreiben mit Lambda-Ausdrücken nur das Ergebnis.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Vermutlich sind mehr EntwicklerInnen mit dem imperativen Programmieren vertraut als mit dem funktionalen. Da die Stream API sich immer mehr verbreitet (siehe z.B. reaktive Programmierung &lt;a href=&quot;https://agile-coding.blogspot.com/2021/02/webflux.html&quot;&gt;webflux.html&lt;/a&gt;) und sehr wichtig ist, solltet Ihr beides beherrschen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den hier gezeigten Code findet ihr in GitHub:&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/elmar-brauch/java16/tree/master/src/main/java/de/bsi/stream&quot;&gt;https://github.com/elmar-brauch/java16/tree/master/src/main/java/de/bsi/stream&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2021/07/streams.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9v6tN0iVTkLTpjOpfjxzfBvHT1x-UV3WFeyHGlJ8G0OzpL1SH4ju3cOS47V__wCZ0HbjSTDxYt7Tya3Em2A_IN2DQZoFBQCMRs6uJfdPnjvpVY_ULJDVIET98SbrYd6qHUG2PZaAhLEEG/s72-w400-h260-c/factory-35104_1280.png" height="72" width="72"/><thr:total>1</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-7267858075229456693</guid><pubDate>Fri, 27 Jan 2023 07:00:00 +0000</pubDate><atom:updated>2023-01-29T08:07:34.232+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">AI</category><category domain="http://www.blogger.com/atom/ns#">Java</category><title>ChatGPT Chatbot in Java Web-Anwendung integrieren</title><description>&lt;p&gt;&lt;b&gt;Wie integrierst Du eine künstliche Intelligenz als Chatbot in Deine Web-Anwendung? Am einfachsten indem Du die GPT-3 API benutzt. Hier zeige ich, wie es mit Java funktioniert.&lt;/b&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;OpenAI Account und API Key&lt;/h2&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh5tMFWWJy_qJ8gN4rjdvF-L-PTaQPmdqXp2V1NVShe1UUXrLMN0EX2ufkOVhqKGm23dKfTiX_998i7FNj15AmULCsuHrivJHhJDxO0ERgpFf5IsUxOzAocNttOlhA4BYjWu6wmgEhCXOXEhujn_9cGHRhdz8dz131NcKtA24zF_6_gE53toBLiKuqBZg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;238&quot; data-original-width=&quot;235&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh5tMFWWJy_qJ8gN4rjdvF-L-PTaQPmdqXp2V1NVShe1UUXrLMN0EX2ufkOVhqKGm23dKfTiX_998i7FNj15AmULCsuHrivJHhJDxO0ERgpFf5IsUxOzAocNttOlhA4BYjWu6wmgEhCXOXEhujn_9cGHRhdz8dz131NcKtA24zF_6_gE53toBLiKuqBZg&quot; width=&quot;237&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;OpenAI bietet die GPT-3 API an, die auch ChatGPT verwendet. Willst Du sie nutzen? Dann geht es vor dem eigentlichen Programmieren in Java so los:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Eigenen Account auf&amp;nbsp;&lt;a href=&quot;https://openai.com/&quot;&gt;https://openai.com/&lt;/a&gt; erstellen.&lt;/li&gt;&lt;li&gt;Einloggen und eigenen API Key erstellen:&lt;br /&gt;&lt;a href=&quot;https://beta.openai.com/account/api-keys&quot;&gt;https://beta.openai.com/account/api-keys&lt;/a&gt;&lt;br /&gt;Der API Key berechtigt Deine Anwendung die GPT-3 API zu nutzen.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Bei YouTube findet ihr ein englisches und &lt;a href=&quot;https://youtu.be/8drfmHTOSMU&quot; target=&quot;_blank&quot;&gt;deutsches Video&lt;/a&gt; passend zu diesem Blog-Artikel.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&quot;&quot; class=&quot;BLOG_video_class&quot; height=&quot;266&quot; src=&quot;https://www.youtube.com/embed/RMBXTMoiXPI&quot; width=&quot;477&quot; youtube-src-id=&quot;RMBXTMoiXPI&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;i&gt;How-To use GPT-3 API video in English&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;GPT-3 API mit Java HttpClient aufrufen&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;Die GPT-3 API kann mit jeder Programmiersprache bzw. jedem HTTP Client verwendet werden. Ich zeige euch hier, wie es mit dem &lt;span style=&quot;font-family: courier;&quot;&gt;HttpClient &lt;/span&gt;von Java funktioniert.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String postBody =&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &quot;&lt;/span&gt;prompt&quot;: &quot;Code in Java to print first 20 Fibonacci numbers&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&quot;max_tokens&quot;: 300,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&quot;model&quot;: &quot;text-davinci-003&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&quot;temperature&quot;: 0.7&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;&quot;&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;		&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;var request = HttpRequest.newBuilder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.uri(URI.create(&quot;https://api.openai.com/v1/completions&quot;))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;.header(&quot;Content-Type&quot;, &quot;application/json&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;.header(&quot;Authorization&quot;, &quot;Bearer &quot; + OPENAI_API_KEY)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;.POST(BodyPublishers.ofString(postBody))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;		&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;var client = HttpClient.newHttpClient();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;var resp = client.send(request,HttpResponse.BodyHandlers.ofString());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;System.out.println(resp.body());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Wir definieren einen String &lt;span style=&quot;font-family: courier;&quot;&gt;postBody&lt;/span&gt; im JSON Format entsprechend der OpenAI API Dokumentation. Dieser String enthält im prompt die Nachricht bzw. hier die Programmieraufgabe die wir an die Chatbot API schicken. Die anderen JSON Attribute sind ausführlich in der API Dokumentation beschrieben:&amp;nbsp;&lt;a href=&quot;https://beta.openai.com/docs/api-reference/completions&quot;&gt;https://beta.openai.com/docs/api-reference/completions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpClient.newHttpClient()&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; erzeugt die Instanz des Java HTTP Clients zum Verschicken eines HTTP Requests.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Den eigentlichen &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpRequest &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;bauen wir mit einem Builder. Hier verwende ich nur die nötigsten Attribute des HttpRequests:&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Die Internet-Addresse mit &lt;span style=&quot;font-family: courier;&quot;&gt;uri&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Der Content-Type Header legt fest, dass wir einen JSON Text übertragen.&lt;/li&gt;&lt;li&gt;Der Authorization Header überträgt unseren API Key als Bearer Token.&lt;/li&gt;&lt;li&gt;Der POST Request Body bestehend aus unseren JSON String.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Mit der &lt;span style=&quot;font-family: courier;&quot;&gt;send &lt;/span&gt;Methode schicken wir den POST Request ab und legen fest, dass der Response Body als String eingelesen wird. Die Antwort ist dann ein JSON, welches wir in die Console schreiben:&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;id&quot;: &quot;cmpl-6aVIuAySr6W83S0xvbCgq2RUNc3hQ&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;object&quot;: &quot;text_completion&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;created&quot;: 1674158160,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;model&quot;: &quot;text-davinci-003&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;choices&quot;: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;text&quot;: &quot;public class Fibonacci {...&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;index&quot;: 0,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;logprobs&quot;: null,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;finish_reason&quot;: &quot;stop&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; ],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &quot;usage&quot;: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;prompt_tokens&quot;: 11,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;completion_tokens&quot;: 131,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;total_tokens&quot;: 142&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die Antwort der künstlichen Intelligenz befindet sich im Attribut &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;text &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;innerhalb der Liste &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;choices&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;. Den Java Code für die Fibonacci Folge habe ich hier gekürzt - probiert es selbst aus, um ihn komplett zu sehen.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Wenn ihr die GPT-3 API in einer Spring Web-Anwendungen sehen wollt, schaut euch einfach mein Projekt bei GitHub&amp;nbsp;&lt;a href=&quot;https://github.com/elmar-brauch/openai-api&quot;&gt;https://github.com/elmar-brauch/openai-api&lt;/a&gt; an. Dort nutze ich zusätzlich das Spring Framework, um eine Thymeleaf Web-Anwendung mit Chatbot zu bauen.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;ChatGPT bzw. die GPT-3 API ist eine beeindruckende künstliche Intelligenz. Es macht wirklich Spaß sie zu benutzen. Leider kostet jeder API Aufruf - daher werden auch die Token-Attribute im Request und der Response gesetzt, um die genauen Kosten transparent zu machen. Zum Ausprobieren gibt es aber ein kostenloses Startkapital.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2023/01/gpt-3.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEh5tMFWWJy_qJ8gN4rjdvF-L-PTaQPmdqXp2V1NVShe1UUXrLMN0EX2ufkOVhqKGm23dKfTiX_998i7FNj15AmULCsuHrivJHhJDxO0ERgpFf5IsUxOzAocNttOlhA4BYjWu6wmgEhCXOXEhujn_9cGHRhdz8dz131NcKtA24zF_6_gE53toBLiKuqBZg=s72-c" height="72" width="72"/><thr:total>1</thr:total><georss:featurename>München, Deutschland</georss:featurename><georss:point>48.1351253 11.5819806</georss:point><georss:box>19.824891463821153 -23.5742694 76.445359136178837 46.7382306</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-617277435583854255</guid><pubDate>Mon, 16 Jan 2023 06:52:00 +0000</pubDate><atom:updated>2023-01-16T07:52:41.910+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Microservice</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Asynchroner, gepufferter Nachrichten-Austausch mit RabbitMQ und Spring</title><description>&lt;p&gt;&lt;b&gt;RabbitMQ ist ein Nachrichten-Broker mit integrierter Queue zur asynchronen Kommunikation zwischen Services. Der Produzenten-Service übergibt Nachrichten an RabbitMQ ohne auf den Konsumenten-Service zu warten. Der Konsument holt sich Nachrichten aus der RabbitMQ Queue nach Bedarf und kann so nicht vom Produzent mit Nachrichten überflutet werden. Hier zeige ich die Spring AMQP Implementierung zur RabbitMQ Integration.&lt;/b&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Vorteile des asynchronen, gepufferten Nachrichten-Austauschs&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Nachrichten Produzent und Konsument müssen nicht aufeinander warten. Wenn der Produzent z. B. für den Benutzer einen Auftrag erstellt, muss der Benutzer nicht warten bis der Konsument den Auftrag erfolgreich verarbeitet hat. Der Produzent übergibt die Nachricht an die RabbitMQ Queue und kann dem Benutzer danach eine Erfolgsmeldung anzeigen. Das ist insbesondere bei lang andauernden Auftragsverarbeitungen im Backend (Konsument) angenehm für den Benutzer, da er nicht warten muss. Der Konsument könnte den Benutzer später per Email über die erfolgreiche Verarbeitung des Auftrags informieren.&lt;/li&gt;&lt;li&gt;Die RabbitMQ Queue dient auch als Puffer zwischen Produzent und Konsument. Wenn innerhalb kurzer Zeit viele Nachrichten produziert werden, können diese in der Queue gesammelt werden. Der Konsument holt sich dann die Nachrichten in seinem Tempo aus der Queue und bricht so auf keinen Fall unter Überlast zusammen.&lt;/li&gt;&lt;li&gt;Wenn Produzent und Konsument als Dienste in der Cloud betrieben werden, könnte die Skalierung dieser Dienste anhand des Füllstandes in der RabbitMQ Queue gemanagt werden. Bei einer vollen Queue könnten Konsumenten Dienste hoch skaliert werden, während sie bei einer sich leerenden Queue herunter skaliert werden. Wir können also Ressourcen effizient nutzen.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;RabbitMQ im Docker Container starten&lt;/h2&gt;&lt;div&gt;Zum schnellen Ausprobieren bietet sich RabbitMQ im Docker Container an. Wir starten RabbitMQ mit Management UI, damit wir die Queues im Browser prüfen können:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;docker run -d -p 15672:15672 -p 5672:5672 rabbitmq:3-management&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;15672 ist der Management UI Port, den wir mittels Port Weiterleitung erreichbar machen. Mit dieser URL können wir das Management UI im Browser öffenen:&lt;br /&gt;http://localhost:15672/&lt;br /&gt;Benutzername und Password zum Login ist: &lt;i&gt;guest &lt;/i&gt;/ &lt;i&gt;guest&lt;/i&gt;&lt;/li&gt;&lt;li&gt;5672 ist der RabbitMQ Port, den unsere Spring Anwendung verwenden wird.&lt;/li&gt;&lt;li&gt;Weitere Details zu Docker findet ihr &lt;a href=&quot;https://agile-coding.blogspot.com/2020/11/java-in-docker.html&quot;&gt;hier&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDhloifn5yDY5wuSUMc3Vq9kNG8UDnCjpeoFz1WvguPOF4ZlWemd6e_r-hLFIHCounWGj0SQVuBexKRd7t5t1hxqCiNTgD2dp-rX0G827M0eazCenMSyOCiNLZMPS2hjuLpHVDJ_DCeAfcKqYJaIn8vOMk_78tvl2GOSbRuMW6MvYIPLn2IywNIhrKiA/s1861/rabbitmq.PNG&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1012&quot; data-original-width=&quot;1861&quot; height=&quot;348&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDhloifn5yDY5wuSUMc3Vq9kNG8UDnCjpeoFz1WvguPOF4ZlWemd6e_r-hLFIHCounWGj0SQVuBexKRd7t5t1hxqCiNTgD2dp-rX0G827M0eazCenMSyOCiNLZMPS2hjuLpHVDJ_DCeAfcKqYJaIn8vOMk_78tvl2GOSbRuMW6MvYIPLn2IywNIhrKiA/w640-h348/rabbitmq.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;RabbitMQ Management UI&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Nachrichten-Produzenten Implementierung&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven &amp;amp; Spring Property Konfiguration&lt;/h3&gt;&lt;div&gt;Bevor die Implementierung des Produzenten startet, benötigen wir die Spring Starter Dependency von Spring AMQP. Hier der entsprechende Ausschnitt aus der Maven Konfiguration:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;artifactId&amp;gt;spring-boot-starter-amqp&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Außerdem konfigurieren wir in der Spring Properties-Datei&amp;nbsp;&lt;i&gt;application.yml&lt;/i&gt; die Verbindung zum RabbitMQ Server:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;spring.rabbitmq:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;host: localhost&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;port: 5672&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;password: guest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;username: guest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Produzenten Implementierung&lt;/h3&gt;&lt;div&gt;Die Implementierung des Produzenten ist eine Spring Bean, welche &lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;Objekte an RabbitMQ schickt:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Service&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class CommentProducerService {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Autowired private RabbitTemplate rabbitTemplate;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public void createComment() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;var comment = Comment.builder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.message(&quot;New comment message created&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;.author(&quot;Elmar&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;.createdAt(LocalDateTime.now())&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;		&lt;/span&gt;.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;rabbitTemplate.convertAndSend(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&quot;comments&quot;,&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;comment);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RabbitTemplate &lt;/span&gt;wird automatisch durch die Spring Boot Starter Dependency erzeugt.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;convertAndSend &lt;/span&gt;schickt die Instanz der Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;zum RabbitMQ Server in die Queue mit dem Namen &quot;comments&quot;. Diese Queue erzeugt ihr vorher im Management UI oder verwendet den Administrationscode weiter unten.&lt;/li&gt;&lt;li&gt;Die Klasse &lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;ist ein Java POJO mit 2 &lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;und einem &lt;span style=&quot;font-family: courier;&quot;&gt;LocalDateTime &lt;/span&gt;Attribute. Für die Instanziierung verwende ich einen Builder&amp;nbsp;anstelle von Konstruktor und Setter-Methoden.&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Nachrichten Serialisierung&lt;/h3&gt;&lt;div&gt;Wenn wir die Nachrichten im gängigen JSON Format zur RabbitMQ Queue schicken wollen, müssen wir einen entsprechenden Konverter konfigurieren. Dazu erzeugen wir eine Spring Bean vom Typ &lt;span style=&quot;font-family: courier;&quot;&gt;Jackson2JsonMessageConverter&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class RabbitmqConfig {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; white-space: pre;&quot;&gt;	&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;SmartMessageConverter messageConverter() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return new &lt;b&gt;Jackson2JsonMessageConverter&lt;/b&gt;(objectMapper());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ObjectMapper objectMapper() {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;var mapper = new ObjectMapper();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;mapper.registerModule(new JavaTimeModule());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return mapper;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Jackson2JsonMessageConverter&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Bean benötigt eine &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ObjectMapper &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Instanz zum Schreiben von Nachrichten-Objekten als AMQP JSON Nachrichten.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Beim &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ObjectMapper&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;musste das &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;JavaTimeModule &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;registriert werden, weil unsere Nachrichten Klasse &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;den Java 8 Datumstyp &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;LocalDateTime &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;verwendet.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Konsument an Queue registrieren&lt;/h2&gt;&lt;div&gt;Der Konsument benötigt zur Deserialisierung der &lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;Nachrichten die gleichen Konverter Beans wie der Produzent. Das Konsumieren von RabbitMQ Nachrichten aus einer Queue ist mit noch weniger Code als das Produzieren zu implementieren:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;@RabbitListener(queues =&amp;nbsp;&quot;comments&quot;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Component&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Slf4j&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class CommentConsumerService {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;@RabbitHandler&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;public void readComments(Comment comment) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;log.info(comment.toString());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die Annotation &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RabbitListener&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; definiert die Bean &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;CommentConsumerService &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;als Konsument der Queue mit dem Namen &quot;comments&quot;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die Annotation &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RabbitHandler&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; legt fest, dass die Methode &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;readComments &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Nachrichten des Typs &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;verarbeitet. Würde die Queue &quot;comment&quot; zusätzlich einfache &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Objekte als Nachrichten enthalten, könnten diese von einer 2. Methode mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@RabbitHandler&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Annotation und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;String &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Parameter verarbeitet werden.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Die &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;readComments &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Methode loggt hier lediglich die konsumierte &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Comment &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Nachricht.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;RabbitMQ administrieren mit Spring&lt;/h2&gt;&lt;div&gt;Spring bietet uns die Möglichkeit den RabbitMQ Nachrichten-Broker per Code zu administrieren. Hier zeige ich euch, wie ihr ohne Management UI eine neue Nachrichten Queue erstellt:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class RabbitmqConfig {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;	&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;RabbitAdmin createRabbitAdminAndInitQueue(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;RabbitTemplate rabbitTemplate) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;var admin = &lt;b&gt;new RabbitAdmin(rabbitTemplate)&lt;/b&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;if (admin.&lt;b&gt;getQueueInfo&lt;/b&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;comments&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;) != null)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;admin.&lt;b&gt;purgeQueue&lt;/b&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;comments&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;admin.&lt;b&gt;declareQueue&lt;/b&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;QueueBuilder.durable(&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;comments&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;).build());&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return admin;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RabbitAdmin &lt;/span&gt;ist die Bean zur Administration von RabbitMQ. Die Verbindung wird durch den Parameter &lt;span style=&quot;font-family: courier;&quot;&gt;RabbitTemplate &lt;/span&gt;festgelegt - wie auch schon zuvor beim Nachrichten Produzent.&lt;/li&gt;&lt;li&gt;&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;getQueueInfo &lt;/span&gt;verwende ich hier nur zum Prüfen, ob die Queue &quot;comments&quot; schon existiert.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;purgeQueue &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;löscht alle Nachrichten in einer Queue.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;declareQueue &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;erzeugt eine neue Queue basierend auf dem übergebenen Parameter. Die neue Queue wurde hier als dauerhafte Queue mit dem Namen &quot;comments&quot; definiert. Der &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;QueueBuilder &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;bietet natürlich noch weitere Optionen an, schaut euch dazu die public Methoden an.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;Die Vorteile von RabbitMQ bzw. eines Nachrichten-Brokers mit Queue habe ich am Anfang des Artikels bereits aufgelistet. In den Code Beispielen habe ich gezeigt, wie viel Programmierarbeit uns Spring abnimmt, da wir vieles per Konfiguration und Annotation einrichten können.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Weitere Details zu Spring Integration findet ihr hier:&lt;br /&gt;&lt;a href=&quot;https://spring.io/projects/spring-integration&quot;&gt;https://spring.io/projects/spring-integration&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Den kompletten Code zum Artikel findet ihr in GitHub:&lt;br /&gt;&lt;a href=&quot;https://github.com/elmar-brauch/rabbitmq&quot;&gt;https://github.com/elmar-brauch/rabbitmq&lt;/a&gt;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2022/11/rabbitmq.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDhloifn5yDY5wuSUMc3Vq9kNG8UDnCjpeoFz1WvguPOF4ZlWemd6e_r-hLFIHCounWGj0SQVuBexKRd7t5t1hxqCiNTgD2dp-rX0G827M0eazCenMSyOCiNLZMPS2hjuLpHVDJ_DCeAfcKqYJaIn8vOMk_78tvl2GOSbRuMW6MvYIPLn2IywNIhrKiA/s72-w640-h348-c/rabbitmq.PNG" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Berlin, Deutschland</georss:featurename><georss:point>52.520006599999988 13.404954</georss:point><georss:box>29.701690138936065 -21.751296 75.3383230610639 48.561204000000004</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-6863645710863581537</guid><pubDate>Sat, 07 Jan 2023 09:54:00 +0000</pubDate><atom:updated>2023-01-07T10:54:36.964+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Microservice</category><category domain="http://www.blogger.com/atom/ns#">OpenAPI</category><title>REST-API mit OpenAPI Spezifikation und Swagger generieren</title><description>&lt;p&gt;&lt;b&gt;REST-APIs können als OpenAPI Spezifikation menschen- und maschinenlesbar beschrieben werden. Die maschinenlesbare OpenAPI Spezifikation (vormals bekannt als Swagger Spezifikation) dient Code-Generatoren als Vorlage zum Generieren von Client- oder Server-Code in viele verschiedenen Programmiersprachen. Neben den Basics zeige ich euch hier, wie man den Code-Generator mit Maven verwendet, um API Implementierung und Spezifikation immer synchron zu halten.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;API first &amp;amp; Code Generatoren&lt;/h2&gt;&lt;p&gt;Beim &lt;a href=&quot;https://swagger.io/resources/articles/adopting-an-api-first-approach/&quot; target=&quot;_blank&quot;&gt;API first Konzept&lt;/a&gt; geht es darum, dass man von Anfang an, die API im Scope der Entwicklung hat. Die API wird vor der Implementierung definiert und kann auch schon mit anderen Teams ausgetauscht werden, welche den Service über die API nutzen. Wird die API als OpenAPI Spezifikation im json oder yaml Format definiert, können mehrere Teams den Client-Code zeitgleich mit der Service-Implementierung entwickeln.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Als Unterstützung für den API first Ansatz gibt es Code Generatoren, die für euch ein Client SDK zum Benutzen einer API in diversen Programmiersprachen oder Server Stubs im gewünschten Technologie Stack generieren.&lt;/p&gt;&lt;p&gt;Das generierte Client SDK ist sicherlich eine gute Starthilfe, wenn ihr mit einer für euch neuen Programmiersprache arbeitet oder generell noch nicht so erfahren im Programmieren von REST-Aufrufen seid. Wie es ohne Code Generator mit OAuth2-Absicherung funktioniert, zeige ich z.B. hier:&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/05/spring-oauth.html&quot;&gt;spring-oauth.html&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Beim generierten Server Stub solltet ihr beachten, dass vom Generator der erste Technologie-Stack eures Services festgelegt wird. Dabei sollte man aus meiner Sicht vorsichtig sein, da der generierte Technologie-Stack häufig leicht veraltet ist und man z.B. mit Spring Boot, die Dependencies deutlich aufgeräumter pflegen kann. Daher bevorzuge ich bei einfachen APIs die Projekt Generatoren des gewählten Frameworks, z.B.&amp;nbsp;&lt;a href=&quot;https://start.spring.io/&quot;&gt;https://start.spring.io/&lt;/a&gt;&amp;nbsp;für Spring.&lt;/p&gt;&lt;p&gt;Der generierte Code an sich ist je nach Programmiersprache gut lesbar (siehe z.B. Modell Klassen in Kotlin) oder voll von Boilerplate Code (siehe z.B. Modell Klassen in Java). Es ist sehr wichtig, dass ihr den generierten Code getrennt von eurem restlichen Code behandelt. Generierter Code sollte immer nur vom Generator geschrieben und nicht vom Entwickler angepasst werden. Daher hat dieser Artikel 2 Teile:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Code Generatoren für Einsteiger: Swagger Editor, OpenApi &amp;amp; Swagger CodeGen. Probiert die Generatoren aus, schaut euch genau an was ihr bekommt und überlegt euch, ob das eurer Startpunkt für die Entwicklung sein soll oder nicht. Ein Versuch kann auch nicht schaden.&lt;/li&gt;&lt;li&gt;Code Generatoren als Maven Plugin (in die CICD Pipeline integriert): In dieser Variante begeistern mich die Code Generatoren, da API Spezifikation und API Code immer synchron bleiben! Überspring ruhig die nächsten beiden Abschnitte, wenn es euch in erster Linie, um diese Variante geht.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Swagger Editor: Online Code Generator&lt;/h2&gt;&lt;div&gt;Wenn ihr eine OpenAPI Spezifikation habt, könnt ihr euch den Client-Code bzw. ein Client SDK automatisch generieren lassen. Der schnellste Weg dazu ist der Swagger Editor, eine Webseite zum Bearbeiten von API Spezifikationen und zur Online Code Generation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh419esBwXUsxmpxw5bHn1dmDOaMP0pjQgy3p16rzR3F5RTXCbUaNr2kDrRSvQuVHJSneSsM9Q0mfERoNhsq5Q9XRczJuLyzu15H7lxIArfY3Cgor-QgJOr9Cvi3y0UsrZ2nnUZtxXp4yzG/&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;685&quot; data-original-width=&quot;1878&quot; height=&quot;234&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh419esBwXUsxmpxw5bHn1dmDOaMP0pjQgy3p16rzR3F5RTXCbUaNr2kDrRSvQuVHJSneSsM9Q0mfERoNhsq5Q9XRczJuLyzu15H7lxIArfY3Cgor-QgJOr9Cvi3y0UsrZ2nnUZtxXp4yzG/w640-h234/image.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://editor.swagger.io/&quot;&gt;https://editor.swagger.io/&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Einfach json oder yaml Datei links im Editor laden, dann sollten (im kompatiblen Browser) die beiden beiden Menu-Punkte &quot;Generate Server&quot; und &quot;Generate Client&quot; erscheinen mit denen ihr den Code in der Programmiersprache bzw. dem Framework (z.B. Spring oder Micronaut) eurer Wahl generieren könnt.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Swagger &amp;amp; OpenAPI Code Generator&lt;/h2&gt;&lt;p&gt;Beim Swagger Editor hat man leider keine Möglichkeiten die Generation des Codes zu konfigurieren. Man kann nur die Programmiersprache oder das Framework auswählen, aber z.B. nicht in welchem Package sich die generierten Java-Klassen befinden sollen. Mit Swagger Codegen kann man das tun, siehe dazu&amp;nbsp;&lt;a href=&quot;https://github.com/swagger-api/swagger-codegen#readme&quot;&gt;https://github.com/swagger-api/swagger-codegen#readme&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Der OpenAPI Code Generator ist ein alternatives Projekt zum Generieren von Code anhand einer OpenAPI Spezifikation, das auf eine frühere Version des Swagger Code Generators aufsetzt. Weitere Details dazu findet ihr hier:&amp;nbsp;&lt;a href=&quot;https://github.com/OpenAPITools/openapi-generator/blob/master/README.md&quot;&gt;https://github.com/OpenAPITools/openapi-generator/blob/master/README.md&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In beiden Links findet ihr einen Link, um ein ausführbares jar-File herunterzuladen, mit dem ihr dann ein Client SDK oder einen Server Stub generieren könnt. Mit folgendem Befehl könnt ihr z.B. einen Kotlin Server Stub erstellen:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;java -jar openapi-generator-cli-6.2.1.jar generate \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-i ./openapi.yaml \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-g kotlin-server \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-o petstore&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;java -jar&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; führt eine ausführbare jar-Datei aus. Die&amp;nbsp;&lt;/span&gt;&lt;i&gt;openapi-generator-cli-6.2.1.jar&lt;/i&gt; ist in der zuvor verlinkten &lt;i&gt;README.md&lt;/i&gt; verlinkt. (In Zukunft gibt es hier bestimmt neuere Versionen als 6.2.1, den Befehl dann entsprechen anpassen.)&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;generate &lt;/span&gt;ist der Befehl zum Generieren von Code. &lt;span style=&quot;font-family: courier;&quot;&gt;help &lt;/span&gt;wäre eine Alternative zum Anzeigen des Hilfetextes.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;-i ./openapi.yaml&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; zeigt an, wo die OpenAPI Spezifikation ist. Ich habe die Datei &lt;i&gt;openapi.yaml&lt;/i&gt; einfach aus dem&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://editor.swagger.io/&quot;&gt;https://editor.swagger.io/&lt;/a&gt;&amp;nbsp;geladen, um dann die API des Demo Projektes Petstore zum Testen zu haben.&lt;/li&gt;&lt;li&gt;Mit &lt;span style=&quot;font-family: courier;&quot;&gt;-g&lt;/span&gt; kann man festlegen, was man generieren möchte. &lt;span style=&quot;font-family: courier;&quot;&gt;kotlin-server&lt;/span&gt; generiert einen Kotlin Server Stub. Alternativen und viele weitere Details findet ihr hier:&lt;br /&gt;&lt;a href=&quot;https://github.com/OpenAPITools/openapi-generator/wiki/Server-stub-generator-HOWTO&quot;&gt;https://github.com/OpenAPITools/openapi-generator/wiki/Server-stub-generator-HOWTO&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;-o &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;legt den Speicherort des generierten Codes fest.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Hier noch ein 2. Beispiel bei dem ich einen Spring Server Stub in Java generiere und zusätzlich eine Konfigurationsdatei &lt;i&gt;config.json&lt;/i&gt; übergebe. In der Konfigurationsdatei habe ich exemplarisch das Package für die generierten Java-Klassen selbst definiert.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; java -jar openapi-generator-cli-6.2.1.jar generate \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-i ./openapi.yaml \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-g spring \&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;-o ./petstore2 \&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; -c ./config.json&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; cat&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;./config.json&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&quot;basePackage&quot;:&quot;de.bsi.petstore&quot;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Weitere Konfigurationsparameter findet ihr in den zuvor verlinkten How-To Artikel oder im nächsten Abschnitt beim Einsatz als Maven Plugin.&lt;br /&gt;Die jar-Datei des Swagger Code Generators wird übrigens analog bedient.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Code Generatoren als Maven Plugin&lt;/h2&gt;&lt;div&gt;Der Swagger und der OpenAPI Code Generator können auch als Maven Plugin in den Build Prozess und in die CICD Pipeline integriert werden. Entscheidend dafür ist die Maven Konfiguration in der &lt;i&gt;pom.xml&lt;/i&gt; Datei. In meinem Demo-Projekt zu diesem Artikel lasse ich die Modell Klassen anhand der Petstore OpenAPI Spezifikation generieren. Das funktioniert mit dieser Plugin-Konfiguration innerhalb der &lt;i&gt;pom.xml&lt;/i&gt;:&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: white; font-size: 9,8pt;&quot;&gt;&lt;span style=&quot;color: #080808; font-family: JetBrains Mono, monospace;&quot;&gt;&amp;lt;plugin&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.openapitools&amp;lt;/groupId&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;artifactId&amp;gt;openapi-generator-maven-plugin&amp;lt;/artifactId&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;version&amp;gt;6.2.1&amp;lt;/version&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;executions&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;execution&amp;gt;
	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;goals&amp;gt;
		&amp;lt;goal&amp;gt;generate&amp;lt;/goal&amp;gt;
	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/goals&amp;gt;
	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;configuration&amp;gt;
		&amp;lt;inputSpec&amp;gt;${project.basedir}/src/main/resources/petstore-openapi.yaml&amp;lt;/inputSpec&amp;gt;
		&amp;lt;generatorName&amp;gt;spring&amp;lt;/generatorName&amp;gt;
		&amp;lt;configOptions&amp;gt;
		&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;sourceFolder&amp;gt;petstore&amp;lt;/sourceFolder&amp;gt;
		&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dateLibrary&amp;gt;java8&amp;lt;/dateLibrary&amp;gt;
		&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;useSpringBoot3&amp;gt;true&amp;lt;/useSpringBoot3&amp;gt;
		&amp;lt;/configOptions&amp;gt;
		&amp;lt;modelPackage&amp;gt;de.bsi.openapi.petstore.model&amp;lt;/modelPackage&amp;gt;
		&amp;lt;generateModels&amp;gt;true&amp;lt;/generateModels&amp;gt;
		&amp;lt;generateApis&amp;gt;false&amp;lt;/generateApis&amp;gt;
		&amp;lt;generateSupportingFiles&amp;gt;false&amp;lt;/generateSupportingFiles&amp;gt;
	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/configuration&amp;gt;
	&amp;lt;/execution&amp;gt;
&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/executions&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Wie bei Maven Dependencies legen &lt;span style=&quot;font-family: courier;&quot;&gt;groupId, atifactId &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;version&lt;/span&gt; das zu verwendende Plugin fest. Hier könnten wir auch als alternatives Plugin den Swagger Code Generator verwenden:&lt;br /&gt;&lt;span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;groupId&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;&amp;gt;io.swagger.codegen.v3&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #0033b3; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;groupId&lt;/span&gt;&lt;span style=&quot;color: #080808; font-family: JetBrains Mono, monospace;&quot;&gt;&lt;span style=&quot;background-color: white; font-size: 15px;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; font-size: 15px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #0033b3; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;artifactId&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;&amp;gt;swagger-codegen-maven-plugin&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #0033b3; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;artifactId&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #080808; font-family: &amp;quot;JetBrains Mono&amp;quot;, monospace; font-size: 15px;&quot;&gt;&amp;gt;&amp;lt;&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;3.0.36&amp;lt;/&lt;span style=&quot;color: #0033b3;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Das würde wenige, zusätzliche Anpassungen an den verwendeten Konfigurations-Parametern erfordern. Da der Swagger Code-Generator im Januar 2023 Spring Boot in der Version 3 nicht unterstützt, habe ich mich hier für den OpenAPI Code-Generator entschieden.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Mit &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;useSpringBoot3 &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;wird die Code Generierung kompatible zu Spring Boot Version 3 aktiviert.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Durch das Goal &lt;span style=&quot;font-family: courier;&quot;&gt;generate &lt;/span&gt;wird das Plugin in der &lt;span style=&quot;font-family: courier;&quot;&gt;generate-sources&lt;/span&gt;&amp;nbsp;Phase des Maven Lifecycles ausgeführt, siehe auch&amp;nbsp;&lt;a href=&quot;https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html&quot;&gt;https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;inputSpec&amp;nbsp;&lt;/span&gt;verweist auf die OpenAPI Spezifikation und entspricht dem &lt;span style=&quot;font-family: courier;&quot;&gt;-i&lt;/span&gt; Parameter aus dem vorherigen Abschnitt.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;generatorName&lt;/span&gt;&amp;nbsp;legt fest was generiert werden soll und entspricht dem &lt;span style=&quot;font-family: courier;&quot;&gt;-g&lt;/span&gt; Parameter.&lt;/li&gt;&lt;li&gt;Innerhalb des&amp;nbsp;&lt;i&gt;target/generated-sources/openapi&amp;nbsp;&lt;/i&gt;Verzeichnisses werden die generierten Klassen gespeichert. Mit dem Parameter&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;sourceFolder &lt;/span&gt;wird noch ein weiterer Teil des Pfades spezifiziert, indem sich dann die package Verzeichnisse befinden.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;modelPackage&lt;/span&gt;&amp;nbsp;legt den package Namen der generierten Modell Klassen fest. Da ich nur Modell Klassen generiere, benutze ich hier nur diesen Parameter zum Festlegen von package Namen.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;generateModels, generateApis&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;generateSupportingFiles&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;definieren was generiert werden soll. In meinem Beispiel nur die Modell Klassen der OpenAPI Spezifikation, daher sind die beiden anderen Parameter auf false gesetzt.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Java 17 wird Stand Januar 2023 von beiden Code Generatoren unterstützt&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die anderen Parameter und weiter führende Erklärungen zu den Maven Plugins findet ihr hier:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/swagger-api/swagger-codegen/blob/3.0.0/modules/swagger-codegen-maven-plugin/README.md&quot;&gt;https://github.com/swagger-api/swagger-codegen/blob/3.0.0/modules/swagger-codegen-maven-plugin/README.md&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-maven-plugin/README.md&quot;&gt;https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-maven-plugin/README.md&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Für Eclipse habe ich den Maven Build mit diesen Goals durchgeführt:&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&lt;br /&gt;mvn clean package eclipse:eclipse&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. &lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die beiden Goals &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;clean &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;package &lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;löschen zuerst die alten Builds und Bauen dann neu, dabei wird auch das Swagger Codegen Plugin ausgeführt.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Mit&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;eclipse:eclipse&lt;/span&gt;&lt;span style=&quot;font-family: times;&quot;&gt; fügt ihr die generierten Sourcen innerhalb des &lt;i&gt;target &lt;/i&gt;Verzeichnisses der Projekt Source Konfiguration hinzu, so dass die generierten Klassen in der IDE auch direkt genutzt werden können (das könnte man auch in den Projekt Properties im Java Build Path von Hand konfigurieren).&lt;br /&gt;In IntelliJ funktioniert das automatisch.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Weitere Dependencies und Controller&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die restlichen Dependencies in der Maven Konfiguration ergeben sich aus den generierten Klassen und dem von euch gewünschten Technologie-Stack. Mein Demo-Projekt zu diesem Artikel findet ihr hier: &lt;a href=&quot;https://github.com/elmar-brauch/openapi-codegen&quot;&gt;https://github.com/elmar-brauch/openapi-codegen&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Mir war es wichtig, dass ich auf die Spring reactive Bibliotheken aufsetze, siehe dazu &lt;/span&gt;&lt;a href=&quot;https://agile-coding.blogspot.com/2021/02/webflux.html&quot; style=&quot;font-family: times;&quot;&gt;webflux.html&lt;/a&gt;&lt;span style=&quot;font-family: times;&quot;&gt;. Damit die generierten Modell Klassen kompilieren, musste ich noch eine weitere Dependency aufnehmen:&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;groupId&amp;gt;org.springdoc&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;artifactId&amp;gt;springdoc-openapi-starter-webflux-ui&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;groupId&amp;gt;org.openapitools&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;artifactId&amp;gt;jackson-databind-nullable&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;lt;version&amp;gt;0.2.4&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: times;&quot;&gt;Die Java-Klassen und JUnit Tests zum Demo-Projekt zeigen, dass selbst geschriebener Code und generierter Code nicht im Konflikt zueinander stehen. Wiederholtes Generieren der Modell Klassen macht keine Controller Klassen kaputt. Controller Klassen generiere ich nicht mit meiner Maven Plugin Konfiguration, da ich die Vorteile der reaktiven Programmierung nutzen möchte.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;p&gt;Wenn ich Code Generatoren verwende sind mir folgende Punkte wichtig:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Moderner Technologie Stack, der zu den Bedürfnissen des Teams passt (und nicht zu 100% aus dem Code Generator abgeleitet ist).&lt;/li&gt;&lt;li&gt;Integration in die CICD Pipeline.&lt;/li&gt;&lt;li&gt;API Spezifikation und generierter Code müssen automatisiert synchron gehalten werden.&lt;/li&gt;&lt;li&gt;Keine händisches Anpassen des generierten Codes, um z.B. Compile-Fehler zu beseitigen.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Im hier gezeigten Demo-Projekt sind diese Punkte erfüllt, so dass ich mit dem Code Generator zufrieden bin. Allerdings gefallen mir auch einige Punkte nicht so gut:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die hier generierten Java Modell Klassen haben unnötig viel Boilerplate Code. Ich fände es besser, wenn der Code Generator diese z.B. mit Lombok Annotationen oder &lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/java-12-bis-15-features.html&quot; target=&quot;_blank&quot;&gt;Java 14 Records&lt;/a&gt; vermieden hätte.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Lohnt sich die Verwendung des Code Generators?&lt;/b&gt; &lt;br /&gt;Ja, wenn ihr die OpenAPI Spezifikation zuerst erstellt (API first) und wenn eure Modell relativ viele Klassen hat. Die API Modelle der Microservices in meinem Team sind deutlich umfangreicher als beim hier gezeigte Petstore. Daher setzen wir die Code Generatoren auch in der Praxis ein.&lt;/div&gt;&lt;p&gt;&lt;/p&gt;</description><link>https://agile-coding.blogspot.com/2021/08/openapi-codegen.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh419esBwXUsxmpxw5bHn1dmDOaMP0pjQgy3p16rzR3F5RTXCbUaNr2kDrRSvQuVHJSneSsM9Q0mfERoNhsq5Q9XRczJuLyzu15H7lxIArfY3Cgor-QgJOr9Cvi3y0UsrZ2nnUZtxXp4yzG/s72-w640-h234-c/image.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-2194980644624669461.post-1150816468243948242</guid><pubDate>Sun, 01 Jan 2023 06:55:00 +0000</pubDate><atom:updated>2023-01-01T07:55:49.850+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Security</category><category domain="http://www.blogger.com/atom/ns#">Spring</category><title>Spring Security 6: Login, Logout &amp; Rollen</title><description>&lt;p&gt;&lt;b&gt;Spring Security ermöglicht Login und Logout für Web-Anwendungen in&lt;/b&gt;&lt;b&gt;&amp;nbsp;wenigen Konfigurationsschritten&lt;/b&gt;&lt;b&gt;. Mit Spring Security sichern wir einzelne&amp;nbsp;&lt;/b&gt;&lt;b&gt;Webseiten oder API-Endpunkte&lt;/b&gt;&lt;b&gt;&amp;nbsp;so ab, dass nur authentifizierte Benutzer mit festgelegten Rollen zugreifen können. Wie funktioniert das? Lest weiter!&lt;/b&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdRKZwDyJarOYN8_vEiXd2Cp0lLGuPlrUuyyzgiHKQ_0dPLO5bh5wuTxdByNi6L3W0EPqqXc84hK3YTuq_RiVf0P1_JQS_oHR6Njqo7qNJVAQ6uijtG0tXZ0aHR_4tG5AZFcbVFfKA7gz6vMN6ym-X7xCXl-y5Yh_K1CwDNCoBwOCcMfQSRq4RCgfLZg/s400/Spring_security_logo.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;400&quot; data-original-width=&quot;400&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdRKZwDyJarOYN8_vEiXd2Cp0lLGuPlrUuyyzgiHKQ_0dPLO5bh5wuTxdByNi6L3W0EPqqXc84hK3YTuq_RiVf0P1_JQS_oHR6Njqo7qNJVAQ6uijtG0tXZ0aHR_4tG5AZFcbVFfKA7gz6vMN6ym-X7xCXl-y5Yh_K1CwDNCoBwOCcMfQSRq4RCgfLZg/w320-h320/Spring_security_logo.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;Hinweis: Für einen ersten Einblick zu Spring Security, schaut euch gerne meinen &lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/spring-security-starter.html&quot; target=&quot;_blank&quot;&gt;Einsteiger-Artikel&lt;/a&gt; an.&amp;nbsp;Dort seht ihr wie Spring Security zum Spring Boot hinzugefügt wird&amp;nbsp;und wie Benutzer mit Rollen definiert werden.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Abgesicherte und frei zugängliche Seiten&lt;/h2&gt;&lt;div&gt;Die Spring Security &lt;a href=&quot;https://agile-coding.blogspot.com/2022/06/spring-boot.html&quot; target=&quot;_blank&quot;&gt;Starter Dependency&lt;/a&gt; ist ohne weitere Konfiguration produktionsbereit, da sie alles absichert. Nur eingeloggte Benutzer haben Zugang. Um anonymen Benutzern bestimmte Seiten zugänglich zu machen, müssen wir die Authentifizierung und den Default Login anpassen.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableWebSecurity&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;public class SecurityConfiguration {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;SecurityFilterChain authenticatedAndFreePagesWithLogin(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;HttpSecurity http) throws Exception {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return http.authorizeHttpRequests()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.requestMatchers(&quot;/admin&quot;).authenticated()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.anyRequest().permitAll()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.and().formLogin()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.and().build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Eine eigene&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Bean ermöglicht uns in die Security Filter-Kette von Spring einzugreifen und Default Einstellungen zu ändern. Diese Änderungen&amp;nbsp;konfigurieren wir am &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpSecurity&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Objekt für Web-Anwendungen. Die&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;build&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Methode des&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpSecurity&lt;/span&gt;&lt;span&gt;&amp;nbsp;Objektes&lt;/span&gt;&amp;nbsp;erzeugt eine neue &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;DefaultSecurityFilterChain&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; Instanz. Die Bean Definition findet in einer mit&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@EnableWebSecurity&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;@Configuration&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; annotierten Klasse&amp;nbsp;statt.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;http.authorizeHttpRequests()&lt;/span&gt;&lt;span&gt;.requestMatchers(&quot;/admin&quot;).authenticated()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;legt fest, dass der Pfad &lt;i&gt;/admin&lt;/i&gt; nur für authentifizierte Benutzer zugänglich ist.&lt;br /&gt;Seit dem Update auf Spring Security 6 müssen die Methoden &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;authorizeHttpRequests &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;anstatt &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;authorizeRequests &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;und &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;requestMatchers &lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;anstatt &lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;antMatchers&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; verwendet werden.&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.anyRequest().permitAll()&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;&amp;nbsp;gibt anonymen und eingeloggten Benutzer Zugang auf allen anderen URL-Pfaden. Ohne&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.requestMatchers(&quot;/admin&quot;).authenticated()&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;wäre unsere komplette Web-Anwendung offen bzw. ohne Absicherung nutzbar.&lt;/span&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.and()&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt; gibt uns immer wieder das&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpSecurity&lt;/span&gt;&amp;nbsp;Objekt&amp;nbsp;zurück, so dass wir weitere Konfigurationen vornehmen können. Alternativ könnten wir statt &lt;span style=&quot;font-family: courier;&quot;&gt;.and()&lt;/span&gt; zu verwenden auch ein neues Statement mit dem&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;HttpSecurity&lt;/span&gt;&lt;span&gt;&amp;nbsp;Objekt beginnen.&lt;/span&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.formLogin()&lt;/span&gt;&amp;nbsp;aktiviert den Default Login von Spring Security. Wenn ein anonymer Benutzer eine mit .&lt;span style=&quot;font-family: courier;&quot;&gt;authenticated()&amp;nbsp;&lt;/span&gt;abgesicherte URL (hier&amp;nbsp;&lt;i&gt;/admin&lt;/i&gt;)&amp;nbsp;aufruft, findet ein Redirect zur Default Login Seite statt. Die Default URL der Login-Seite ist &lt;i&gt;/login&lt;/i&gt; - Anpassungen sind natürlich möglich.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Logout anpassen - aber wie?&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;Mit dem Login bekommen wir automatisch einen Logout, den wir über den Pfad &lt;i&gt;/logout&lt;/i&gt; erreichen. Im Browser sieht es wie folgt aus:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs3RqoeuBTXwEbL4oqJQe0mp0sjqb7rdACaw_pXljlLwRp7gCJRiiSqzCtL0eOcBxIwnsy67FNf_-7tYTdxl3ecuIOaofydBfUfosV_GP1ATnkW5LCFpNYs53M7D17sSuor65_yGW6Aqo0EAnMRysa1hhd05tbnVdGI1vqBp84eCOy0a9xoc81b65vNg/s621/Spring_logout.PNG&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;362&quot; data-original-width=&quot;621&quot; height=&quot;374&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs3RqoeuBTXwEbL4oqJQe0mp0sjqb7rdACaw_pXljlLwRp7gCJRiiSqzCtL0eOcBxIwnsy67FNf_-7tYTdxl3ecuIOaofydBfUfosV_GP1ATnkW5LCFpNYs53M7D17sSuor65_yGW6Aqo0EAnMRysa1hhd05tbnVdGI1vqBp84eCOy0a9xoc81b65vNg/w640-h374/Spring_logout.PNG&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;span&gt;Bei Bedarf konfigurieren wir den Logout ebenfalls in der&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&lt;/span&gt;&amp;nbsp;Bean. &lt;br /&gt;Beispielsweise legen wir fest, welche URL nach erfolgreichem Logout aufgerufen wird:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.and().formLogin()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;b&gt;.and().logout().logoutSuccessUrl(&quot;/index&quot;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Zugriff nur mit autorisierter Rolle&lt;/h2&gt;&lt;div&gt;&lt;span&gt;Bis jetzt hat jeder eingeloggte Benutzer Zugriff auf die &lt;i&gt;/admin&lt;/i&gt; Seite. Das ändern wir im nächsten Schritt, so dass nur noch Administratoren auf diese Seite zugreifen.&amp;nbsp;&lt;/span&gt;Dazu definieren wir, dass nur die Rolle ADMIN für die&amp;nbsp;&lt;i&gt;/admin&lt;/i&gt; Seite berechtigt ist. Normale Benutzer haben hier nur die Rolle USER - ihnen ist der Zugriff verboten. Schauen wir uns zuerst die Benutzer und deren Rollen an.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;UserDetailsService users(@Autowired PasswordEncoder pwEnc) {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;UserDetails user = User.builder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.username(&quot;user&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.password(pwEnc.encode(&quot;top&quot;))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.roles(&quot;USER&quot;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;UserDetails admin = User.builder()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.username(&quot;admin&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.password(pwEnc.encode(&quot;secret&quot;))&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.roles(&quot;USER&quot;, &quot;ADMIN&quot;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return new InMemoryUserDetailsManager(user, admin);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Die&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;UserDetailsService&amp;nbsp;&lt;/span&gt;Bean und ihre Alternativen erkläre ich im &lt;a href=&quot;https://agile-coding.blogspot.com/2020/12/spring-security-starter.html&quot; target=&quot;_blank&quot;&gt;ersten Spring Security Artikel&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Wichtig:&amp;nbsp;In&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;UserDetails &lt;/span&gt;Objekten legen wir sowohl Credentials als auch Rollen der Benutzer fest. Dabei kann ein Benutzer mehrere Rollen bekommen. Das sind dann die einzelnen String Parameter der Methode &lt;span style=&quot;font-family: courier;&quot;&gt;roles&lt;/span&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Jetzt sichern wir die &lt;i&gt;/admin&lt;/i&gt; Seite durch die Rollenüberprüfung ab. Dazu konfigurieren wir in unserer&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;SecurityFilterChain&lt;/span&gt;&lt;span&gt;&amp;nbsp;Bean die folgende fett markierte Stelle:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;@Bean&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;SecurityFilterChain authenticatedAndFreePagesWithLogin(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;HttpSecurity http) throws Exception {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;return &lt;b&gt;http.authorizeHttpRequests()&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;.requestMatchers(&quot;/admin&quot;).hasRole(&quot;ADMIN&quot;)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.anyRequest().permitAll()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.and().formLogin()&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;.and().build();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Der Methodenaufruf von&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;.authenticated()&lt;/span&gt;&amp;nbsp;wurde durch&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;.hasRole(&quot;ADMIN&quot;)&lt;/span&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;ersetzt. Dabei wird die Rolle ADMIN wird als String Parameter übergeben. Der Methodenaufruf&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;.requestMatchers(&quot;/admin&quot;)&lt;/span&gt;&amp;nbsp;legt fest, an welchen URL-Pfaden die Rolle ADMIN überprüft wird.&lt;/li&gt;&lt;li&gt;Logt ihr euch als Benutzer &quot;user&quot; ein und öffnet im Browser die Seite&amp;nbsp;&lt;i&gt;http://localhost:8080/admin&lt;/i&gt;, bekommt ihr den HTTP Status-Code 403 mit &quot;Forbidden&quot; Nachricht. Denn nur mit USER Rolle und ohne ADMIN Rolle habt ihr in diesem Testfall keine Zugriffsrechte.&lt;/li&gt;&lt;li&gt;Hinweis: Der HTTP Status Code 403 befindet sich auf einer Whitelabel Error Page, da ich hier keine eigenen Fehlerseiten verwende.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Fazit&lt;/h2&gt;&lt;div&gt;So leicht steigen wir mit Spring Security in die Themen Login, Logout und Rollenmanagement ein. Spring Security ist ein anpassbares Framework. Ihr könnt beispielsweise eigene Login und Logout Seiten hinterlegen oder andere Verfahren wie OpenId Connect einsetzen, siehe dazu auch&amp;nbsp;&lt;a href=&quot;https://agile-coding.blogspot.com/2022/10/openid.html&quot;&gt;OpenID Connect mit Spring Boot&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Spring Security hat eine sichere Default-Konfiguration. Damit kommen wir nach kurzer Adaption schnell zu einen sicheren Produktivsystem.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Das Update auf Spring Security 6 habe ich mit dem Spring Boot Update auf Version 3.0.0 gemacht. Da in Spring Security 6 deprecated Methoden (z. B. &lt;span style=&quot;font-family: courier;&quot;&gt;antMatchers&lt;/span&gt;) entfernt wurden, musste ich Code Anpassungen vornehmen. Den kompletten Code und die Spring Security 6 Code Anpassungen findet ihr in GitHub bzw. in der Git-Historie:&amp;nbsp;&lt;a href=&quot;https://github.com/elmar-brauch/spring-security-webapp.git&quot;&gt;https://github.com/elmar-brauch/spring-security-webapp.git&lt;/a&gt;&amp;nbsp;&lt;/div&gt;</description><link>https://agile-coding.blogspot.com/2022/09/spring-security-roles.html</link><author>noreply@blogger.com (Elmar Brauch - Microservice Master)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdRKZwDyJarOYN8_vEiXd2Cp0lLGuPlrUuyyzgiHKQ_0dPLO5bh5wuTxdByNi6L3W0EPqqXc84hK3YTuq_RiVf0P1_JQS_oHR6Njqo7qNJVAQ6uijtG0tXZ0aHR_4tG5AZFcbVFfKA7gz6vMN6ym-X7xCXl-y5Yh_K1CwDNCoBwOCcMfQSRq4RCgfLZg/s72-w320-h320-c/Spring_security_logo.png" height="72" width="72"/><thr:total>0</thr:total><georss:featurename>Darmstadt, Deutschland</georss:featurename><georss:point>49.8728253 8.6511929</georss:point><georss:box>21.562591463821157 -26.505057100000002 78.183059136178855 43.8074429</georss:box></item></channel></rss>