Commit 34dc0ca3 authored by Jens Ehlers's avatar Jens Ehlers
Browse files

Merge branch 'Lektorat'

parents a9a5490c 54aaaa1a
Pipeline #8051 passed with stage
in 1 minute and 10 seconds
......@@ -16,7 +16,7 @@
<li><i>Physical View</i>: Die Verteilung der Komponenten auf Laufzeitumgebungen, insbesondere auf virtuelle und dedizierte Server, aus Sicht des für den Betrieb zuständigen Systemadministrators wird dargestellt, z.B. als Verteilungsdiagramm in der UML.</li>
</ul>
</li>
<li><b>Allgemeingültigkeit</b> (engl. <i>Generality</i>): Zu Beginn eines Softwareprojekts stellt sich die Frage, wie individuell die Architektur des zu konstruierenden Systems sein muss. Wenn ein bewährtes Framework bereits als passende Architekturvorlage vorliegt, ist es grundsätzlich empfehlenswert darauf aufzubauen. Bei der Wiederverwendung von erprobten Komponenten kann i.d.R. davon ausgegangen werden, dass der Aufwand für eine eigene Implementierung deutlich höher ausfällt als die Einarbeitungszeit in die API der wiederverwendeten Komponente. Außerdem können durch Wiederverwendung ansonsten häufig auftretende Schwachstellen vermieden werden. Die Vor- und Nachteile eines individuellen Architekturentwurfs gegenüber einer adaptierten Standardarchitektur sind sorgfältig abzuwägen. Es ist zu beachten, dass die Architektur eines neues Systems sich durch die eigenen Entwurfsentscheidungen manifestiert.</li>
<li><b>Allgemeingültigkeit</b> (engl. <i>Generality</i>): Zu Beginn eines Softwareprojekts stellt sich die Frage, wie individuell die Architektur des zu konstruierenden Systems sein muss. Wenn ein bewährtes Framework bereits als passende Architekturvorlage vorliegt, ist es grundsätzlich empfehlenswert darauf aufzubauen. Bei der Wiederverwendung von erprobten Komponenten kann i.d.R. davon ausgegangen werden, dass der Aufwand für eine eigene Implementierung deutlich höher ausfällt als die Einarbeitungszeit in die API der wiederverwendeten Komponente. Außerdem können durch Wiederverwendung ansonsten häufig auftretende Schwachstellen vermieden werden. Die Vor- und Nachteile eines individuellen Architekturentwurfs gegenüber einer adaptierten Standardarchitektur sind sorgfältig abzuwägen. Es ist zu beachten, dass die Architektur eines neuen Systems sich durch die eigenen Entwurfsentscheidungen manifestiert.</li>
<li><b>Inkrementalität</b>: Inkrementalität basiert auf der Annahme, dass im Verlauf der Entwicklung Änderungen nicht zu verhindern sind und impliziert damit iteratives Vorgehen. Solange das Anwendungssystem weiterentwickelt wird, ist auch dessen Architektur i.d.R. nicht dauerhaft stabil. Die Evolution eines Anwendungssystems führt zu Unterschieden in der sogenannten präskriptiven und deskriptiven Architektur. Die präskriptive Architektur beschreibt, was durch die Architekten zur Entwurfszeit geplant war, während die deskriptive Architektur ausdrückt, was tatsächlich zur Laufzeit im Betrieb beobachtet werden kann, z.B. durch dynamische Code-Analyse mittels geeigneter <a href="https://www.gartner.com/reviews/market/apm">Application Performance Monitoring-Werkzeuge</a> wie <a href="https://www.appdynamics.com/">AppDynamics</a>, <a href="https://www.dynatrace.de/">Dynatrace</a> oder <a href="https://newrelic.com/">New Relic</a>. Wir sprechen von Software-Erosion, wenn der Soll-Zustand (präskriptiv) und der Ist-Zustand (deskriptiv) der Architektur stark voneinander abweichen.</li>
<li><b>Früherkennung von Änderungen</b> (engl. <i>Anticipation of Change</i>): Aufbauend auf der inkrementellen Entwicklung eines Systems gilt es potentielle zukünftige Änderungen möglichst früh zu antizipieren und in der Architektur zu berücksichtigen. Eine gute Architektur ist flexibel erweiterbar, aber weiterhin möglichst einfach zu handhaben. Die Ursachen für Änderungen sind sehr verschieden, z.B. Beseitigung von Fehlern (korrektive Wartung), Verbesserung nicht-funktionaler Eigenschaften (perfektive Wartung), Anpassung der Funktionalität wegen sich ändernder Bedingungen (adaptive Wartung).</li>
<li><b>Genauigkeit und Formalität</b>: Es geht hier nicht darum Kreativität zu verhindern, sondern das Ergebnis kreativer Entwurfsphasen möglichst präzise, z.B. in standardisierten Notationen, zu erfassen. Genauigkeit schafft Vertrauen für einen Entwurf. Formalität ist Genauigkeit in höchstem Maße, wobei jede Organisation bzw. jedes Team hier ein adäquates Maß für sich definieren muss. Es stellen sich dabei Fragen wie: Ist eine Verifikation für bestimmte Algorithmen erforderlich? Wie systematisch werden Testdaten und Testfälle erzeugt? Wie gründlich ist die Dokumentation von Aktivitäten?</li>
......@@ -56,12 +56,12 @@
<div class="cite"><a href="http://lig-membres.imag.fr/donsez/ujf/m2r/glcs/composants/lau.pdf">" A software component model is a definition of (1) the semantics of components, that is, what components are meant to be, (2) the syntax of components, that is, how they are defined, constructed, and represented, and (3) the composition of components, that is, how they are composed or assembled." (Kung-Kiu Lau et al.)</a></div>
<p>Aktuelle Komponentenmodelle sind z.B. <a href="https://docs.microsoft.com/de-de/dotnet/">Microsoft .NET</a>, <a href="https://jcp.org/en/jsr/detail?id=376">Java Platform Module System (JPMS)</a>, <a href="https://www.osgi.org/developer/specifications/">OSGi</a> und <a href="https://spring.io/">Spring</a>. Zu frühen Komponentenmodellen, die heute veraltet sind, gehören <a href="https://jcp.org/en/jsr/detail?id=220">Enterprise JavaBeans (EJBs)</a>, <a href="https://www.omg.org/spec/CCM/4.0/">OMG CORBA Component Model (CCM)</a> und <a href="https://docs.microsoft.com/de-de/windows/desktop/com/">Microsoft COM</a>. Auch die UML bietet ein Komponentenmodell an, das nicht wie die vorherigen auf eine konkrete Implementierung sondern auf die Spezifikation, Dokumentation und Kommunikation eines Architekturentwurfs ausgerichtet ist. Die folgende Abbildung zeigt wie Komponenten in einem UML-Komponentendiagramm dargestellt werden.</p>
<p>Aktuelle Komponentenmodelle sind z.B. <a href="https://docs.microsoft.com/de-de/dotnet/">Microsoft .NET</a>, <a href="https://jcp.org/en/jsr/detail?id=376">Java Platform Module System (JPMS)</a>, <a href="https://www.osgi.org/developer/specifications/">OSGi</a> und <a href="https://spring.io/">Spring</a>. Zu frühen Komponentenmodellen, die heute veraltet sind, gehören <a href="https://jcp.org/en/jsr/detail?id=220">Enterprise JavaBeans (EJBs)</a>, <a href="https://www.omg.org/spec/CCM/4.0/">OMG CORBA Component Model (CCM)</a> und <a href="https://docs.microsoft.com/de-de/windows/desktop/com/">Microsoft COM</a>. Auch die UML bietet ein Komponentenmodell an, das nicht wie die vorherigen auf eine konkrete Implementierung, sondern auf die Spezifikation, Dokumentation und Kommunikation eines Architekturentwurfs ausgerichtet ist. Die folgende Abbildung zeigt wie Komponenten in einem UML-Komponentendiagramm dargestellt werden.</p>
<img src="media/architecture_uml_component_model.png" style="width:720px">
<label>Notation des UML-Komponentendiagramms</label>
<p>Eine Komponente kann in der UML durch andere Teilkomponenten oder durch Klassen realisiert werden. Beides sind im Sinne der UML sogenannte <i>Classifier</i>. In der obigen Abbildung wird die Komponente <code>Provider</code> durch die Teilkomponenten <code>PartA</code> und <code>PartB</code> realisiert, wobei <code>PartA</code> wiederum durch die Klassen <code>ClassA1</code> und <code>ClassA2</code> realisiert wird. Weitere Realisierungen sind nicht angegeben. Die Komponente <code>Provider</code> wird manifestiert durch das ausführbare Artefakt <code>provider.jar</code>. Das Komponentendiagramm enthält keine Information darüber, in welcher Laufzeitumgebung und auf welchem Host dieses Artefakt bereitgestellt wird. Diese Information würde in der Sicht eines zum Gesamtmodell zugehörigen Verteilungsdiagramms dargestellt werden. Die Komponente <code>Provider</code> bietet nach außen die Schnittstelle <code>ProvidedInterface</code> an, die von der Komponente <code>Client</code> genutzt wird. Intern wird diese Schnittstelle in der Teilkomponente <code>PartA</code> realisiert. Eine anschauliche Notationsbeschreibung des UML-Komponentendiagramms findet sich in der <a href="https://docs.microsoft.com/visualstudio/modeling/uml-component-diagrams-reference?view=vs-2015#reading-component-diagrams">Dokumentation zu Microsoft Visual Studio</a> sowie in den UML-Lehrbüchern, die im Kapitel <a href="#unit-0-2" class="navigate">Objektorientierung und UML</a> referenziert werden.
<p>Eine Komponente kann in der UML durch andere Teilkomponenten oder durch Klassen realisiert werden. Beides sind im Sinne der UML sogenannte <i>Classifier</i>. In der obigen Abbildung wird die Komponente <code>Provider</code> durch die Teilkomponenten <code>PartA</code> und <code>PartB</code> realisiert, wobei <code>PartA</code> wiederum durch die Klassen <code>ClassA1</code> und <code>ClassA2</code> realisiert wird. Weitere Realisierungen sind nicht angegeben. Die Komponente <code>Provider</code> wird manifestiert durch das ausführbare Artefakt <code>provider.jar</code>. Das Komponentendiagramm enthält keine Information darüber, in welcher Laufzeitumgebung und auf welchem Host dieses Artefakt bereitgestellt wird. Diese Information würde in der Sicht eines zum Gesamtmodell zugehörigen Verteilungsdiagramms dargestellt werden. Die Komponente <code>Provider</code> bietet nach außen die Schnittstelle <code>ProvidedInterface</code> an, die von der Komponente <code>Client</code> genutzt wird. Intern wird diese Schnittstelle in der Teilkomponente <code>PartA</code> realisiert. Eine anschauliche Notationsbeschreibung des UML-Komponentendiagramms findet sich in der <a href="https://docs.microsoft.com/visualstudio/modeling/uml-component-diagrams-reference?view=vs-2015#reading-component-diagrams">Dokumentation zu Microsoft Visual Studio</a> sowie in den UML-Lehrbüchern, die im Kapitel <a href="#unit-0-2" class="navigate">Objektorientierung und UML</a> referenziert werden. </p>
<h4>Java Platform Module System (JPMS)</h4>
......@@ -138,7 +138,7 @@ public class SimpleRegressionModel {
</div>
<code></code>
</p>
<p>Die gezeigten Code-Beispiele zu JPMS finden sich im Verzeichnis <a href="/architecture/jpms" class="repo-link">/architecture/jpms</a> des Modul-Repository. Das folgende UML-Komponentendiagramm veranschaulicht die Abhängigkeiten der Module aus dem obigen Code-Beispiel. Es wird deutlich, wie das JPMS dazu beiträgt, Abhängigkeiten zwischen Komponenten explizit zu gestalten und ggf. einzuschränken.</p>
......@@ -147,7 +147,7 @@ public class SimpleRegressionModel {
<h4>Kapselung auf Ebene von Klassen</h4>
<span>Bisher ist nur über Modularisierung und Kapselung auf Ebene von Komponenten gesprochen worden. Im Feinentwurf werden die Komponenten durch Klassen realisiert, die ebenfalls interne Daten- und Verhaltensstrukturen in Form von privaten Attributen und Methoden voreinander verstecken können. Dazu dienen in objektorientierten Sprachen insbesondere die Sicherbarkeitsmodifizierer <code>private</code> und <code>protected</code>. Wenn auf eine Klasse regelmäßig von außen zugegriffen wird, ist eine stabile Schnittstelle wichtig. Der Zugriff kann in diesem Fall sinnvoll über Getter- und Setter-Methoden gekapselt werden, um die zugrundeliegenden internen Strukturen verändern zu können, ohne den Zugriff von außen zu beeinflussen. Grundsätzlich stellt sich die Frage, vor welchen äußeren Zugriffen die internen Strukturen gekapselt werden sollen.</span>
<span>Bisher ist nur über Modularisierung und Kapselung auf Ebene von Komponenten gesprochen worden. Im Feinentwurf werden die Komponenten durch Klassen realisiert, die ebenfalls interne Daten- und Verhaltensstrukturen in Form von privaten Attributen und Methoden voreinander verstecken können. Dazu dienen in objektorientierten Sprachen insbesondere die Sicherbarkeitsmodifizierer <code>private</code> und <code>protected</code>. Wenn auf eine Klasse regelmäßig von außen zugegriffen wird, ist eine stabile Schnittstelle wichtig. Der Zugriff kann in diesem Fall sinnvoll über Getter- und Setter-Methoden gekapselt werden, um die zugrundeliegenden internen Strukturen verändern zu können, ohne den Zugriff von außen zu beeinflussen. Grundsätzlich stellt sich die Frage, vor welchen äußeren Zugriffen die internen Strukturen gekapselt werden sollen:</span>
<ul>
<li>Vor Zugriffen aus Methoden in anderen Klassen im selben Modul?</li>
<li>Oder vor Zugriffen aus anderen Modulen durch externe Anwender, die das eigene Modul als abhängige Bibliothek einsetzen?</li>
......
......@@ -427,7 +427,7 @@ Eine Konvention des MvvmFX-Framework verlangt es, dass beide Dateien im gleichen
<li><b>ViewModel</b>: Das <i>ViewModel</i> hat keine Abhängigkeiten zu UI-Elementen von JavaFX (s. <code>import</code>-Anweisungen in Zeilen 1-2 im <i>ViewModel</i>). Wie der <i>Controller</i> in MVC kennt das <i>ViewModel</i> eine Instanz des <i>Model</i>, um es zu manipulieren (Zeile 7). Der Zustand der <i>View</i> wird über die <code>StringProperty</code>-Attribute in den Zeilen 9-10 gespiegelt. Ansonsten enthält das <i>ViewModel</i> die Präsentationslogik, die die Anwenderinteraktion steuert, d.h. hier die Methoden <code>selectCell</code> und <code>reset</code>. Für diese Methoden können einfache <i>Unit Tests</i> geschrieben werden – und zwar unabhängig von JavaFX.
</li>
<li><b>Model</b>: Das <i>Model</i> ist nicht verändert worden.</li>
<li><b>Application</b>: Wie zuvor startet die Klasse <code>TicTacToeApplicationMVVM</code> in gewohnter Weise die JavaFX-Anwendung. Bei initialen Laden der <i>View</i> (Zeile 8) wird die Klasse <code>FluentViewLoader</code> von MvvmFX verwendet, um das <i>ViewModel</i> an die <i>View</i> zu binden. Ab hier verwaltet das Framework beide Objekte in einem sogenannten <code>ViewTuple</code>.</li>
<li><b>Application</b>: Wie zuvor startet die Klasse <code>TicTacToeApplicationMVVM</code> in gewohnter Weise die JavaFX-Anwendung. Beim initialen Laden der <i>View</i> (Zeile 8) wird die Klasse <code>FluentViewLoader</code> von MvvmFX verwendet, um das <i>ViewModel</i> an die <i>View</i> zu binden. Ab hier verwaltet das Framework beide Objekte in einem sogenannten <code>ViewTuple</code>.</li>
</ul>
<p>Die gezeigten Code-Beispiele zur Realisierung des MVC- und MVVM-Musters in JavaFX finden sich in den Verzeichnissen <a href="/architecture/mvc" class="repo-link">/architecture/mvc</a> und <a href="/architecture/mvvm" class="repo-link">/architecture/mvvm</a> des Modul-Repository.</p>
\ No newline at end of file
......@@ -10,7 +10,7 @@
<li>Die fehlende Modularisierung führt dazu, dass die Komplexität nicht aufgeteilt und reduziert werden kann.</li>
<li>Die korrektive Wartung gestaltet sich sehr aufwändig, da eine Änderung an einer bestimmten Stelle im Code eine kaskadierende Folge von weiteren Änderungen an anderen Stellen verursacht. Dadurch gelingt auch eine schrittweise perfektive Wartung nicht, insbesondere die Migration einzelner Aspekte auf eine neue technologische Basis (z.B. andere Laufzeitumgebung, andere Programmiersprache).</li>
<li>Die Wiederverwendung einzelner Komponenten ist ausgeschlossen.</li>
<li>Skalierbarkeit im Kontext von Cloud Platformen ist stark eingeschränkt.</li>
<li>Skalierbarkeit im Kontext von Cloud-Plattformen ist stark eingeschränkt.</li>
</ul>
<p>Die Schichtenarchitektur (auch Schichtenmodell) ist ein einfaches Muster zur Strukturierung eines Softwaresystems, bei dem die einzelnen Aspekte (z.B. Komponenten oder Klassen) eines Systems jeweils unterschiedlichen Schichten (engl. <i>Tier</i> oder <i>Layer</i>) zugeordnet werden. Es gilt das grundsätzliche Prinzip, dass aus höheren Schichten auf tiefere Schichten zugegriffen werden darf, aber nicht von unten nach oben. Die Abhängigkeiten sind also von oben nach unten gerichtet. Bei einer strikten (oder geschlossenen) Schichtenarchitektur sind ausschließlich Zugriffe auf die nächsttiefere Schicht zulässig, d.h. es dürfen keine Schichten übersprungen werden. Im Gegensatz dazu darf in einer nicht-strikten (oder offenen) Schichtenarchitektur eine beliebige tiefere Schicht aufgerufen werden. Der Kontrollfluss und die Daten müssen dadurch nicht durch zwischenliegende Schichten durchgeschleust werden, was im verteilten System zu einer verbesserten Performance führen kann. Als Nachteil steht diesem Vorteil gegenüber, dass sich der Grad der Kopplung im System erhöht, wenn die tieferen Schichten potentiell von diversen höheren Schichten eingebunden werden und nicht nur von der nächsthöheren Schicht.</p>
......
......@@ -112,8 +112,8 @@ Diese Teilaufgaben können in der Fork-Phase rekursiv in noch kleinere Teilaufga
<label>Grundlegende Klassen des Fork-Join-Framework</label>
<ul>
<li><b>Tasks</b>: <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinTask.html"><code>ForkJoinTasks</code></a> sind abgeschlossene Arbeitseinheiten/Teilprobleme, die nicht auf andere Tasks warten müssen. Sie sich nicht an einen bestimmten Thread zur Ausführung gebunden und implementieren das <code>Future</code>-Interface, versprechen also i.d.R. ein Ergebnis bestimmten Typs (= <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code>RecursiveTask</code></a>). Tasks ohne Ergebnis heißen <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveAction.html"><code>RecursiveAction</code></a>.</li>
<li><b>Threads</b>: In einem <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html"><code>ForkJoinPool</code></a> werden spezielle Threads, nämlich <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinWorkerThread.html"><code>ForkJoinWorkerThreads</code></a> verwaltet, die <code>ForkJoinTasks</code> ausführen. Ein <code>ForkJoinPool</code> unterscheidet sich anderen <code>ExecutorServices</code> durch sogenanntes <i>Work Stealing</i>, d.h. alle Threads im Pool versuchen aktiv, in den Pool gesendete Tasks zu ergreifen und auszuführen. Dies ermöglicht eine effiziente Verarbeitung, insbesondere wenn die Tasks ihrerseits neue Tasks erzeugen – dadurch dass ein <code>ForkJoinTask</code> typischerweise eine Teilaufgabe rekursiv in kleinere Teilaufgaben zerlegt und diese wieder an den Pool sendet.</li>
<li><b>Tasks</b>: <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinTask.html"><code>ForkJoinTasks</code></a> sind abgeschlossene Arbeitseinheiten/Teilprobleme, die nicht auf andere Tasks warten müssen. Sie sind nicht an einen bestimmten Thread zur Ausführung gebunden und implementieren das <code>Future</code>-Interface, versprechen also i.d.R. ein Ergebnis bestimmten Typs (= <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code>RecursiveTask</code></a>). Tasks ohne Ergebnis heißen <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveAction.html"><code>RecursiveAction</code></a>.</li>
<li><b>Threads</b>: In einem <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html"><code>ForkJoinPool</code></a> werden spezielle Threads, nämlich <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinWorkerThread.html"><code>ForkJoinWorkerThreads</code></a> verwaltet, die <code>ForkJoinTasks</code> ausführen. Ein <code>ForkJoinPool</code> unterscheidet sich von anderen <code>ExecutorServices</code> durch sogenanntes <i>Work Stealing</i>, d.h. alle Threads im Pool versuchen aktiv, in den Pool gesendete Tasks zu ergreifen und auszuführen. Dies ermöglicht eine effiziente Verarbeitung, insbesondere wenn die Tasks ihrerseits neue Tasks erzeugen – dadurch dass ein <code>ForkJoinTask</code> typischerweise eine Teilaufgabe rekursiv in kleinere Teilaufgaben zerlegt und diese wieder an den Pool sendet.</li>
</ul>
<p>Das Fork-Join-Framework kann auch manuell ohne parallele Streams eingesetzt werden. Das folgende Code-Beispiel demonstriert wie die Berechnung einer bestimmmten Fibonacchi-Zahl über eine <code>RecursiveTask</code> parallelisiert werden kann. Das Beispiel ist aus der Dokumentation der <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code>RecursiveTask</code></a>-Klasse übernommen. Die Implementierung ist nicht effizient, da die gebildeten Teilaufgaben zu klein sind. Stattdessen ist – wie bei allen Fork-Join-Anwendungen – eine sinnvolle, minimale Granularität zu wählen, ab der eine Aufgabe sequentiell gelöst wird, anstatt sie zu unterteilen. Die Methode <code>fork</code> in Zeile 9 erzeugt eine Teilaufgabe, die nebenläufig über den <code>ForkJoinPool</code> abgearbeitet wird. Über die Methode <code>join</code> in Zeile 11 wird im aufrufenden Thread auf das Ergebnis dieser Teilaufgabe gewartet.</p>
......
<p>Der Zugriff auf gemeinsame Ressourcen, z.B. auf Objekte in Java, aus mehreren nebenläufigen Threads heraus, kann ohne Synchronisation zu inkonsistenten, ungewollten Zuständen führen. Dies tritt insbesondere dann auf wenn mehrere Anweisungen im Kontrollfluss als eine atomare Aktion ausgeführt werden sollen, deren Effekt unbeeinflusst von Anweisungen sein muss, die in nebenläufigen Threads ausgeführt werden. Ähnliche Probleme treten im Umfeld von Datenbanken auf, wenn Transaktionen nicht ausreichend isoliert voneinander sind (s. <a href="https://de.wikipedia.org/wiki/ACID">ACID-Prinzip</a>).</p>
<p>Der Zugriff auf gemeinsame Ressourcen, z.B. auf Objekte in Java, aus mehreren nebenläufigen Threads heraus, kann ohne Synchronisation zu inkonsistenten, ungewollten Zuständen führen. Dies tritt insbesondere dann auf, wenn mehrere Anweisungen im Kontrollfluss als eine atomare Aktion ausgeführt werden sollen, deren Effekt unbeeinflusst von Anweisungen sein muss, die in nebenläufigen Threads ausgeführt werden. Ähnliche Probleme treten im Umfeld von Datenbanken auf, wenn Transaktionen nicht ausreichend isoliert voneinander sind (s. <a href="https://de.wikipedia.org/wiki/ACID">ACID-Prinzip</a>).</p>
<h4>Race Conditions</h4>
......@@ -69,7 +69,7 @@ class LostUpdate {
}
}</code></pre>
<p>Es existiert ein sogenannter kritischer Abschnitt (engl. <i>Critical Section</i>), indem sich nur ein Thread zurzeit befinden darf, damit keine Lost Updates auftreten. In dem obigen Code-Beispiel ist die Methode <code>incrementResourceValue</code> der kritische Abschnitt (Zeilen 8-12). Das Lesen, Inkrementieren und Schreiben des Zustands der Ressource muss als eine atomare Aktion ausgeführt werden und darf nicht durch eine Verschränkung der Threads unterbrochen werden. Eine naheliegende Idee ist es, den kritischen Abschnitt auf eine einzelne Anweisung zu reduzieren, z.B. indem die Methode <code>incrementResourceValue</code> wie folgt umgestaltet wird:
<p>Es existiert ein sogenannter kritischer Abschnitt (engl. <i>Critical Section</i>), indem sich nur ein Thread zurzeit befinden darf, damit keine Lost Updates auftreten. In dem obigen Code-Beispiel ist die Methode <code>incrementResourceValue</code> der kritische Abschnitt (Zeilen 8-12). Das Lesen, Inkrementieren und Schreiben des Zustands der Ressource muss als eine atomare Aktion ausgeführt werden und darf nicht durch eine Verschränkung der Threads unterbrochen werden. Eine naheliegende Idee ist es, den kritischen Abschnitt auf eine einzelne Anweisung zu reduzieren, z.B. indem die Methode <code>incrementResourceValue</code> wie folgt umgestaltet wird:</p>
<pre><code class="language-java line-numbers">void incrementResourceValue() {
resource++; // read, increment, and write resource value
......@@ -96,13 +96,13 @@ class LostUpdate {
<li>Ein Thread gibt die erlangte Sperre wieder frei und informiert die blockierten Threads darüber.</li>
</ol>
<p>In Java muss das Schlüsselwort <code>synchronized</code> verwendet werden, um eine Methode oder einen beliebigen Anweisungsblock als kritischen Abschnitt zu kennzeichnen. Es gewährleistet, dass nur ein Thread gleichzeitig den kritischen Abschnitt betreten und durchlaufen darf. Erst durch die Verwendung des Schlüsselworts <code>synchronized</code> ist die folgende Methode tatsächlich <i>thread-safe</i>.
<p>In Java muss das Schlüsselwort <code>synchronized</code> verwendet werden, um eine Methode oder einen beliebigen Anweisungsblock als kritischen Abschnitt zu kennzeichnen. Es gewährleistet, dass nur ein Thread gleichzeitig den kritischen Abschnitt betreten und durchlaufen darf. Erst durch die Verwendung des Schlüsselworts <code>synchronized</code> ist die folgende Methode tatsächlich <i>thread-safe</i></p>.
<pre><code class="language-java line-numbers">synchronized void incrementResourceValue() {
resource++;
}</code></pre>
<p>Während sich ein Thread im kritischen Abschnitt befindet, sichert die JVM durch eine Sperre zu, dass weitere Threads am Anfang des kritischen Abschnitts blockiert werden. Dadurch wechseln die Threads ggf. aus dem Zustand <b>Runnable</b> in den Zustand <b>Blocked</b>. Eine Sperre wird in Java je Objekt gesetzt, d.h. mehrere <code>synchronized</code>-Methoden und -Blöcke, die sich auf dasselbe Objekt beziehen, sind nicht unabhängig voneinander, sondern teilen sich eine gemeinsame Sperre. Das folgende Code-Beispiel zeigt beispielhaft eine Klasse mit zwei <code>synchronized</code>-Methoden und einem <code>synchronized</code>-Block. Wenn ein Objekt X dieser Klasse instantiiert wird und und ein Thread auf diesem Objekt X die Methode <code>a</code> aufruft, so kann kein anderer Thread die Methoden <code>a</code>, <code>b</code> und den <code>synchronized</code>-Block in Methode <code>c</code> betreten. Auf einem weiteren Objekt Y dieser Klasse hingegen, wären dadurch keine Sperren gesetzt und die <code>synchronized</code>-Methoden und -Blöcke könnten weiterhin aufgerufen werden, ohne zu blockieren.</p>
<p>Während sich ein Thread im kritischen Abschnitt befindet, sichert die JVM durch eine Sperre zu, dass weitere Threads am Anfang des kritischen Abschnitts blockiert werden. Dadurch wechseln die Threads ggf. aus dem Zustand <b>Runnable</b> in den Zustand <b>Blocked</b>. Eine Sperre wird in Java je Objekt gesetzt, d.h. mehrere <code>synchronized</code>-Methoden und -Blöcke, die sich auf dasselbe Objekt beziehen, sind nicht unabhängig voneinander, sondern teilen sich eine gemeinsame Sperre. Das folgende Code-Beispiel zeigt beispielhaft eine Klasse mit zwei <code>synchronized</code>-Methoden und einem <code>synchronized</code>-Block. Wenn ein Objekt X dieser Klasse instantiiert wird und ein Thread auf diesem Objekt X die Methode <code>a</code> aufruft, so kann kein anderer Thread die Methoden <code>a</code>, <code>b</code> und den <code>synchronized</code>-Block in Methode <code>c</code> betreten. Auf einem weiteren Objekt Y dieser Klasse hingegen wären dadurch keine Sperren gesetzt und die <code>synchronized</code>-Methoden und -Blöcke könnten weiterhin aufgerufen werden, ohne zu blockieren.</p>
<pre><code class="language-java line-numbers">class SharedResource {
......@@ -128,14 +128,14 @@ class LostUpdate {
<h4>Thread-Zustände in der JVM</h4>
<p>Das folgende UML-Zustandsdiagramm visualisiert die möglichen Zustände eines Threads innerhalb der JVM sowie die möglichen Transitionen zwischen diesen Zuständen. Sperren mittels <code>synchronized</code> gewährleisten den wechselseitigen Ausschluss für kritische Abschnitte und können damit Race Conditions verhindern. Die JVM versetzt die Threads ggf. automatisch in den Zustand <b>Blocked</b>, falls auf eine Sperre bedingt durch <code>synchronized</code> zu warten ist: <i>"Thread is blocked for synchronization"</i>.</p>
<p>Das folgende UML-Zustandsdiagramm visualisiert die möglichen Zustände eines Threads innerhalb der JVM sowie die möglichen Transitionen zwischen diesen Zuständen. Sperren mittels <code>synchronized</code> gewährleisten den wechselseitigen Ausschluss für kritische Abschnitte und können damit Race Conditions verhindern. Die JVM versetzt die Threads ggf. automatisch in den Zustand <b>Blocked</b>, falls auf eine Sperre, bedingt durch <code>synchronized</code>, zu warten ist: <i>"Thread is blocked for synchronization"</i>.</p>
<img src="media/concurrency_thread_states.png" style="width:750px">
<label>Zustände und Transitionen von Threads in der JVM</label>
<span class="source"><a href="https://www.uml-diagrams.org/java-thread-uml-state-machine-diagram-example.html">Java Thread States and Life Cycle</a></span>
<p>Mittels <code>synchronized</code> können aber keine anwendungsspezifischen Bedingungen formuliert werden. Häufig ergibt sich in der Praxis die Situation, dass ein Thread auf ein bestimmtes Ereignis warten muss, bevor er sinnvoll weiterarbeiten kann. Er soll von einem anderen Thread benachrichtigt werden können, wenn dieser das Ereignis herbeigeführt hat, das aus dem Wartezustand befreit. Der grundlegende Mechanismus für das Warten auf Benachrichtigungen von anderen Threads kann in Java über die Methoden <code>wait</code> und <code>notify</code> abgebildet werden, die in der allgemeinen Oberklasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html"><code>java.lang.Object</code></a> implementiert sind und damit jedem Objekt zur Verfügung stehen. Wenn ein Thread die Methode <code>wait</code> auf einem Objekt aufruft, wird er in den Zustand <b>Waiting</b> versetzt: <i>"Thread is waiting for notification"</i>. Erst wenn ein anderer Thread die Methode <code>notify</code> oder die Methode <code>notifyAll</code> auf demselben Objekt aufruft, wird der wartende Thread wieder aus dem Wartezustand befreit und in den Zustand <b>Runnable</b> versetzt. Die Methode <code>wait</code> kann auch mit einem Timeout-Argument versehen werden. In diesem Fall wird der Thread, wie bei einem Aufruf der Methode <code>Thread.sleep</code>, in den Zustand <b>Timed Waiting</b> versetzt. Über <code>java.lang.management.ThreadInfo</code> kann grundsätzlich ausgewertet werden, wie viel Zeit ein Thread in welchem der Zustände verbracht hat.</p>
</p>
<p>Der Zustand <b>Runnable</b> ist in die Zustände <b>Ready</b> und <b>Running</b> untergliedert. Wenn es mehr nebenläufige Threads als Prozessoren gibt, steuert der Scheduler den Wechsel der Threads zwischen diesen Zuständen. Im Zustand <b>Running</b> ist einem Thread tatsächlich ein Prozessor zugewiesen, so dass der Kontrollfluss des Threads abgearbeitet werden kann. Ein Thread im Zustand <b>Ready</b> wartet darauf, dass der Scheduler ihm im Zuge des Interleaving einen Prozessor zuweist.</p>
......@@ -261,7 +261,7 @@ class BoundedBufferSync implements BoundedBuffer {
</div>
</div>
<span>Es ist anzumerken, dass die Methode <code>notify</code> nur einen zufälligen Thread aus der Menge der aktuell auf die betroffene Sperre wartenden Threads aus dem Wartezustand befreit. Dadurch ist keine Fairness unter den wartenden Erzeugern und Verbrauchern gewährleistet. Das folgende Beispiel widmet sich der Herausforderung faires Warten gemäß des Prinzips einer FIFO-Warteschlange zu realisieren. Wir stellen uns dazu eine Parkgarage mit einer beschränkten Kapazität an Stellplätzen vor. In diese Parkgarage fahren Autos ein und aus. Wenn alle Stellplätze belegt sind, müssen neu ankommende Autos an einer Schranke vor der Parkgarage in einer FIFO-Warteschlange warten. Die Parkgarage ist die gemeinsam genutzte Ressource für mehrere nebenläufige Threads. Die Autos werden als Threads implementiert, die in einer Endlosschleife folgende Aktivitäten ausführen (Zeilen 22-27 in Klasse <code>Car</code>):</span>
<span>Es ist anzumerken, dass die Methode <code>notify</code> nur einen zufälligen Thread aus der Menge, der aktuell auf die betroffene Sperre wartenden Threads, aus dem Wartezustand befreit. Dadurch ist keine Fairness unter den wartenden Erzeugern und Verbrauchern gewährleistet. Das folgende Beispiel widmet sich der Herausforderung faires Warten, gemäß des Prinzips einer FIFO-Warteschlange, zu realisieren. Wir stellen uns dazu eine Parkgarage mit einer beschränkten Kapazität an Stellplätzen vor. In diese Parkgarage fahren Autos ein und aus. Wenn alle Stellplätze belegt sind, müssen neu ankommende Autos an einer Schranke vor der Parkgarage in einer FIFO-Warteschlange warten. Die Parkgarage ist die gemeinsam genutzte Ressource für mehrere nebenläufige Threads. Die Autos werden als Threads implementiert, die in einer Endlosschleife folgende Aktivitäten ausführen (Zeilen 22-27 in Klasse <code>Car</code>):</span>
<ul>
<li>herumfahren (Methode <code>cruise</code> in Klasse <code>Car</code>),</li>
<li>einfahren in die Parkgarage (Methode <code>enter</code> in Interface <code>ParkingGarage</code>),</li>
......@@ -335,7 +335,7 @@ class BoundedBufferSync implements BoundedBuffer {
<span>Die Implementierung <code>FairParkingGarage</code> unterscheidet sich leicht, um die Fairness beim Warten abzubilden – insbesondere in den Zeilen 16 und 32.</span>
<ul>
<li>In Zeile 32 werden mittels der Methode <code>notifyAll</code> alle der wartenden Autos statt nur ein zufälliges benachrichtigt.</li>
<li>In Zeile 16 wird die Bedingung für das Warten erweitert. Es muss nicht nur gewartet werden, wenn keine Stellplätze frei sind (<code>places == 0</code>), sondern auch wenn das Auto noch nicht an vorderster Position der Warteschlange angelangt ist. Es wird dazu gezählt, wie viele Autos bereits an der Schranke vor der Parkgarage angekommen sind (<code>arrivalCount</code>, Zeile 12) und wie viele Autos bereits in die Parkgarage eingefahren sind (<code>entranceCount</code>, Zeile 22). Die Wertzuweisung in die Variable <code>arrival</code> (Zeile 12) entspricht daher der Vergabe einer laufend inkrementierten Wartenummer – ähnlich des Wartebereichs in vielen Ämtern in Deutschland. Ein vor der Schranke wartendes Auto wird zwar jedes Mal erweckt, wenn ein anderes Auto die Parkgarage verlässt, kann aber nur vorrücken und nicht einfahren (<code>arrival - 1 != entranceCount</code>, Zeile 16), bis alle übrigen wartenden Autos mit niedrigerer Wartenummer eingefahren sind. Auf diese Weise wird die Fairness beim Zugriff auf die gemeinsame Ressource eingehalten.</li>
<li>In Zeile 16 wird die Bedingung für das Warten erweitert. Es muss nicht nur gewartet werden, wenn keine Stellplätze frei sind (<code>places == 0</code>), sondern auch, wenn das Auto noch nicht an vorderster Position der Warteschlange angelangt ist. Es wird dazu gezählt, wie viele Autos bereits an der Schranke vor der Parkgarage angekommen sind (<code>arrivalCount</code>, Zeile 12) und wie viele Autos bereits in die Parkgarage eingefahren sind (<code>entranceCount</code>, Zeile 22). Die Wertzuweisung in die Variable <code>arrival</code> (Zeile 12) entspricht daher der Vergabe einer laufend inkrementierten Wartenummer – ähnlich des Wartebereichs in vielen Ämtern in Deutschland. Ein vor der Schranke wartendes Auto wird zwar jedes Mal erweckt, wenn ein anderes Auto die Parkgarage verlässt, kann aber nur vorrücken und nicht einfahren (<code>arrival - 1 != entranceCount</code>, Zeile 16), bis alle übrigen wartenden Autos mit niedrigerer Wartenummer eingefahren sind. Auf diese Weise wird die Fairness beim Zugriff auf die gemeinsame Ressource eingehalten.</li>
</ul>
<ul class="nav nav-tabs" id="sync-fairness2-tabs" role="tablist">
......@@ -422,7 +422,7 @@ class BoundedBufferSync implements BoundedBuffer {
</div>
</div>
<p>Es stellt sich beim genauen Hinsehen die Frage, warum auch am Ende der Methode <code>enter</code> die Methode <code>notifyAll</code> aufgerufen wird (Zeile 25). Dazu müssen wir uns vorstellen, dass die Parkgarage voll belegt ist, während mehrere Autos vor der Schranke warten. Nun verlassen direkt hintereinander mehrere Autos die Parkgarage, so dass wieder einige Stellplätze frei werden. Der gemeinsame kritische Abschnitt der Methoden <code>enter</code> und <code>leave</code> sichert zu, dass während des Ausfahrens eines Autos nicht gleichzeitig ein anderes Auto einfahren kann. Anschließend gelingt es dem vordersten Auto in der Warteschlange in die Parkgarage einzufahren. Nach dem Einfahren muss es die anderen wartenden Autos mittels <code>notifyAll</code> benachrichtigen (Zeile 25), da noch weitere Plätze frei sind und ansonsten nicht wieder belegt werden würden.</p>
<p>Es stellt sich beim genauen Hinsehen die Frage, warum auch am Ende der Methode <code>enter</code> die Methode <code>notifyAll</code> aufgerufen wird (Zeile 25). Dazu müssen wir uns vorstellen, dass die Parkgarage voll belegt ist, während mehrere Autos vor der Schranke warten. Nun verlassen direkt hintereinander mehrere Autos die Parkgarage, sodass wieder einige Stellplätze frei werden. Der gemeinsame kritische Abschnitt der Methoden <code>enter</code> und <code>leave</code> sichert zu, dass während des Ausfahrens eines Autos nicht gleichzeitig ein anderes Auto einfahren kann. Anschließend gelingt es dem vordersten Auto in der Warteschlange in die Parkgarage einzufahren. Nach dem Einfahren muss es die anderen wartenden Autos mittels <code>notifyAll</code> benachrichtigen (Zeile 25), da noch weitere Plätze frei sind und ansonsten nicht wieder belegt werden würden.</p>
<p>Im Package <code>java.util.concurrent</code> finden wir heute lange etablierte Interfaces und Klassen, die eine entwicklerfreundlichere API für die grundlegenden Muster zur Synchronisation von Threads bieten. Dazu zählen z.B. die Interfaces <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Lock.html"><code>Lock</code></a> als Alternative zu <code>synchronized</code> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Condition.html"><code>Condition</code></a> als Alternative zu den Methoden <code>wait</code>, <code>notify</code> und <code>notifyAll</code>. Blockierende FIFO-Warteschlangen bieten sämtliche Implementierungen des Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/BlockingQueue.html"><code>BlockingQueue</code></a>. Das folgende Code-Beispiel demonstriert wie der oben dargestellte, begrenzte und blockierende Speicher in der Klasse <code>BoundedBufferSync</code> alternativ über <code>Lock</code>- und <code>Condition</code>-Objekte (siehe <code>BoundedBufferLock</code>) bzw. über eine <code>ArrayBlockingQueue</code> (siehe <code>BoundedBufferBlockingQueue</code>) realisiert werden kann.</p>
......@@ -547,7 +547,7 @@ class BoundedBufferBlockingQueue implements BoundedBuffer {
<h4>Deadlock und Starvation</h4>
<p>Wenn ein Thread nicht nur eine sondern mehrere Sperren auf exklusiv nutzbare Ressourcen anfordert, kann es zu einer Verklemmung (engl. <i>Deadlock</i>) zwischen den nebenläufigen Threads kommen, die um den Zugriff auf die Ressourcen konkurrieren. Ein Deadlock bezeichnet den Zustand, bei dem eine zyklische Wartekette zwischen mehreren Threads eingetreten ist, wobei jeder beteiligte Thread auf die Freigabe von Sperren auf Ressourcen wartet, die ein anderer beteiligter Thread bereits exklusiv erlangt hat. Im einfachsten Fall sind wie in der folgenden Abbildung visualisiert nur 2 Threads und 2 Ressourcen beteiligt.</p>
<p>Wenn ein Thread nicht nur eine, sondern mehrere Sperren auf exklusiv nutzbare Ressourcen anfordert, kann es zu einer Verklemmung (engl. <i>Deadlock</i>) zwischen den nebenläufigen Threads kommen, die um den Zugriff auf die Ressourcen konkurrieren. Ein Deadlock bezeichnet den Zustand, bei dem eine zyklische Wartekette zwischen mehreren Threads eingetreten ist, wobei jeder beteiligte Thread auf die Freigabe von Sperren auf Ressourcen wartet, die ein anderer beteiligter Thread bereits exklusiv erlangt hat. Im einfachsten Fall sind, wie in der folgenden Abbildung visualisiert, nur 2 Threads und 2 Ressourcen beteiligt.</p>
<img src="media/concurrency_deadlock.png" style="width:800px">
<label>Verklemmung von Threads beim Zugriff auf exklusiv nutzbare Ressourcen</label>
......@@ -622,7 +622,7 @@ class BoundedBufferBlockingQueue implements BoundedBuffer {
<li><b>Concurrent Collections:</b> Neben Warteschlangen werden <code>Collection</code>-Implementierungen angeboten, die für die Verwendung im Multithreading-Kontext konzipiert sind, z.B. <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html"><code>ConcurrentHashMap</code></a> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CopyOnWriteArrayList.html"><code>CopyOnWriteArrayList</code></a>. Wenn zu erwarten ist, dass viele Threads auf eine bestimmte Collection zugreifen, ist eine <code>ConcurrentHashMap</code> i.d.R. einer synchronisierten <code>HashMap</code> vorzuziehen. Eine <code>CopyOnWriteArrayList</code> ist einer synchronisierten <code>ArrayList</code> vorzuziehen, wenn die erwartete Anzahl an Lesezugriffen und Iterationen die Anzahl an Schreibzugriffen auf einer Liste übersteigt.</li>
<li><b>Locks:</b> Die Implementierungen der Interfaces <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Lock.html"><code>Lock</code></a> und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/Condition.html"><code>Condition</code></a> ermöglichen eine größere Flexibilität bei der Verwendung von Sperren und Bedingungen, jedoch auf Kosten einer unhandlicheren Syntax im Vergleich zum Schlüsselwort <code>synchronized</code>.</li>
<li><b>Timing:</b> Die Klasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/TimeUnit.html"><code>TimeUnit</code></a> bietet unterschiedliche Granularitäten (inkl. Nanosekunden) zum Steuern von Timeout-basierten Vorgängen.</li>
<li><b>Atomics:</b> Eine Sammlung von Klassen, die eine Thread-sichere Programmierung ohne Sperren für einzelne Variablen unterstützen, findet sich im Package <code>java.util.concurrent.atomic</code> – insbesondere für Variablen, die von einem Basisdatentyp sind. Instanzen der Klasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/atomic/AtomicLong.html"><code>AtomicLong</code></a> o.ä. bieten jeweils Zugriff und Aktualisierungen für eine einzelne Variable des entsprechenden Typs. Mittels <code>AtomicLong</code> kann im Multithreading-Kontext z.B. die Vergabe einer eindeutigen Sequenznummer realisiert, wie das folgende Code-Beispiel demonstriert.</li>
<li><b>Atomics:</b> Eine Sammlung von Klassen, die eine Thread-sichere Programmierung ohne Sperren für einzelne Variablen unterstützen, findet sich im Package <code>java.util.concurrent.atomic</code> – insbesondere für Variablen, die von einem Basisdatentyp sind. Instanzen der Klasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/atomic/AtomicLong.html"><code>AtomicLong</code></a> o.ä. bieten jeweils Zugriff und Aktualisierungen für eine einzelne Variable des entsprechenden Typs. Mittels <code>AtomicLong</code> kann im Multithreading-Kontext z.B. die Vergabe einer eindeutigen Sequenznummer realisiert werden, wie das folgende Code-Beispiel demonstriert.</li>
<pre><code class="language-java line-numbers">class Sequencer {
private final AtomicLong sequenceNumber = new AtomicLong(0);
......
......@@ -56,11 +56,11 @@
Thread thread = new Thread(runnable);
thread.start();</code></pre>
<p>Im folgenden Code-Beispiel werden in der Klasse <code>MyLoopThread</code> 3 Threads gestartet, die jeweils die Zahlen von 1 bis 100 hochzählen und ausgeben. Anhand der Ausgaben lässt sich das zufällige <i>Thread-Interleaving</i> beobachten. Es ist nicht vorhersehbar, welcher Thread zuerst fertig wird. In der Klasse <code>MyLoopSleepThread</code> wird das voherige Beispiel nur darum erweitert, dass jeder Thread nach der Ausgabe einer Zahl für 10 ms wartet (Zeilen 9-11). Durch diese bewusste Verzögerung zählen zwar alle Threads mit hoher Wahrscheinlichkeit im Takt, aber nicht garantiert. Die Korrektheit einer Anwendung sollte nie von einem derartigen Aufruf der Methode <code>sleep</code> abhängen.</p>
<p>Im folgenden Code-Beispiel werden in der Klasse <code>MyLoopThread</code> 3 Threads gestartet, die jeweils die Zahlen von 1 bis 100 hochzählen und ausgeben. Anhand der Ausgaben lässt sich das zufällige <i>Thread-Interleaving</i> beobachten. Es ist nicht vorhersehbar, welcher Thread zuerst fertig wird. In der Klasse <code>MyLoopSleepThread</code> wird das vorherige Beispiel nur darum erweitert, dass jeder Thread nach der Ausgabe einer Zahl für 10 ms wartet (Zeilen 9-11). Durch diese bewusste Verzögerung zählen zwar alle Threads mit hoher Wahrscheinlichkeit im Takt, aber nicht garantiert. Die Korrektheit einer Anwendung sollte nie von einem derartigen Aufruf der Methode <code>sleep</code> abhängen.</p>
<ul class="nav nav-tabs" id="threads-tabs" role="tablist">
<li class="nav-item"><a href="#threads-tabs-loop" class="nav-link active" data-toggle="tab" role="tab">MyLoopThread</a></li>
<li class="nav-item"><a href="#threads-tabs-loop2" class="nav-link" data-toggle="tab" role="tab">MyLoopSleepThread</code></a></li>
<li class="nav-item"><a href="#threads-tabs-loop2" class="nav-link" data-toggle="tab" role="tab">MyLoopSleepThread</a></li>
</ul>
<div class="tab-content" id="threads-tabs-content">
<div class="tab-pane show active" id="threads-tabs-loop" role="tabpanel">
......@@ -104,11 +104,11 @@ thread.start();</code></pre>
}</code></pre></div>
</div>
<p>Ein <code>Thread</code> wird beendet, wenn er das Ende der Methode <code>run</code> des ausgeführten <code>Runnable</code> erreicht. Es ist durchaus üblich, dass Threads dauerhaft auf bestimmte Ereignisse warten und diese verarbeiten, bis eine Abbruchbedingung erfüllt wird. Das erste der beiden folgenden Code-Beispiele zeigt, wie ein derartiges sanftes, kontrolliertes Beenden eines Threads am Ende einer Iteration herbeigeführt werden kann. Das zweite Code-Beispiel zeigt dagegen, dass ein Thread auch abrupt mit sofortiger Wirkung von außen unterbrochen werden kann, indem die Methode <code>interrupt</code> auf dem <code>Thread</code>-Objekt ausgeführt wird. Die aktuelle Iteration wird hierbei nicht mehr vollständig ausgeführt, sondern unmittelbar abgebrochen. Methoden wie z.B. <code>sleep</code>, die einen Thread in einen wartenden Zustand versetzen, werfen i.d.R. eine <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/InterruptedException.html"><code>InterruptedException</code></a>, falls sie von einem anderen Thread von außen durch Aufruf der Methode <code>interrupt</code> unterbrochen worden sind.</p>
<p>Ein <code>Thread</code> wird beendet, wenn er das Ende der Methode <code>run</code> des ausgeführten <code>Runnable</code> erreicht. Es ist durchaus üblich, dass Threads dauerhaft auf bestimmte Ereignisse warten und diese verarbeiten, bis eine Abbruchbedingung erfüllt wird. Das erste der beiden folgenden Code-Beispiele zeigt, wie ein derartiges sanftes, kontrolliertes Beenden eines Threads am Ende einer Iteration herbeigeführt werden kann. Das zweite Code-Beispiel zeigt dagegen, dass ein Thread auch abrupt mit sofortiger Wirkung von außen unterbrochen werden kann, indem die Methode <code>interrupt</code> auf dem <code>Thread</code>-Objekt ausgeführt wird. Die aktuelle Iteration wird hierbei nicht mehr vollständig ausgeführt, sondern unmittelbar abgebrochen. Methoden wie z.B. <code>sleep</code>, die einen Thread in einen wartenden Zustand versetzen, werfen i.d.R. eine <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/InterruptedException.html"><code>InterruptedException</code></a>, falls sie von einem anderen Thread von außen, durch Aufruf der Methode <code>interrupt</code>, unterbrochen worden sind.</p>
<ul class="nav nav-tabs" id="threads2-tabs" role="tablist">
<li class="nav-item"><a href="#threads2-tabs-stop" class="nav-link active" data-toggle="tab" role="tab">Thread über Bedingung beenden</a></li>
<li class="nav-item"><a href="#threads2-tabs-interrupt" class="nav-link" data-toggle="tab" role="tab">Thread-Interruption</code></a></li>
<li class="nav-item"><a href="#threads2-tabs-interrupt" class="nav-link" data-toggle="tab" role="tab">Thread-Interruption</a></li>
</ul>
<div class="tab-content" id="threads2-tabs-content">
<div class="tab-pane show active" id="threads2-tabs-stop" role="tabpanel">
......@@ -155,7 +155,7 @@ thread.start();</code></pre>
}</code></pre></div>
</div>
<p>In Java können Threads eine Priorität zwischen 1 und 10 zugewiesen bekommen. Einem Thread wird je nach Priorität mehr oder weniger CPU-Zeit zugestanden. Das folgende Code-Beispiel führt 10 Threads mit jeweils unterschiedlicher Priorität aus (Zeilen 14-18). Jeder Thread belastet die CPU, indem er eine Variable inkrementiert (Zeilen 7-8). Nach 10 s werden alle Threads abgebrochen (Zeilen 20+33). Die Ausgabe in den Zeilen 23-31 zeigt exemplarisch, wie viel CPU-Zeit den Threads zugestanden worden ist. Bei jeder Ausführung wird die Ausgabe sich leicht unterscheiden. Es ist zu berücksichtigen, dass auf dem Betriebssystem weitere Prozesse ausgeführt werden, so dass die theoretisch verfügbar CPU-Zeit nicht allein auf die Java-Threads verteilt werden kann. Die Ausgabe könnte auf einem Computer mit 4 Prozessorkernen wie folgt aussehen:</p>
<p>In Java können Threads eine Priorität zwischen 1 und 10 zugewiesen bekommen. Einem Thread wird je nach Priorität mehr oder weniger CPU-Zeit zugestanden. Das folgende Code-Beispiel führt 10 Threads mit jeweils unterschiedlicher Priorität aus (Zeilen 14-18). Jeder Thread belastet die CPU, indem er eine Variable inkrementiert (Zeilen 7-8). Nach 10 s werden alle Threads abgebrochen (Zeilen 20+33). Die Ausgabe in den Zeilen 23-31 zeigt exemplarisch, wie viel CPU-Zeit den Threads zugestanden worden ist. Bei jeder Ausführung wird die Ausgabe sich leicht unterscheiden. Es ist zu berücksichtigen, dass auf dem Betriebssystem weitere Prozesse ausgeführt werden, sodass die theoretisch verfügbar CPU-Zeit nicht allein auf die Java-Threads verteilt werden kann. Die Ausgabe könnte auf einem Computer mit 4 Prozessorkernen wie folgt aussehen:</p>
<pre>
CPU time of Thread-0: 187 ms
......@@ -210,7 +210,7 @@ Available processors: 4
<h4>Scheduling von Threads</h4>
<p>Es ergibt sich häufig die Anforderung, dass ein Runnable nicht sofort sondern zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt werden soll, oder dass ein Runnable periodisch in bestimmten Intervallen wiederholt werden soll. Eine derartige zeitliche Ablaufsteuerung von Prozessen bezeichnen wir allgemein als <i>Scheduling</i>. Im Folgenden werden zwei Varianten für das Scheduling von Runnables in Java gezeigt. In beiden Code-Beispielen entsteht bei Ausführung die folgende Ausgabe:</p>
<p>Es ergibt sich häufig die Anforderung, dass ein Runnable nicht sofort, sondern zu einem bestimmten Zeitpunkt in der Zukunft ausgeführt werden soll, oder dass ein Runnable periodisch in bestimmten Intervallen wiederholt werden soll. Eine derartige zeitliche Ablaufsteuerung von Prozessen bezeichnen wir allgemein als <i>Scheduling</i>. Im Folgenden werden zwei Varianten für das Scheduling von Runnables in Java gezeigt. In beiden Code-Beispielen entsteht bei Ausführung die folgende Ausgabe:</p>
<pre>Timed event 1 12:01:02.689008600
Timed event 2 12:01:03.673393100
Timed event 3 12:01:04.673484800
......@@ -219,7 +219,7 @@ Timed event 5 12:01:06.673461800</pre>
<ul class="nav nav-tabs" id="threads3-tabs" role="tablist">
<li class="nav-item"><a href="#threads3-tabs-timer" class="nav-link active" data-toggle="tab" role="tab">TimerTask</a></li>
<li class="nav-item"><a href="#threads3-tabs-executor" class="nav-link" data-toggle="tab" role="tab">ScheduledExecutorService</code></a></li>
<li class="nav-item"><a href="#threads3-tabs-executor" class="nav-link" data-toggle="tab" role="tab">ScheduledExecutorService</a></li>
</ul>
<div class="tab-content" id="threads3-tabs-content">
<div class="tab-pane show active" id="threads3-tabs-timer" role="tabpanel">
......@@ -265,15 +265,15 @@ class MyScheduledExecutorService {
<ul>
<li><b>Timer</b>: In Zeile 15 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Timer.html"><code>Timer</code></a> erzeugt, über den mittels der Methode <code>scheduleAtFixedRate</code> ein <code>TimerTask</code>-Objekt, also eine zeitgesteuerte Aufgabe, wiederholt ausgeführt werden kann (Zeile 16). Das zweite Input-Argument gibt die Verzögerung (in ms) der ersten Ausführung an, das dritte Input-Argument das Intervall (in ms) für die wiederholte Ausführung. Die Ausführung endet erst mit dem Abbruch des <code>Timer</code> (Zeile 19). Die Klasse <code>TimerTask</code> implementiert das Interface <code>Runnable</code>.</li>
<li><b>ScheduledExecutorService</b>: In Zeile 11 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html"><code>ScheduledExecutorService</code></a> erzeugt, dem in diesem Fall genau ein Thread zur Ausführung der über ihn gesteuerten Runnables zur Verfügung steht. In Zeile 12 wird entsprechend des vorherigen Timer-Beispiels die wiederholte Ausführung eines Runnable gestartet. Die initiale Verzögerung beträgt wiederum 500 ms und das Intervall zwischen den Ausführungen 1 s. Der <code>ScheduledExecutorService</code> kann mittels der Methode <code>shutdown</code> beendet werden (Zeile 15).</li>
<li><b>ScheduledExecutorService</b>: In Zeile 11 wird ein <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html"><code>ScheduledExecutorService</code></a> erzeugt, dem in diesem Fall genau ein Thread zur Ausführung der über ihn gesteuerten Runnables zur Verfügung steht. In Zeile 12 wird, entsprechend des vorherigen Timer-Beispiels, die wiederholte Ausführung eines Runnable gestartet. Die initiale Verzögerung beträgt wiederum 500 ms und das Intervall zwischen den Ausführungen 1 s. Der <code>ScheduledExecutorService</code> kann mittels der Methode <code>shutdown</code> beendet werden (Zeile 15).</li>
</ul>
<p>Das Interface <code>ScheduledExecutorService</code> erweitert das allgemeine funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executor.html"><code>Executor</code></a>, das lediglich die Methode <code>void execute(Runnable command)</code> spezifiziert, um ein Runnable nebenläufig auszuführen. Eine konkrete <code>Executor</code>- und <code>ScheduledExecutorService</code>-Implementierung ist die Klasse <code>ScheduledThreadPoolExecutor</code>, der im Konstruktor die Kapazität für einen <i>Thread-Pool</i> mitgegeben werden kann. Die Threads werden nun nicht mehr für je Runnable über einen Konstruktoraufruf <code>new Thread(...)</code> instantiiert, sondern aus einem begrenztem Thread-Pool wiederverwendet. Ein Thread-Pool bietet sich insbesondere für den typischen Anwendungsfall an, bei dem ein Server die Aufgaben (engl. <i>Tasks</i>) mehrerer Clients bedient. Das Thread-Pool-Muster vermeidet die ineffiziente Erzeugung eines neuen Threads je Aufgabe und schützt vor Ressourcenüberlastung, z.B. bei <i>Denial-of-Service</i>-Angriffen. Die Warteschlange, die zeitweise vor einem Thread-Pool entsteht, kann über unterschiedliche Strategien abgebaut werden, wobei <i>First-In-First-Out (FIFO)</i> häufig naheliegend und pragmatisch ist. Die folgende Abbildung verdeutlicht die Funktionsweise eines Thread-Pools.</p>
<p>Das Interface <code>ScheduledExecutorService</code> erweitert das allgemeine funktionale Interface <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executor.html"><code>Executor</code></a>, das lediglich die Methode <code>void execute(Runnable command)</code> spezifiziert, um ein Runnable nebenläufig auszuführen. Eine konkrete <code>Executor</code>- und <code>ScheduledExecutorService</code>-Implementierung ist die Klasse <code>ScheduledThreadPoolExecutor</code>, der im Konstruktor die Kapazität für einen <i>Thread-Pool</i> mitgegeben werden kann. Die Threads werden nun nicht mehr für jede Runnable über einen Konstruktoraufruf <code>new Thread(...)</code> instantiiert, sondern aus einem begrenztem Thread-Pool wiederverwendet. Ein Thread-Pool bietet sich insbesondere für den typischen Anwendungsfall an, bei dem ein Server die Aufgaben (engl. <i>Tasks</i>) mehrerer Clients bedient. Das Thread-Pool-Muster vermeidet die ineffiziente Erzeugung eines neuen Threads je Aufgabe und schützt vor Ressourcenüberlastung, z.B. bei <i>Denial-of-Service</i>-Angriffen. Die Warteschlange, die zeitweise vor einem Thread-Pool entsteht, kann über unterschiedliche Strategien abgebaut werden, wobei <i>First-In-First-Out (FIFO)</i> häufig naheliegend und pragmatisch ist. Die folgende Abbildung verdeutlicht die Funktionsweise eines Thread-Pools.</p>
<img src="media/concurrency_pool.png" style="width:360px">
<label>Funktionsweise eines Thread-Pools</label>
<p>In dem folgenden Code-Beispiel wird die begrenzte Kapazität eines Thread-Pools demonstriert. Es sollen 7 Runnables gestartet werden (Zeilen 18-19). Der Thread-Pool lässt aber nur die Ausführung von 3 nebenläufigen Threads zu (Zeile 1), so dass die letzten 4 Runnables warten müssen, bis wieder ein Thread freigegeben wird. Da die Runnables jeweils zufällig lange arbeiten (Zeilen 8-11), ist nicht vorhersehbar, in welchem der 3 Threads die letzten 4 Runnables ausgeführt werden. Eine mögliche Ausgabe könnte wie folgt aussehen:</p>
<p>In dem folgenden Code-Beispiel wird die begrenzte Kapazität eines Thread-Pools demonstriert. Es sollen 7 Runnables gestartet werden (Zeilen 18-19). Der Thread-Pool lässt aber nur die Ausführung von 3 nebenläufigen Threads zu (Zeile 1), sodass die letzten 4 Runnables warten müssen, bis wieder ein Thread freigegeben wird. Da die Runnables jeweils zufällig lange arbeiten (Zeilen 8-11), ist nicht vorhersehbar, in welchem der 3 Threads die letzten 4 Runnables ausgeführt werden. Eine mögliche Ausgabe könnte wie folgt aussehen:</p>
<pre>Task 2 worked for 2522 ms in pool-1-thread-2. Total time: 2538 ms
Task 3 worked for 2617 ms in pool-1-thread-3. Total time: 2633 ms
Task 1 worked for 2916 ms in pool-1-thread-1. Total time: 2932 ms
......
......@@ -5,7 +5,7 @@
<p>In der Software-Entwicklung wird ein Adapter (engl. auch <i>Wrapper</i>) eingesetzt, wenn eine existierende Klasse verwendet werden soll, deren Interface nicht dem vorgesehenen Interface entspricht. Die existierende Klasse soll oder kann nicht verändert werden, insbesondere wenn sie Teil einer externen Bibliothek ist, die von Dritten entwickelt wird. Die zu adaptierende Klasse aus der Bibliothek ist häufig von Interesse, da sie komplexes Verhalten implementiert, das wiederverwendet werden soll. Das Prinzip der Wiederverwendung ist grundsätzlich effizient, wenn externe Bibliotheken in ein Projekt eingebunden werden, von denen bekannt ist, dass sie umfänglich getestet worden sind und dass sie sich in anderen Projekten bereits praktisch bewährt haben. Im Java-Umfeld werden Bibliotheken i.d.R. aus einem <a href="https://mvnrepository.com/">zentralen Repository</a> über Build-Management-Werkzeuge wie <a href="https://maven.apache.org/">Maven</a> oder <a href="https://gradle.org/">Gradle</a> automatisiert in ein Projekt eingebunden.</p>
<p>Das Adapter-Muster gibt es in zwei Varianten: Objektadapter und Klassenadapter. In beiden Varianten wird die Ziel-Schnittstelle <code>Target</code> von der Adapter-Klasse <code>Adapter</code> implementiert. Weiterhin ruft der Adapter in der von <code>Target</code> vorgeschriebenen Methode <code>operation</code> die Methode <code>service</code> der adaptierten Klasse <code>Adaptee</code> auf. Es können natürlich auch mehrere Methoden adaptiert werden. Beim Objektadapter besteht eine Assoziation zwischen Adapter und adaptierter Klasse, während beim Klassenadapter der Adapter die adaptierte Klasse erweitert. Der Klassenadapter ist in Java nicht zu realisieren, falls <code>Target</code> kein Interface sondern eine zustandsbehaftete Klasse ist, da in diesem Fall Mehrfachvererbung erforderlich wäre. Die folgenden UML-Klassendiagramme visualisieren Objekt- und Klassenadapter im Vergleich zueinander.</p>
<p>Das Adapter-Muster gibt es in zwei Varianten: Objektadapter und Klassenadapter. In beiden Varianten wird die Ziel-Schnittstelle <code>Target</code> von der Adapter-Klasse <code>Adapter</code> implementiert. Weiterhin ruft der Adapter in der von <code>Target</code> vorgeschriebenen Methode <code>operation</code> die Methode <code>service</code> der adaptierten Klasse <code>Adaptee</code> auf. Es können natürlich auch mehrere Methoden adaptiert werden. Beim Objektadapter besteht eine Assoziation zwischen Adapter und adaptierter Klasse, während beim Klassenadapter der Adapter die adaptierte Klasse erweitert. Der Klassenadapter ist in Java nicht zu realisieren, falls <code>Target</code> kein Interface, sondern eine zustandsbehaftete Klasse ist, da in diesem Fall Mehrfachvererbung erforderlich wäre. Die folgenden UML-Klassendiagramme visualisieren Objekt- und Klassenadapter im Vergleich zueinander.</p>
<img src="media/patterns_adapter.png" style="width:1000px">
<label>Entwurfsmuster Adapter (in Varianten Objekt- und Klassenadapter)</label>
......@@ -87,7 +87,7 @@
<img src="media/patterns_adapter_shapes.png" style="width:500px">
<label>Polygone zur Motivation eines Adapters</label>
<p>Die Bestimmung der Fläche eines Polygons ist nicht trivial. Aus diesem Grund möchten wir die Bibliothek <a href="http://commons.apache.org/proper/commons-math/">Apache Commons Math</a> einbinden, die eine API für sehr viele mathematische Grundlagen bereitstellt. Wir interessieren uns für die Methode <code>getSize</code> der Klasse <a href="http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math4/geometry/euclidean/twod/PolygonsSet.html"><code>PolygonsSet</code></a>, über die Fläche für ein Polygon berechnet werden kann. Das Code-Beispiel zeigt, wie das Polygon über einen Objektadapter (<code>PolygonObjectAdapter</code>) oder alternativ über einen Klassenadapter (<code>PolygonClassAdapter</code>) realisiert werden kann. In der Klasse <code>Client</code> ist zu erkennen, dass auch die neuen Adapter-Klassen das Interface <code>Shape</code> erfüllen.</p>
<p>Die Bestimmung der Fläche eines Polygons ist nicht trivial. Aus diesem Grund möchten wir die Bibliothek <a href="http://commons.apache.org/proper/commons-math/">Apache Commons Math</a> einbinden, die eine API für sehr viele mathematische Grundlagen bereitstellt. Wir interessieren uns für die Methode <code>getSize</code> der Klasse <a href="http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math4/geometry/euclidean/twod/PolygonsSet.html"><code>PolygonsSet</code></a>, über die die Fläche für ein Polygon berechnet werden kann. Das Code-Beispiel zeigt, wie das Polygon über einen Objektadapter (<code>PolygonObjectAdapter</code>) oder alternativ über einen Klassenadapter (<code>PolygonClassAdapter</code>) realisiert werden kann. In der Klasse <code>Client</code> ist zu erkennen, dass auch die neuen Adapter-Klassen das Interface <code>Shape</code> erfüllen.</p>
<ul class="nav nav-tabs" id="adapter-tabs2" role="tablist">
<li class="nav-item"><a href="#adapter-tabs-polygon1" class="nav-link active" data-toggle="tab" role="tab">PolygonObjectAdapter</a></li>
......
<p>Das Kompositum (engl. <i>Composite</i>) ist ein Strukturmuster, das angewendet wird, um Objekte, die sich ein gemeinsames Interface teilen, zu einer hierarchischer Baumstruktur zusammenfügen. Die grundlegende Idee des Kompositum-Musters ist es, sowohl primitive Objekte als auch zusammengesetzte Objekte, die aus mehreren primitiven Objekte bestehen, in einem Interface zu repräsentieren. Dadurch können einzelne Objekte und ihre Kompositionen einheitlich behandelt werden.</p>
<p>Das Kompositum (engl. <i>Composite</i>) ist ein Strukturmuster, das angewendet wird, um Objekte, die sich ein gemeinsames Interface teilen, zu einer hierarchischer Baumstruktur zusammenzufügen. Die grundlegende Idee des Kompositum-Musters ist es, sowohl primitive Objekte als auch zusammengesetzte Objekte, die aus mehreren primitiven Objekten bestehen, in einem Interface zu repräsentieren. Dadurch können einzelne Objekte und ihre Kompositionen einheitlich behandelt werden.</p>
<p>Es soll also folgende Frage durch das Muster beantwortet werden: Wie können in einer objektorientierten Sprache einzelne Objekte als Teile (= Komponenten) geschickt zu einem Ganzen (= Kompositum) zusammengesetzt werden?</p>
......@@ -35,7 +35,7 @@
</div>
</div>
<p>Es bietet sich an, ein gemeinsames Interface für die Formen zu spezifieren, das die Methode <code>draw</code> für jede Form vorschreibt. Da sich alle Formen auch ein gemeinsames Attribut vom Typ <code>GraphicsContext</code> teilen und ein Interface zustandslos ist, wird eine abstrakte Klasse <code>Shape</code> eingeführt.</p>
<p>Es bietet sich an, ein gemeinsames Interface für die Formen zu spezifizieren, das die Methode <code>draw</code> für jede Form vorschreibt. Da sich alle Formen auch ein gemeinsames Attribut vom Typ <code>GraphicsContext</code> teilen und ein Interface zustandslos ist, wird eine abstrakte Klasse <code>Shape</code> eingeführt.</p>
<ul class="nav nav-tabs" id="composite-tabs2" role="tablist">
<li class="nav-item"><a href="#composite-tabs-shape" class="nav-link active" data-toggle="tab" role="tab">Shape</a></li>
......@@ -91,7 +91,7 @@
<img src="media/patterns_composite_shapes.png" style="width:750px">
<label>Zusammengesetzte Formen</label>
<p>Dadurch dass das Kompositum als Teile nicht nur primitive Formen sondern auch andere zusammengesetzte Formen aufnehmen kann, entsteht eine Teil-Ganzes-Hierarchie, die in ihrer Tiefe grundsätzlich nicht beschränkt ist. Die Beziehungen der beteiligten Objekte stellen eine Baumstruktur dar, da zyklische Beziehungen i.d.R. nicht erwünscht sind und daher beim Einfügen von Teilen zu einem Ganzen verhindert werden sollten. Da es sich um eine Baumstruktur handelt, nennen wir die Basisformen auch Blätter (engl. <i>Leaf</i>). Das folgende Objektdiagramm verdeutlicht eine derartige verschachtelte Baumstruktur, die die obigen Formen weiterverwendet. Die resultierende komplexe Form ist daneben dargestellt. Auf Basis weniger Basisformen können so beliebig komplexe Kompositionen zusammengestellt werden.</p>
<p>Dadurch, dass das Kompositum als Teile nicht nur primitive Formen, sondern auch andere zusammengesetzte Formen aufnehmen kann, entsteht eine Teil-Ganzes-Hierarchie, die in ihrer Tiefe grundsätzlich nicht beschränkt ist. Die Beziehungen der beteiligten Objekte stellen eine Baumstruktur dar, da zyklische Beziehungen i.d.R. nicht erwünscht sind und daher beim Einfügen von Teilen zu einem Ganzen verhindert werden sollten. Da es sich um eine Baumstruktur handelt, nennen wir die Basisformen auch Blätter (engl. <i>Leaf</i>). Das folgende Objektdiagramm verdeutlicht eine derartige verschachtelte Baumstruktur, die die obigen Formen weiterverwendet. Die resultierende komplexe Form ist daneben dargestellt. Auf Basis weniger Basisformen können so beliebig komplexe Kompositionen zusammengestellt werden.</p>
<img src="media/patterns_composite_shapes2.png" style="width:1000px">
<label>Zusammengesetzte Form "Vitruvian Man"</label>
......
......@@ -47,7 +47,7 @@ class CapsLockConsoleLogger implements MyLogger {
<p>In der folgenden Klasse <code>SpringClient</code> wird diese Bean in das Attribut <code>MyLogger logger</code> injiziert (Zeilen 4-5), d.h. genau an dieser Stelle findet die Dependency Injection statt. Obwohl kein Konstruktoraufruf zu sehen ist, kann der Logger später verwendet werden (Zeile 13). Die Annotation <code>@Autowired</code> sorgt dafür, dass das Framework per Reflection nach einer passenden Bean für das Interface <code>MyLogger</code> sucht und diese Bean an das Attribut bindet. Wichtig ist, dass genau eine passende Bean gefunden wird – und nicht keine oder mehrere.</p>
<p>Die Klasse <code>SpringClient</code> ist hier eine einfache <a href="https://spring.io/guides/gs/spring-boot/">Spring Boot-Anwendung</a>, die als solche annotiert ist (Zeile 1) und wie üblich gestartet wird (Zeile 8). Durch den Aufruf von <code>SpringApplication.run()</code> wird ein <code>ApplicationContext</code>-Objekt, erzeugt, das den Spring IoC-Container repräsentiert und über das als zentraler Anwendungskontext alle Beans verwaltet werden und erreichbar sind.</p>
<p>Die Klasse <code>SpringClient</code> ist hier eine einfache <a href="https://spring.io/guides/gs/spring-boot/">Spring Boot-Anwendung</a>, die als solche annotiert ist (Zeile 1) und wie üblich gestartet wird (Zeile 8). Durch den Aufruf von <code>SpringApplication.run()</code> wird ein <code>ApplicationContext</code>-Objekt erzeugt, das den Spring IoC-Container repräsentiert und über das als zentraler Anwendungskontext alle Beans verwaltet werden und erreichbar sind.</p>
<pre><code class="language-java line-numbers">@SpringBootApplication
class SpringClient { // this example is a Spring Boot application
......
......@@ -18,7 +18,7 @@
<li>Querschnittliche Belange (engl. <i>Cross-cutting Concerns</i>) können von einer Fassade übernommen werden, z.B. Authentifizierung und Autorisierung, Session-Verwaltung, allgemeine Validierungen, Logging oder Monitoring.</li>
</ul></p>
<p>Das in der obigen Abbildung dargestellte UML-Komponentendiagramm soll anhand der mit Java 9 eingeführten <a href="https://www.informatik-aktuell.de/entwicklung/programmiersprachen/java-9-das-neue-modulsystem-jigsaw-tutorial.html">Module</a> (= Komponenten) folgend implementiert werden. Die Programmiersprache Java bietet seit ihrer Entstehung die Möglichkeit, die Sichtbarkeit von Klassen, ihren Attributen und Methoden, usw. einzuschränken. Per Default ist die Sichtbarkeit auf Inhalte innerhalb des eigenen Package beschränkt. Der Default kann über die bekannten Modifizierer <code>public</code>, <code>private</code> und <code>protected</code> angepasst werden. Seit Java 9 gibt es auf höherer Abstraktionsebene nun Module, die den Komponenten der Architektur entsprechen und über die ebenfalls die Sichtbarkeit gesteuert werden kann. Diese Module umfassen i.d.R. mehrere Packages und spezifizieren in ihrem Modul-Deskriptor (<code>module-info.java</code>) zum einen, von welchen anderen Modulen sie abhängig sein (<code>requires &lt;&lt;module&gt;&gt;</code>), und zum anderen, welche ihrer Packages sie an andere Module nach außen freigeben (<code>exports &lt;&lt;package&gt;&gt;</code>). Per Konvention werden die Module in Java genau wie Packages kleingeschrieben und mit einer umgekehrten Domain als identifizierendes Prefix bezeichnet. Auf das Domain-Prefix wird hier zur Vereinfachung verzichtet.</p>
<p>Das in der obigen Abbildung dargestellte UML-Komponentendiagramm soll anhand der mit Java 9 eingeführten <a href="https://www.informatik-aktuell.de/entwicklung/programmiersprachen/java-9-das-neue-modulsystem-jigsaw-tutorial.html">Module</a> (= Komponenten) folgend implementiert werden. Die Programmiersprache Java bietet seit ihrer Entstehung die Möglichkeit, die Sichtbarkeit von Klassen, ihren Attributen und Methoden, usw. einzuschränken. Per Default ist die Sichtbarkeit auf Inhalte innerhalb des eigenen Package beschränkt. Der Default kann über die bekannten Modifizierer <code>public</code>, <code>private</code> und <code>protected</code> angepasst werden. Seit Java 9 gibt es auf höherer Abstraktionsebene nun Module, die den Komponenten der Architektur entsprechen und über die ebenfalls die Sichtbarkeit gesteuert werden kann. Diese Module umfassen i.d.R. mehrere Packages und spezifizieren in ihrem Modul-Deskriptor (<code>module-info.java</code>) zum einen, von welchen anderen Modulen sie abhängig sind (<code>requires &lt;&lt;module&gt;&gt;</code>), und zum anderen, welche ihrer Packages sie an andere Module nach außen freigeben (<code>exports &lt;&lt;package&gt;&gt;</code>). Per Konvention werden die Module in Java genau wie Packages kleingeschrieben und mit einer umgekehrten Domain als identifizierendes Prefix bezeichnet. Auf das Domain-Prefix wird hier zur Vereinfachung verzichtet.</p>
<ul class="nav nav-tabs" id="facade-tabs" role="tablist">
<li class="nav-item"><a href="#facade-tabs-client" class="nav-link active" data-toggle="tab" role="tab">Modul Client</a></li>
......
......@@ -165,7 +165,7 @@ logger.log(Level.INFO, "Hello World!");
<p>Die abstrakte Klasse <code>LoggerCreator</code> definiert eine abstrakte Methode <code>createLogger</code>. Eine statische Methode zur Erzeugung der bekannten konkreten Logger gibt es hingegen nicht mehr. Es wird angenommen, dass zukünftig verschiedene konkrete Logger (wie z.B. der <code>JDBCLogger</code>) entstehen, die durch Dritte realisiert werden. Der Fokus liegt auf der Erweiterbarkeit. Die Oberklasse zur Objekterzeugung <code>LoggerCreator</code> kann auch Methoden beinhalten, die Verhalten implementieren, das für alle Logger gleich ist – hier beispielhaft die Methode <code>register</code>. Nur die konkreten Erzeuger (z.B. <code>JDBCLoggerCreator</code>) sind von einer zugehörigen Logger-Implementierung (z.B. <code>JDBCLogger</code>) abhängig.</p>
<p>Das folgende UML-Klassendiagramm verallgemeinert das Logger-Beispiel, so dass das allgemeingültige Muster der GoF-Fabrikmethode entsteht, das durch Erich Gamma et al. <a href="#cite-GHJ+10">[GHJ+10]</a> wie folgt definiert ist:<p>
<p>Das folgende UML-Klassendiagramm verallgemeinert das Logger-Beispiel, sodass das allgemeingültige Muster der GoF-Fabrikmethode entsteht, das durch Erich Gamma et al. <a href="#cite-GHJ+10">[GHJ+10]</a> wie folgt definiert ist:<p>
<div class="cite">"Define an interface for creating an object, but let subclasses decide which class to instantiate. The factory method lets a class defer instantiation it uses to subclasses." (Erich Gamma et al.)</div>
......@@ -180,7 +180,7 @@ logger.log(Level.INFO, "Hello World!");
<h4>Abstrakte Fabrik</h4>
<p>Wenn die Fabrik-Klasse nicht nur ein Produkt, sondern gleich eine ganze Familie von zusammengehörigen Produkten erzeugen soll, wird die Fabrikmethode schnell zum Entwurfsmuster <a href="https://en.wikipedia.org/wiki/Abstract_factory_pattern"><Abstrakte Fabrik (engl. <i>Abstract Factory</i>)</a> ausgebaut. Ein beliebtes Beispiel ist hier im Kontext von UI-Frameworks die Erzeugung von UI-Elementen (wie Buttons, Textfelder, usw.), die jeweils einem bestimmten Design-Stil folgen sollen (wie Cupertino oder Material Design). Hierbei entsteht für jedes Produkt einer Produktfamilie eine separate Spezialisierungshierarchie. Die konkreten Produkte einer Familie werden alle aus einer gemeinsamen Fabrik-Klasse erzeugt, die dadurch einen konsistenten Design-Stil gewährleistet. Die Client arbeitet ausschließlich auf abstrakten Produkten, die er über eine konkrete Fabrik-Klasse erzeugt. Die Idee der Abstrakten Fabrik wird durch das folgende UML-Klassendiagramm und das zugehörige Code-Beispiel verdeutlicht.</p>
<p>Wenn die Fabrik-Klasse nicht nur ein Produkt, sondern gleich eine ganze Familie von zusammengehörigen Produkten erzeugen soll, wird die Fabrikmethode schnell zum Entwurfsmuster <a href="https://en.wikipedia.org/wiki/Abstract_factory_pattern">Abstrakte Fabrik (engl. <i>Abstract Factory</i>)</a> ausgebaut. Ein beliebtes Beispiel ist hier im Kontext von UI-Frameworks die Erzeugung von UI-Elementen (wie Buttons, Textfelder, usw.), die jeweils einem bestimmten Design-Stil folgen sollen (wie Cupertino oder Material Design). Hierbei entsteht für jedes Produkt einer Produktfamilie eine separate Spezialisierungshierarchie. Die konkreten Produkte einer Familie werden alle aus einer gemeinsamen Fabrik-Klasse erzeugt, die dadurch einen konsistenten Design-Stil gewährleistet. Der Client arbeitet ausschließlich auf abstrakten Produkten, die er über eine konkrete Fabrik-Klasse erzeugt. Die Idee der Abstrakten Fabrik wird durch das folgende UML-Klassendiagramm und das zugehörige Code-Beispiel verdeutlicht.</p>
<img src="media/patterns_abstractfactory_example.png" style="width:1000px">
<label>Anwendungsbeispiel für das Entwurfsmuster Abstrakte Fabrik</label>
......
......@@ -130,4 +130,4 @@ btn.setOnAction((ActionEvent e) -> System.out.println("Clicked button"));
}
</code></pre>
<p>Die Klasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/SubmissionPublisher.html"><code>SubmissionPublisher</code></a> bietet eine Standard-Implementierung des <code>Publisher</code>-Interface. Nachrichten, die über die Methode <code>submit</code> (Zeile 6) eingereicht werden, sendet der Publisher an alle aktuell registrierten Subscriber, bis er über die Methode <code>close</code> (Zeile 7) geschlossen wird. Hervorzuheben ist, dass ein Publisher die Nachrichten asynchron zustellt, d.h. er wartet nicht auf eine Empfangs- oder Verarbeitungsbestätigung der Subscriber, die ihrerseits jeweils in einem parallelen Thread (s. Kapitel <a href="#unit-threads" class="navigate">Threads in Java</a>) ablaufen. Jeder aktuelle Subscriber erhält neu übermittelte Nachrichten in derselben Reihenfolge, wie sie versendet werden - außer bei Exceptions (<code>onError</code>) oder bei Timeouts. Wenn eine Nachricht über <code>offer</code> anstatt <code>submit</code> versendet wird, kann ein Timeout definiert werden, falls ein Subscriber zu lange braucht, um die Nachricht entgegenzunehmen. Auf diese Weise können Publisher als nicht blockierende, <a href="http://www.reactive-streams.org/">reaktive Streams</a> fungieren. Damit der Subscriber-Thread ausreichend Zeit zur Verarbeitung der Nachrichten hat, schläft der Main-Thread im obigen Code eine Weile (Zeile 8). Das ist für die Praxis keine gute Lösung sondern ein <a href="https://de.wikipedia.org/wiki/Anti-Pattern">Anti-Pattern</a>, aber es trägt hier zur Erhaltung der Einfachheit des Beispiels bei.</p>
\ No newline at end of file
<p>Die Klasse <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/SubmissionPublisher.html"><code>SubmissionPublisher</code></a> bietet eine Standard-Implementierung des <code>Publisher</code>-Interface. Nachrichten, die über die Methode <code>submit</code> (Zeile 6) eingereicht werden, sendet der Publisher an alle aktuell registrierten Subscriber, bis er über die Methode <code>close</code> (Zeile 7) geschlossen wird. Hervorzuheben ist, dass ein Publisher die Nachrichten asynchron zustellt, d.h. er wartet nicht auf eine Empfangs- oder Verarbeitungsbestätigung der Subscriber, die ihrerseits jeweils in einem parallelen Thread (s. Kapitel <a href="#unit-threads" class="navigate">Threads in Java</a>) ablaufen. Jeder aktuelle Subscriber erhält neu übermittelte Nachrichten in derselben Reihenfolge, wie sie versendet werden - außer bei Exceptions (<code>onError</code>) oder bei Timeouts. Wenn eine Nachricht über <code>offer</code> anstatt <code>submit</code> versendet wird, kann ein Timeout definiert werden, falls ein Subscriber zu lange braucht, um die Nachricht entgegenzunehmen. Auf diese Weise können Publisher als nicht blockierende, <a href="http://www.reactive-streams.org/">reaktive Streams</a> fungieren. Damit der Subscriber-Thread ausreichend Zeit zur Verarbeitung der Nachrichten hat, schläft der Main-Thread im obigen Code eine Weile (Zeile 8). Das ist für die Praxis keine gute Lösung, sondern ein <a href="https://de.wikipedia.org/wiki/Anti-Pattern">Anti-Pattern</a>, aber es trägt hier zur Erhaltung der Einfachheit des Beispiels bei.</p>
\ No newline at end of file
......@@ -8,17 +8,17 @@
<img src="media/patterns_proxy.png" style="width:450px">
<label>Entwurfsmuster Proxy</label>
<p>Das Proxy-Muster bringt zunächst einen eindeutigen folgenden Nachteil mit sich: Es entsteht durch die Proxy-Klasse zusätzlicher Code, der die Komplexität im Call-Stack und den verbundenen Aufwand für Test und Wartung erhöht. Für die zusätzliche Kontrollebene, die durch den Proxy gewonnen wird, muss es also gute Gründe geben, die folgend exemplarisch vorgestellt werden. Voraussetzung für die Notwendigkeit einer Proxy-Klasse ist es, dass die dahinterliegende Klasse nicht verändert werden kann oder soll.</p>
<p>Das Proxy-Muster bringt zunächst einen eindeutigen Nachteil mit sich: Es entsteht durch die Proxy-Klasse zusätzlicher Code, der die Komplexität im Call-Stack und den verbundenen Aufwand für Test und Wartung erhöht. Für die zusätzliche Kontrollebene, die durch den Proxy gewonnen wird, muss es also gute Gründe geben, die folgend exemplarisch vorgestellt werden. Voraussetzung für die Notwendigkeit einer Proxy-Klasse ist es, dass die dahinterliegende Klasse nicht verändert werden kann oder soll.</p>
<ul>
<li><b>Schutz-Proxy</b>: Ein Schutz-Proxy fängt den Zugriff auf das echte Zielobjekt ob, um diesen ggf. nach Prüfung der Autorisierung einzuschränken. Ein solcher Proxy kann eingesetzt werden, wenn die abzusichernde Klasse selbst nicht angepasst werden kann, weil ihr Code nicht zugänglich ist oder Änderungen nicht zulässig sind (z.B. aus lizenzrechtlichen Gründen).</li>
<li><b>Schutz-Proxy</b>: Ein Schutz-Proxy fängt den Zugriff auf das echte Zielobjekt ab, um diesen ggf. nach Prüfung der Autorisierung einzuschränken. Ein solcher Proxy kann eingesetzt werden, wenn die abzusichernde Klasse selbst nicht angepasst werden kann, weil ihr Code nicht zugänglich ist oder Änderungen nicht zulässig sind (z.B. aus lizenzrechtlichen Gründen).</li>
<li><b>Remote-Proxy</b>: Ein Remote-Proxy ist ein Stellvertreterobjekt auf der Seite des Client, wenn dieser Methoden auf Remote-Objekten aufruft, die in einer anderen Laufzeitumgebung (z.B. einer entfernten JVM) residieren. Die andere Laufzeitumgebung wird in einem anderen Prozess und Adressraum ausgeführt, meistens sogar auf einem anderen Host, und bietet einen <i>Remote Service</i> an, der von außen aufgerufen werden kann (s. Kapitel <a href="#unit-rmi" class="navigate">Remote Method Invocation</a> und <a href="#unit-soap" class="navigate">SOAP-Webservices</a>). Der Remote-Proxy ist der Endpunkt für diese Interprozesskommunikation im Adressraum des Client – als Stellvertreter des Remote-Objekts im entfernten Adressraum. Der Proxy übernimmt das Verpacken der Input-Argumente bei einem Methodenaufruf auf dem Remote-Objekt in ein Serialisierungsformat (z.B. <code>java.io.Serializable</code>) und das entsprechende Entpacken des Return-Arguments, wenn die Antwort eingeht. Der Proxy delegiert die erforderliche Netzwerkkommunikation an die Laufzeitumgebung bzw. das Betriebssystem und kümmert sich um Fehlerfälle wie z.B. <i>Timeouts</i>.</li>
<li><b>Virtueller Proxy</b>: Ein virtueller Proxy wird eingesetzt, wenn die Erzeugung des Zielobjekts sehr aufwändig ist, d.h. es werden dazu viele Ressourcen beansprucht. Der Proxy kann in diesem Fall die vollständige Erzeugung des Zielobjekts verzögern oder sie in einzelne Teilschritte zerlegen. Da der Proxy den Zugriff auf das Zielobjekt kontrolliert, kann er dessen einzelne Teile ggf. erst bei Bedarf, d.h. beim ersten Zugriff, erzeugen lassen (= <i>Lazy Loading</i>). Ein virtueller Proxy eignet sich wenn das eigentliche Zielobjekt groß und behäbig ist, z.B. beim Einlesen eines komplexen Objekts aus einer sehr großen Datei oder Datenbank. Ein virtueller Proxy lässt sich häufig sinnvoll mit einem Remote-Proxy kombinieren, wenn über eine Netzwerkverbindung mit eingeschränkter Datenübertragungsrate kommuniziert wird, z.B. beim Mobilfunk mit schlechtem Empfang.</li>
</ul>
<p>Im folgenden Code-Beispiel zur Verdeutlichung des Proxy-Musters besteht die Funktion des Zielobjekts <code>RealVideo</code> darin eine Video-Datei aus dem Web zu laden und abzuspielen. Das Laden des Videos findet im Konstruktor der Klasse <code>RealVideo</code> statt (Zeile 6). Das Abspielen startet bei Aufruf der Methode <code>play</code> (Zeilen 10-14). Das Interface <code>Video</code> schreibt nur diese Methode <code>play</code> vor, die auch von der Proxy-Klasse <code>ProxyVideo</code> implementiert wird. Die Proxy-Klasse verändert das Verhalten des Zielobjekts um zwei Aspekte:
<ul>
<li>Das Laden des Videos wird verzögert und findet nicht im Konstruktor sondern erst direkt vor dem Abspielen in der Methode <code>play</code> statt (Lazy Loading).</li>
<li>Das Laden des Videos wird verzögert und findet nicht im Konstruktor, sondern erst direkt vor dem Abspielen in der Methode <code>play</code> statt (Lazy Loading).</li>
<li>Videos, die kürzer sind als 30 Sek., werden gar nicht erst abgespielt, da es sich (in diesem Fall) um Werbung handelt.</li>
</ul></p>
......
<p>Beginnen wir mit einem der einfachsten Erzeugungsmuster: dem Einzelstück (engl. <i>Singleton</i>). Das Singleton-Muster ist eng mit der Frage verknüpft, wie zur Laufzeit einer objektorientierten Anwendung sichergestellt werden kann, dass max. 1 Objekt einer Klasse erzeugt werden kann.</p>
<p>Singleton-Muster soll über folgendes Beispiel motiviert werden: Stellen wir uns vor, dass wir in einer Java-Anwendung eine Verbindung zu einer Datenbank aufbauen wollen. Dazu wird die folgende Klasse <code>DatabaseConnection</code> entworfen, die eine Methode zum Verbindungsaufbau und weitere Methoden zum Abfragen und Verändern der Daten enthält. Diese fachlichen Methoden werden in den Code-Listings weiter unten ausgeblendet.</p>
<p>Das Singleton-Muster soll über folgendes Beispiel motiviert werden: Stellen wir uns vor, dass wir in einer Java-Anwendung eine Verbindung zu einer Datenbank aufbauen wollen. Dazu wird die folgende Klasse <code>DatabaseConnection</code> entworfen, die eine Methode zum Verbindungsaufbau und weitere Methoden zum Abfragen und Verändern der Daten enthält. Diese fachlichen Methoden werden in den Code-Listings weiter unten ausgeblendet.</p>
<pre><code class="language-java line-numbers">public class DatabaseConnection {
public boolean connect(String host, String user, String pass, String db) { /* ... */ }
......@@ -8,7 +8,7 @@
/* ... */
}</code></pre>
<p>Es ist ausreichend, wenn diese Datenbankverbindung beim Start der Anwendung nur einmal aufgebaut wird, und es demzufolge nur ein Objekt dieser Klasse gibt. Dieses Objekt entspricht einer globalen Variable. Es soll nicht möglich sein, dass aus Methoden in beliebigen anderen Klassen in unserem Projekt oder durch Dritte die unsere Klasse weiterverwenden <code>DatabaseConnection</code>-Instanzen erzeugt werden können. Um genau das zu verhindern, wird die Sichtbarkeit des Konstruktors der Klasse <code>DatabaseConnection</code> auf <code>private</code> gesetzt. Damit können nur noch aus der Klasse selbst Instanzen erzeugt werden. In der Klasse selbst wird ein statisches Attribut vom Typ <code>DatabaseConnection</code> angelegt, auf das über eine ebenfalls statische und öffentliche Methode zugegriffen werden kann. In folgendem Code wird das Attribut <code>instance</code> bereits beim Laden der Klasse instantiiert.</p>
<p>Es ist ausreichend, wenn diese Datenbankverbindung beim Start der Anwendung nur einmal aufgebaut wird, und es demzufolge nur ein Objekt dieser Klasse gibt. Dieses Objekt entspricht einer globalen Variable. Es soll nicht möglich sein, dass aus Methoden in beliebigen anderen Klassen in unserem Projekt oder durch Dritte, die unsere Klasse weiterverwenden, <code>DatabaseConnection</code>-Instanzen erzeugt werden können. Um genau das zu verhindern, wird die Sichtbarkeit des Konstruktors der Klasse <code>DatabaseConnection</code> auf <code>private</code> gesetzt. Damit können nur noch aus der Klasse selbst Instanzen erzeugt werden. In der Klasse selbst wird ein statisches Attribut vom Typ <code>DatabaseConnection</code> angelegt, auf das über eine ebenfalls statische und öffentliche Methode zugegriffen werden kann. In folgendem Code wird das Attribut <code>instance</code> bereits beim Laden der Klasse instantiiert.</p>
<pre><code class="language-java line-numbers">public class DatabaseConnection {
// Singleton object
......@@ -20,7 +20,7 @@
/* ... */
}</code></pre>
<p>Soll die Erzeugung des Singleton-Objekt nicht beim Laden der Klasse sondern erst beim ersten Zugriff erfolgen (<i>Lazy Creation</i>), muss die Zugriffsmethode durch das Java-Schlüsselwort <code>synchronized</code> als kritischer Abschnitt geschützt werden, so dass nur ein paralleler <i>Thread</i> gleichzeitig diese Methode durchlaufen kann. Threads und kritische Abschnitte werden erst später im Kapitel <a href="#unit-sync" class="navigate">Synchronisation von Threads</a> behandelt.</p>
<p>Soll die Erzeugung des Singleton-Objekts nicht beim Laden der Klasse, sondern erst beim ersten Zugriff erfolgen (<i>Lazy Creation</i>), muss die Zugriffsmethode durch das Java-Schlüsselwort <code>synchronized</code> als kritischer Abschnitt geschützt werden, sodass nur ein paralleler <i>Thread</i> gleichzeitig diese Methode durchlaufen kann. Threads und kritische Abschnitte werden erst später im Kapitel <a href="#unit-sync" class="navigate">Synchronisation von Threads</a> behandelt.</p>
<p>Wenn das Beispiel der Datenbankverbindung verallgemeinert wird (d.h. die Klasse heißt nun <code>Singleton</code> anstatt <code>DatabaseConnection</code>), erhalten wir das geläufige Erzeugungsmuster Singleton, das zusichert, dass zur Laufzeit max. 1 Objekt einer Klasse instantiiert werden kann.</p>
......
......@@ -17,7 +17,7 @@ appears to its users as a single coherent system." (Andrew Tanenbaum)</div>
<p>Es existiert eine Vielzahl von Protokollen zur Kommunikation in einem verteilten System, d.h. zum Austausch von Nachrichten/Daten zwischen Software-Komponenten, die in Prozessen auf verschiedenen Computern ausgeführt werden und über ein Netzwerk miteinander verbunden sind. Entsprechend den Anforderungen eines konkreten Anwendungsfalls ist ein geeignetes Protokoll als Grundlage für diese Interprozesskommunikation auszuwählen. Eine erste Orientierung bietet das <a href="https://de.wikipedia.org/wiki/Internetprotokollfamilie">TCP/IP-Modell</a>, das die Kommunikationsprotokolle gemäß der folgenden Abbildung in 4 Schichten untergliedert.</p>
<img src="media/distributed_tcp_ip_model.png" style="width:900px">
<label>Protokolle zur Remote-Kommunikation und das TCP/IP-Schichtenmodell</a></label>
<label>Protokolle zur Remote-Kommunikation und das TCP/IP-Schichtenmodell</label>
<p>Aus der Perspektive eines Anwendungsentwicklers möchten wir uns auf die Protokolle der unteren Schichten (Transport-, Internet-, Netzzugangsschicht im TCP/IP-Modell) verlassen und ein adäquates Protokoll auf Ebene der Anwendungsschicht nutzen. Wir verfolgen grundsätzlich das Ziel, dass eine lokale Client-Komponente eine Kommunikationsverbindung zu einer entfernten (engl. <i>remote</i>) Server-Komponente aufbaut und anschließend anwendungsspezifische Daten austauschen kann. In den nächsten Kapiteln werden dazu folgende alternative Ansätze zur sogenannten Remote-Kommunikation am Beispiel von Java-Anwendungen vorgestellt:</p>
<ul>
......@@ -53,7 +53,7 @@ appears to its users as a single coherent system." (Andrew Tanenbaum)</div>
<p>Es ergeben sich folgende Einflussfaktoren auf die Performance einer Komponente zur Laufzeit:</p>
<img src="media/distributed_performance_drivers.png" style="width:600px">
<label>Einflussfaktoren auf die Antwortzeit einer Komponente</a></label>
<label>Einflussfaktoren auf die Antwortzeit einer Komponente</label>
<ul>
<li><b>Implementierung</b>: In Abhängigkeit der verwendeten Datenstrukturen und Algorithmen kann eine Komponente unterschiedlich effizient implementiert werden.</li>
......@@ -66,18 +66,18 @@ appears to its users as a single coherent system." (Andrew Tanenbaum)</div>
<p>Skalierbarkeit drückt das Verhältnis zwischen der Steigerung der Problemgröße (z.B. mehr Workload) und den zur Lösung zusätzlich benötigten Ressourcen aus. Antwortzeiten und Durchsatz skalieren bei steigendem Workload und beschränkter Ressourcen-Kapazität bis zum Erreichen der Kapazitätsgrenze etwa linear. Anschließend steigen die Antwortzeiten exponentiell, da eine bestimmte Ressource (z.B. CPU, RAM, I/O) aufgrund von Überlastung zum Engpass wird. Dieser typische Effekt ist in der folgenden Abbildung dargestellt. Gleichermaßen erreicht auch der Durchsatz einen Sättigungspunkt, an dem die Komponente oder das System einfach nicht mehr Anfragen pro Zeitintervall verarbeiten kann. In automatisch skalierenden Cloud-Plattformen kann dieser Sättigungspunkt soweit hinausgezögert werden, das er praktisch nicht erreicht wird. Allgemein ist es aus Gründen der Wirtschaftlichkeit zu empfehlen, die bereitgestellten Hardware-Ressourcen nicht zu stark überzudimensionieren.</p>
<img src="media/distributed_scalability.png" style="width:600px">
<label>Skalierbarkeit von Antwortzeit und Durchsatz bei beschränkter Ressourcen-Kapazität</a></label>
<label>Skalierbarkeit von Antwortzeit und Durchsatz bei beschränkter Ressourcen-Kapazität</label>
<p>Um die Ressourcen-Kapazität eines Systems auszubauen, gibt es zwei grundlegende Ansätze: die vertikale Skalierung (<i>Scale Up</i>) und die horizontale Skalierung (<i>Scale Out</i>). Bei der vertikalen Skalierung wird ein leistungsstarker Großrechner angestrebt, der sukzessiv ausgebaut wird. Dieser Ansatz gilt als vergleichsweise teuer. Außerdem stellt der Großrechner einen möglichen <i>Single Point of Failure</i> dar. Daher wird heute horizontale Skalierung durch ein verteiltes System von vielen gleichartigen Knoten favorisiert. In einem derartigen <i>Cluster</i> wird der Ausfall von wenigen Knoten als Normalzustand angenommen, der die Verfügbarkeit und die Konsistenz der Daten nicht gefährden darf. Zwar ist ein Cluster komplexer zu beherrschen als ein einzelner Großrechner, kann dafür aber skalierbare Performance und Datensicherheit gleichermaßen gewährleisten.</p>
<img src="media/distributed_scale_out.png" style="width:700px">
<label>Vertikale und horizontale Skalierung</a></label>
<label>Vertikale und horizontale Skalierung</label>
<h4>Datensicherheit durch Replikation</h4>
<p>Nehmen wir an, dass wie oben dargestellt ein Cluster von gleichartigen Knoten aufgebaut worden ist, um auch bei hohem Workload die geforderte Performance des Systems zu gewährleisten. Eine Komponente wird innerhalb des Clusters nun mehrfach instantiiert, damit der Dienst, den sie anbietet, ebenso skaliert. Jede Instanz dieser Komponente wird nur einen Ausschnitt der gesamten Datenbasis verarbeiten und auf dem Knoten, auf dem sie ausgeführt wird, speichern. Die Daten werden in einem verteilten Datei- oder Datenbanksystem in sogenannte Partitionen (oder auch <i>Shards</i>) zerlegt. Einzelne Datenobjekte/Datensätze werden i.d.R. über eine Hashfunktion einer Partition zugeordnet. Die folgende Abbildung zeigt ein Cluster mit 4 Knoten, auf denen jeweils eine Instanz der gleichen Komponente ausgeführt wird. Die Datenbasis wurde in 6 Partitionen (A-F) unterteilt. Da das System keine Daten verlieren darf, wenn ein Knoten ausfällt, werden die Partitionen repliziert. Im dargestellten Beispiel ist der Replikationsfaktor 3, d.h. von jeder Partition gibt 3 identische Replikate, so dass das abgebildete System den Ausfall beliebiger 2 Knoten tolerieren kann. Die Anzahl der Knoten und insbesondere der Partitionen ist in der Praxis häufig deutlich größer. Eine größere Anzahl an Partitionen als Knoten vereinfacht die Umverteilung der Partitionen, wenn ein neuer Knoten in den Cluster aufgenommen wird oder ein Knoten ausscheidet.</p>
<img src="media/distributed_replication.png" style="width:700px">
<label>Partitionierung und Replikation von Daten im verteilten System</a></label>
<label>Partitionierung und Replikation von Daten im verteilten System</label>
<p>Ein Kompromiss zwischen Verfügbarkeit und Konsistenz der Daten ergibt sich dadurch, dass die Replikate nach einem Schreibzugriff synchronisiert werden müssen. Es stellt sich in einem verteilten System die Frage, ob während der Synchronisation bedingt durch einen Schreibzugriff entweder ein veralteter Zustand gelesen werden darf (d.h. Verfügbarkeit nicht eingeschränkt, aber Konsistenz eingeschränkt) oder das Lesen bis zum Ende der Synchronisation verzögert wird (d.h. Konsistenz nicht eingeschränkt, aber Verfügbarkeit eingeschränkt). In einem verteilten System ist grundsätzlich mit dem Ausfall einzelner Knoten oder der vorübergehenden Unterbrechung der Netzwerkverbindungen zu rechnen, was zu Verzögerungen in der Synchronisation führt. Der Begriff Partitionstoleranz beschreibt, dass das System bis zu einem gewissen Grad tolerant gegenüber einem kurzzeitigen Verfall des Systems in unverbundene Teile sein muss. Der Kompromiss zwischen der Konsistenz (engl. <i><u>C</u>onsistency</i>), der Verfügbarkeit (engl. <i><u>A</u>vailability</i>) und der Partitionstoleranz (engl. <i><u>P</u>artition Tolerance</i>) in einem verteilten System ist als <a href="https://en.wikipedia.org/wiki/CAP_theorem">CAP-Theorem</a> bekannt. Verteilte Datenbanken werden in diesem Model nicht weiter behandelt. Einen fundierten Überblick liefert das populäre Buch "Designing Data-Intensive Applications" von Martin Kleppmann <a href="#cite-Kle17">[Kle17]</a>.
......@@ -160,7 +160,7 @@ public class Person {
<h4>JPA-Persistenzkontext</h4>
<p>Der JPA-Persistenzkontext je Session wird durch einen sogenanntes <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html"><code>EntityManager</code></a>-Objekt verwaltet. Wie in der folgenden Abbildung dargestellt wird, erfolgt in JPA jeder Lese- oder Schreibzugriff auf die Datenbank über Methoden des <i>EntityManager</i>. Das Pendant zum eher abstrakten Begriff <i>EntityManager</i> wird in Hibernate etwas sprechender als <i>Session</i> bezeichnet. Das Interface <code>org.hibernate.Session</code> erweitert dementsprechend das Interface <code>javax.persistence.EntityManager</code>. Der EntityManager verwaltet also sämtliche Entitäten, die während einer Session mit der Datenbank ausgetauscht werden.</p>
<p>Der JPA-Persistenzkontext je Session wird durch ein sogenanntes <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html"><code>EntityManager</code></a>-Objekt verwaltet. Wie in der folgenden Abbildung dargestellt wird, erfolgt in JPA jeder Lese- oder Schreibzugriff auf die Datenbank über Methoden des <i>EntityManager</i>. Das Pendant zum eher abstrakten Begriff <i>EntityManager</i> wird in Hibernate etwas sprechender als <i>Session</i> bezeichnet. Das Interface <code>org.hibernate.Session</code> erweitert dementsprechend das Interface <code>javax.persistence.EntityManager</code>. Der EntityManager verwaltet also sämtliche Entitäten, die während einer Session mit der Datenbank ausgetauscht werden.</p>
<img src="media/distributed_orm_entitymanager.png" style="width:700px">
<label>EntityManager in JPA</label>
......@@ -214,7 +214,7 @@ EntityManager em = emf.createEntityManager();</code></pre>
<pre><code class="language-java line-numbers">em.clear(); // clear 1st level cache
em.getEntityManagerFactory().getCache().evictAll(); // clear 2nd level cache</code></pre>
<h4>Transaktionen in JPA</h4
<h4>Transaktionen in JPA</h4>
<p>In dem folgenden Code-Beispiel wird ein <code>Department</code>-Objekt mit einem zugeordneten <code>Person</code>-Objekt persistiert. Zu beachten ist, dass jeder schreibende Zugriff auf die Datenbank explizit in eine Transaktion eingebettet werden muss.</p>
......@@ -294,7 +294,7 @@ public class Department {
// ...
}</code></pre>
<p>Beim <i>Lazy Loading</i> wird das entsprechende Attribut erst bei einem späteren Zugriff befüllt. In folgendem Code-Beispiel löst die Zeile 1 eine oder zwei SQL-Anfragen aus: zum einen wird stets der gesuchte Datensatz aus der Tabelle <code>department</code> gelesen, zum anderen der zugehörige Abteilungsleiter ermittelt, falls das Attribut <code>lead</code> in der Klasse <code>Department</code> gesetzt ist. Das Laden der zugeordneten Personen im Attribut <code>staff</code> durch eine SQL-Anfrage auf die Tabelle <code>person</code> erfolgt erst beim ersten Zugriff auf eine Methode dieses Attributs in Zeile 8. Bis dahin ist das Attribut nicht instantiiert (vgl. Zeilen 5-6). Im Debug-Modus kann das Lazy Loading nicht ohne Weiteres beobachtet werden, da der Debugger selbst das Laden der Attribute verursacht, um deren aktuellen Status anzeigen zu können.</p>
<p>Beim <i>Lazy Loading</i> wird das entsprechende Attribut erst bei einem späteren Zugriff befüllt. Im folgenden Code-Beispiel löst die Zeile 1 eine oder zwei SQL-Anfragen aus: zum einen wird stets der gesuchte Datensatz aus der Tabelle <code>department</code> gelesen, zum anderen der zugehörige Abteilungsleiter ermittelt, falls das Attribut <code>lead</code> in der Klasse <code>Department</code> gesetzt ist. Das Laden der zugeordneten Personen im Attribut <code>staff</code> durch eine SQL-Anfrage auf die Tabelle <code>person</code> erfolgt erst beim ersten Zugriff auf eine Methode dieses Attributs in Zeile 8. Bis dahin ist das Attribut nicht instantiiert (vgl. Zeilen 5-6). Im Debug-Modus kann das Lazy Loading nicht ohne Weiteres beobachtet werden, da der Debugger selbst das Laden der Attribute verursacht, um deren aktuellen Status anzeigen zu können.</p>
<pre><code class="language-java line-numbers">Department dept = em.find(Department.class, 1); // finds person with id = 1
// causes department query: SELECT id, label, lead_id FROM department WHERE id = ?
......@@ -325,7 +325,7 @@ später zu einem ungewollten Zeitpunkt während der Anwenderinteraktion geschehe
</table>
<p>Dazu zählen eine Methode für native SQL-Anfragen und eine Methode zum Aufruf von <i>Stored Procedures</i>. Bevorzugt werden aber Anfragen über die <i>Java Persistence Query Language (JPQL)</i>, die ihrerseits stark an SQL angelehnt ist. JPQL orientiert sich in seiner deklarativen Syntax mit <code>SELECT</code> ... <code>FROM</code> ... <code>WHERE</code> ... <code>GROUP BY</code> ... <code>ORDER BY</code> ... an dem gleichen Gerüst an Schlüsselworten für eine Anfrage wie SQL.
Viele Operatoren (z.B. <code>LIKE</code>, <code>IN</code>) und Aggregatfunktionen (z.B. <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>) verhalten sich ebenfalls wie in SQL. Ein wichtiger Unterschied ist, dass in der <code>FROM</code>-Klausel nicht die zugrundeliegende Tabelle sondern die objektorientierte Klasse adressiert wird. Demzufolge ist auch die Navigation entlang von Assoziationen über den Operator <code>.</code> möglich, welcher bei der Übersetzung in eine SQL-Anfrage implizit einen Join verursachen kann. Zur Veranschaulichung dienen die folgenden Beispiele.</p>
Viele Operatoren (z.B. <code>LIKE</code>, <code>IN</code>) und Aggregatfunktionen (z.B. <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>) verhalten sich ebenfalls wie in SQL. Ein wichtiger Unterschied ist, dass in der <code>FROM</code>-Klausel nicht die zugrundeliegende Tabelle, sondern die objektorientierte Klasse adressiert wird. Demzufolge ist auch die Navigation entlang von Assoziationen über den Operator <code>.</code> möglich, welcher bei der Übersetzung in eine SQL-Anfrage implizit einen Join verursachen kann. Zur Veranschaulichung dienen die folgenden Beispiele.</p>
<pre><code class="language-sql line-numbers">-- Select all persons and their associated department label (causes a join of the person table and the department table)
SELECT p.id, p.name, p.department.label FROM Person p;
......@@ -511,7 +511,7 @@ Nachteile:
</ul><br>
</li>
<li><b>Table per Class</b>: Bei dieser Vererbungsstrategie entsteht eine Tabelle für jede konkrete Klasse einer Vererbungshierarchie. Jede Tabelle enthält nicht nur die Attribute der konkreten Unterklasse sondern auch sämtliche Attribute ihrer Oberklassen. Dadurch gibt es im relationalen Modell keine Fremdschlüssel. Es sind keine Joins erforderlich.
<li><b>Table per Class</b>: Bei dieser Vererbungsstrategie entsteht eine Tabelle für jede konkrete Klasse einer Vererbungshierarchie. Jede Tabelle enthält nicht nur die Attribute der konkreten Unterklasse, sondern auch sämtliche Attribute ihrer Oberklassen. Dadurch gibt es im relationalen Modell keine Fremdschlüssel. Es sind keine Joins erforderlich.
<pre><code class="language-java line-numbers">@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
......
......@@ -233,7 +233,7 @@ public class AccountEntry {
<span>Erläuterungen zu den Annotationen aus dem Package <code>javax.ws.rs</code> in dem obigen Code-Beispiel:</span>
<ul>
<li><b>@Path</b>: In der Klasse <code>WebServiceStarter</code> wird ein HTTP-Server gestartet, dem eine Ressourcen-Konfiguration als Argument übergeben wird (s. <code>WebServiceStarter</code>, Zeilen 7-8). Mittels der Ressourcen-Konfiguration wird Jersey angewiesen alle Klassen im Package <code>resources</code> dahingehend zu analysieren, ob sie mit einer Annotation <code>@Path</code> versehen sind (z.B. <code>AccountResource</code>, Zeile 9). Falls diese Annotation vorhanden ist, handelt es sich um eine REST-Ressourcenklasse, die unter dem entsprechenden Pfad erreichbar gemacht wird.</li>
<li><b>@GET</b>, <b>@POST</b>, etc.: Die einzelnen Methoden einer Ressourcenklasse reagieren auf nur spezifische HTTP-Methoden, wenn sie mit einer entsprechenden Annotation versehen sind, z.B. <code>@GET</code> (Zeile 14) oder <code>@POST</code> (Zeile 18).</li>
<li><b>@GET</b>, <b>@POST</b>, etc.: Die einzelnen Methoden einer Ressourcenklasse reagieren nur auf spezifische HTTP-Methoden, wenn sie mit einer entsprechenden Annotation versehen sind, z.B. <code>@GET</code> (Zeile 14) oder <code>@POST</code> (Zeile 18).</li>
<li><b>@PathParam</b>: Der Pfad einer Ressourcenklasse kann durch eine entsprechende Annotation an ihren Methoden dynamisch erweitert werden, z.B. <code>@Path("{id}")</code> (Zeile 23). Die angehängte Id wird von Jersey aus der URI extrahiert und mittels der Annotation <code>@PathParam("id")</code> als Methodenargument injiziert (Zeile 24). Hier wird implizit ein Cast von <code>String</code> auf <code>int</code> durchgeführt, der auch fehlschlagen kann.</li>
<li><b>@Consumes</b> und <b>@Produces</b>: An den Methoden einer Ressourcenklasse kann mittels der Annotationen <code>@Consumes</code> (Zeile 19) und <code>@Produces</code> (Zeile 15) explizit angegeben werden, in welchen Formaten die annotierten Methoden Eingaben verarbeiten bzw. Ausgaben erzeugen können. Diese Annotationen sind insbesondere dann notwendig, wenn je nach Eingabeformat (HTTP-Header <code>Content-Type</code>) oder angefordertem Ausgabeformat (HTTP-Header <code>Accept</code>) eine unterschiedliche Methode der Ressourcenklasse ausgeführt werden soll.</li>
</ul>
......@@ -531,7 +531,7 @@ public class AccountResourceTest extends JerseyTest {
<h4>JWT (JSON Web Token)</h4>
<p>REST-Webservices sind per Konvention zustandslos, d.h. auf dem Server werden keine Sessions für die aktuell verbundenen Clients verwalten. Demzufolge muss jeder Client selbst seine Session verwaltet und mit jedem HTTP-Request ein Token o.ä. an den Server senden, über das er authentifiziert und für den Zugriff autorisiert werden kann. Eine standardisierte Möglichkeit zur Erzeugung derartiger Tokens findet sich in <a href="https://tools.ietf.org/html/rfc7519">RFC 7519</a>, in dem sogenannte <a href="https://jwt.io/"><i>JSON Web Token (JWT)</i></a> eingeführt werden.</p>
<p>REST-Webservices sind per Konvention zustandslos, d.h. auf dem Server werden keine Sessions für die aktuell verbundenen Clients verwaltet. Demzufolge muss jeder Client selbst seine Session verwalten und mit jedem HTTP-Request ein Token o.ä. an den Server senden, über das er authentifiziert und für den Zugriff autorisiert werden kann. Eine standardisierte Möglichkeit zur Erzeugung derartiger Tokens findet sich in <a href="https://tools.ietf.org/html/rfc7519">RFC 7519</a>, in dem sogenannte <a href="https://jwt.io/"><i>JSON Web Token (JWT)</i></a> eingeführt werden.</p>
<div class="cite"><a href="https://tools.ietf.org/html/rfc7519">"JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted." (Internet Engineering Task Force)</a></div>
......@@ -686,7 +686,7 @@ public class UserResource {
<li>Serialisieren des JWS-Objekts in das JWT-Format (Zeile 22).</li>
</ul>
</li>
<li><code>AuthenticationFilter</code>: Das JWT, das der Client bei weiteren Anfragen im HTTP-Header mitsendet, soll bei gesicherten Ressourcen-Methoden geprüft werden. Die Sicherung einer Ressourcen-Methode erfolgt über einen eigene Annotation, die hier <code>@Secured</code> heißen soll und in den Zeilen 12-13 der Klasse <code>AuthenticationFilter</code> deklariert wird. Diese Klasse implementiert das Interface <code>ContainerRequestFilter</code> und funktioniert ihrem Namen entsprechend als Filter vor jedem Aufruf einer Methode mit der Annotation <code>@Secured</code>. Die Verifikation des JWT erfolgt in drei Schritten:
<li><code>AuthenticationFilter</code>: Das JWT, das der Client bei weiteren Anfragen im HTTP-Header mitsendet, soll bei gesicherten Ressourcen-Methoden geprüft werden. Die Sicherung einer Ressourcen-Methode erfolgt über eine eigene Annotation, die hier <code>@Secured</code> heißen soll und in den Zeilen 12-13 der Klasse <code>AuthenticationFilter</code> deklariert wird. Diese Klasse implementiert das Interface <code>ContainerRequestFilter</code> und funktioniert ihrem Namen entsprechend als Filter vor jedem Aufruf einer Methode mit der Annotation <code>@Secured</code>. Die Verifikation des JWT erfolgt in drei Schritten:
<ul>
<li>Zunächst wird das JWT als sogenanntes <a href="https://www.iana.org/assignments/http-authschemes/">Bearer Token</a> aus dem HTTP-Header extrahiert (Zeilen 17-23).</li>
<li>Anschließend wird aus dem JWT wieder ein JWS-Objekt erzeugt (Zeile 27) und dieses mit demselben <i>Secret</i> verifiziert, das auch zur Signatur verwendet worden ist (Zeilen 28-29).</li>
......
......@@ -2,13 +2,13 @@
<h4>SOAP</h4>
<p>Eine konkrete RPC-Implementierung ist hingegen das XML-basierte Protokoll <i><a href="https://www.w3.org/TR/soap/">SOAP</a></i>, das seit 2000 vom <a href="https://de.wikipedia.org/wiki/World_Wide_Web_Consortium">W3C</a> als empfohlener Standard etabliert worden ist. Wie wir noch im Detail sehen werden, definiert SOAP ein XML-Schema zur Repräsentation der auszutauschenden Daten in XML und nutzt die üblichen Protokolle des <a href="https://de.wikipedia.org/wiki/Internetprotokollfamilie">TCP/IP-Modells</a> zur Nachrichtenübertragung, auf oberste Ebene i.d.R. HTTPS. Ursprünglich war SOAP ein Akronym für <i>Simple Object Access Protocol</i>. Seit der aktuellen <a href="https://www.w3.org/TR/soap12/">Version 1.2</a> soll SOAP aber nicht mehr als Abkürzung sondern als Eigenname verstanden werden, da es subjektiv nicht unbedingt einfach (<i>simple</i>) ist und nicht ausschließlich dem Zugriff auf Objekte (<i>object access</i>) dient. Historisch geht SOAP auf das Protokoll <a href="https://en.wikipedia.org/wiki/XML-RPC">XML-RPC</a> zurück, das Microsoft und Dave Winer 1998 veröffentlicht haben.</p>
<p>Eine konkrete RPC-Implementierung ist hingegen das XML-basierte Protokoll <i><a href="https://www.w3.org/TR/soap/">SOAP</a></i>, das seit 2000 vom <a href="https://de.wikipedia.org/wiki/World_Wide_Web_Consortium">W3C</a> als empfohlener Standard etabliert worden ist. Wie wir noch im Detail sehen werden, definiert SOAP ein XML-Schema zur Repräsentation der auszutauschenden Daten in XML und nutzt die üblichen Protokolle des <a href="https://de.wikipedia.org/wiki/Internetprotokollfamilie">TCP/IP-Modells</a> zur Nachrichtenübertragung, auf oberster Ebene i.d.R. HTTPS. Ursprünglich war SOAP ein Akronym für <i>Simple Object Access Protocol</i>. Seit der aktuellen <a href="https://www.w3.org/TR/soap12/">Version 1.2</a> soll SOAP aber nicht mehr als Abkürzung, sondern als Eigenname verstanden werden, da es subjektiv nicht unbedingt einfach (<i>simple</i>) ist und nicht ausschließlich dem Zugriff auf Objekte (<i>object access</i>) dient. Historisch geht SOAP auf das Protokoll <a href="https://en.wikipedia.org/wiki/XML-RPC">XML-RPC</a> zurück, das Microsoft und Dave Winer 1998 veröffentlicht haben.</p>
<span>Im Folgenden soll das Bankkontoservice-Anwendungsbeispiel aus dem vorherigen <a href="#unit-rmi" class="navigate">Kapitel zu RMI</a> in einen SOAP-Webservice überführt werden. Dabei soll die fachliche Funktionaliät des Anwendungsbeispiels möglichst nicht verändert werden. Die einzelnen Methoden des Webservice sind:</span>
<ul>
<li><code>String createAccount(String: owner)</code>: In dieser Methode wird ein neues Bankkonto für den angegebenen Eigentümer erzeugt. Die eindeutige Id des Kontos (vom Typ <code>java.util.UUID</code>) wird in ihrer String-Repräsentation zurückgegeben. Die Klasse <code>java.util.UUID</code> selbst kann nicht als Input- oder Return-Argument verwendet werden, da die Klasse keinen leeren Default-Konstruktor besitzt, der für die Objekterzeugung per Reflection API erforderlich ist.</li>
<li><code>void addAccountEntry(String id, AccountEntry entry)</code>: In dieser Methode wird einem Bankkonto ein neuer Kontoeintrag (<code>AccountEntry</code>) hinzugefügt. Ein Kontoeintrag besteht weiterhin aus einem Betreff, einem Wert und einem Buchungsdatum. Der Klasse <code>AccountEntry</code> ist ein leerer Default-Konstruktor hinzugefügt worden.</li>
<li><code>AccountEntry[] getAccountEntries(String id)</code>: Diese Methode gibt alle Kontoeinträge zu einem Konto als Array zurück. Da die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB (Java Architecture for XML Binding)</a> nicht ohne weiteres Schnittstellen wie z.B. <code>java.util.List</code> verarbeiten kann, ist das Return-Argument der Methode ein Array. JAXB übernimmt in der Java-Implementierung (s. unten) implizit die Serialisierung von Java-Objekten in eine XML-Repräsentation und die Deserialisierung in die andere Richtung.</li>
<li><code>AccountEntry[] getAccountEntries(String id)</code>: Diese Methode gibt alle Kontoeinträge zu einem Konto als Array zurück. Da die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB (Java Architecture for XML Binding)</a> nicht ohne Weiteres Schnittstellen wie z.B. <code>java.util.List</code> verarbeiten kann, ist das Return-Argument der Methode ein Array. JAXB übernimmt in der Java-Implementierung (s. unten) implizit die Serialisierung von Java-Objekten in eine XML-Repräsentation und die Deserialisierung in die andere Richtung.</li>
<li><code>int getAccountBalance(String id)</code>: Diese Methode gibt den Saldo (= Summe der Werte aller Kontoeinträge) eines Kontos zurück.</li>
</ul>
......@@ -266,12 +266,12 @@ public class AccountNotFoundException extends Exception { }</code></pre>
<ul>
<li><code>WebServiceStarter</code>: In Zeile 10 wird der Webservice gestartet und unter dem Endpunkt <a href="http://localhost:8080/AccountService">http://localhost:8080/AccountService</a> veröffentlicht. In Zeile 13 wird der Webservice wieder beendet. Die Systemeigenschaft, die in Zeile 8 aktiviert wird, sorgt lediglich dafür, dass alle ausgetauschten SOAP-Nachrichten auf die Konsole ausgegeben werden.</li>
<li><code>AccountServiceImpl</code>: Die Klasse implementiert den SOAP-Webservice. Im Gegensatz zu RMI ist ein zugehöriges Interface, das dem Client zur Verfügung gestellt werden muss, nicht unbedingt erforderlich. Jede Klasse, die als Webservice instantiiert werden soll, benötigt die Annotation <code>Webservice</code> (Zeile 8).
<li><code>AccountServiceImpl</code>: Die Klasse implementiert den SOAP-Webservice. Im Gegensatz zu RMI ist ein zugehöriges Interface, das dem Client zur Verfügung gestellt werden muss, nicht unbedingt erforderlich. Jede Klasse, die als Webservice instantiiert werden soll, benötigt die Annotation <code>Webservice</code> (Zeile 8).</li>
<ul>
<li>Die optionale Annotation <code>@SOAPBinding(style = SOAPBinding.Style.RPC)</code> sorgt u.a. dafür, dass im generierten WSDL-Dokument Basisdatentypen wie Integer und String auf die entsprechenden Basisdatentypen von XML-Schema abgebildet werden, also z.B. <code>xsd:int</code> und <code>xsd:string</code>.</li>
<li>Jede öffentliche Methode (Sichtbarkeit <i>public</i>) wird in die Schnittstelle zum Client, also in das WSDL-Dokument, aufgenommen – außer sie ist mit <code>@WebMethod(exclude = true)</code> annotiert. Private Methoden werden nicht in das WSDL-Dokument aufgenommen. Aus eben diesen Gründen sind die Methoden <code>deleteAccount</code> (Zeilen 38-41) und <code>getAccountById</code> (Zeilen 43-46) nicht im obigen WSDL-Dokument wiederzufinden, während alle anderen Methoden der Klasse <code>AccountServiceImpl</code> im WSDL-Dokument enthalten sind.</li>
<li>Mittels der Annotationen <code>@WebMethod(operationName = "...")</code> und <code>@WebParam(name = "...")</code> können Methoden bzw. ihre Argumente gezielt nach außen gerichtet bezeichnet werden. Im obigen WSDL-Dokument wird z.B. die Methode <code>getEntries</code> exemplarisch in <code>getAccountEntries</code> umbenannt (Zeilen 27-28). Die Methodenargumente explizit zu bezeichnen, ist bei JAX-WS RI sinnvoll, da diese im WSDL-Dokument andernfalls schlicht <code>arg0</code>, <code>arg1</code>, ... heißen.</li>
</li>
<li><code>Account</code>, <code>AccountEntry</code> und <code>AccountNotFoundException</code>: Die serverseitigen DTO-Klassen haben sich im Vergleich zum RMI-Anwendungsbeispiel nicht grundlegend geändert. Das Interface <code>java.io.Serializable</code> muss nicht mehr implementiert werden, da die Serialisierung und Deserialisierung in das XML-Format nun über die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB</a> erfolgt.</li>
</ul>
</ul>
......
<p>Eine sehr grundlegende Methode zur bidirektionalen Interprozesskommunikation zwischen verteilten Software-Komponenten ist die Verwendung von <i>Internet-Sockets</i>. Ein Internet-Socket ist ein interner Endpunkt aus Sicht einer Anwendung zum Senden und Empfangen von Daten in einem Netzwerk auf Basis des Internet-Protokolls (IP). Der Begriff <i>Socket</i> selbst geht urspünglich auf den <a href="https://tools.ietf.org/html/rfc147">RFC 147</a> von 1971 zurück, der sich noch auf das ARPA-Netzwerk bezieht. Ein Socket wird einer Anwendung vom Betriebssystem auf Anforderung unter einem bestimmten Port bereitgestellt. Das Betriebssystem verwaltet die aktuell aktiven, lokalen Sockets und die zugehörigen Remote-Sockets (IP-Adressen und Ports). Internet-Sockets lassen sich in <i>Stream Sockets</i> (TCP) und <i>Datagram Sockets</i> (UDP) unterscheiden. Im folgenden soll die <a href="https://docs.oracle.com/javase/tutorial/networking/sockets/readingWriting.html">Java-API für Stream Sockets</a> vorgestellt werden, die im Package <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/package-summary.html"><code>java.net</code></a> implementiert und damit Bestandteil des Moduls <code>java.base</code> ist.</p>
<p>Eine sehr grundlegende Methode zur bidirektionalen Interprozesskommunikation zwischen verteilten Software-Komponenten ist die Verwendung von <i>Internet-Sockets</i>. Ein Internet-Socket ist ein interner Endpunkt aus Sicht einer Anwendung zum Senden und Empfangen von Daten in einem Netzwerk auf Basis des Internet-Protokolls (IP). Der Begriff <i>Socket</i> selbst geht urspünglich auf den <a href="https://tools.ietf.org/html/rfc147">RFC 147</a> von 1971 zurück, der sich noch auf das ARPA-Netzwerk bezieht. Ein Socket wird einer Anwendung vom Betriebssystem auf Anforderung unter einem bestimmten Port bereitgestellt. Das Betriebssystem verwaltet die aktuell aktiven, lokalen Sockets und die zugehörigen Remote-Sockets (IP-Adressen und Ports). Internet-Sockets lassen sich in <i>Stream Sockets</i> (TCP) und <i>Datagram Sockets</i> (UDP) unterscheiden. Im Folgenden soll die <a href="https://docs.oracle.com/javase/tutorial/networking/sockets/readingWriting.html">Java-API für Stream Sockets</a> vorgestellt werden, die im Package <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/package-summary.html"><code>java.net</code></a> implementiert und damit Bestandteil des Moduls <code>java.base</code> ist.</p>
<p>Das Code-Beispiel zeigt eine einfache Chat-Anwendung ähnlich des Beispiels im Kapitel <a href="#unit-websocket" class="navigate">WebSockets</a>.</p>
......
......@@ -21,7 +21,7 @@
<li><a href="https://spring.io/guides/gs/accessing-data-rest/">Einstieg in Spring Data REST</a></li>
</ul>
<p>Die zu entwickelnde REST-API soll Objekte zu den folgenden Entitätsklassen bereitstellen: <code>User</code>, <code>Product</code> und <code>Booking</code>. Der Zusammenhang der Klassen ist in der nächsten Abbildung veranschaulicht. Die API bildet die Datenhaltung für eine minimale Shop-Anwendung ab, in der authentifizierte Anwender (<code>User</code>) Produkte (<code>Product</code>) aus einem Produktkatalog auswählen und für diese beliebig viele Buchungen (<code>Booking</code>) anlegen und wieder entfernen können. Die hier entwickelte API soll in den folgenden Kapiteln zu den UI-Frameworks als gemeinsame Basis genutzt werden, d.h. die später vorgestellten Clients in <a href="#unit-javafx" class="navigate">JavaFX</a> und <a href="#unit-web" class="navigate">JQuery/Angular</a> greifen jeweils auf diese API zu. c
<p>Die zu entwickelnde REST-API soll Objekte zu den folgenden Entitätsklassen bereitstellen: <code>User</code>, <code>Product</code> und <code>Booking</code>. Der Zusammenhang der Klassen ist in der nächsten Abbildung veranschaulicht. Die API bildet die Datenhaltung für eine minimale Shop-Anwendung ab, in der authentifizierte Anwender (<code>User</code>) Produkte (<code>Product</code>) aus einem Produktkatalog auswählen und für diese beliebig viele Buchungen (<code>Booking</code>) anlegen und wieder entfernen können. Die hier entwickelte API soll in den folgenden Kapiteln zu den UI-Frameworks als gemeinsame Basis genutzt werden, d.h. die später vorgestellten Clients in <a href="#unit-javafx" class="navigate">JavaFX</a> und <a href="#unit-web" class="navigate">JQuery/Angular</a> greifen jeweils auf diese API zu.</p>
<img src="media/distributed_spring_entities.png" style="width:540px">
<label>Datenmodell der entwickelten REST-API mit Spring Data</label>
......@@ -176,7 +176,7 @@ Content-type: application/hal+json; charset=utf-8
} --></code></pre></div>
</div>
<p>Dass die HTTP-Response nicht nur die Daten selbst sondern auch weiterführende Links zur Navigation innerhalb der API enthält, entspricht dem sogenannten <a href="http://restcookbook.com/Basics/hateoas/">HATEOAS</a>-Prinzip: <i>Hypermedia As The Engine Of Application State</i>. Da der Client bei einer REST-API (im Gegensatz zur WSDL bei SOAP-Webservices) keine formale Schnittstellenbeschreibung erhält, sind die Entwickler, die eine REST-API aufrufen möchten, auf eine gute Dokumentation angewiesen, in der die mögliche Parametrisierung der API-Endpunkte, die über die Konventionen hinausgeht, beschrieben wird. Die grundlegende Idee von HATEOAS ist es, diese Dokumentation in die API selbst einzubetten. Daher werden neben den angefragten Daten auch Links zurückgegeben, die potentielle Transitionen ausgehend von der aktuellen Anfrage zur nächsten Anfrage angeben. Die REST-API wird auf diese Weise als Zustandsautomat betrachtet, durch den mittels Hypermedia (= Repräsentation einer Ressource inkl. Links) navigiert werden kann. Die nächste Abbildung zeigt einen kleinen Ausschnitt einer REST-API als Zustandsautomat.</p>
<p>Dass die HTTP-Response nicht nur die Daten selbst, sondern auch weiterführende Links zur Navigation innerhalb der API enthält, entspricht dem sogenannten <a href="http://restcookbook.com/Basics/hateoas/">