Spring 4 als WebSocket Client

Im vorherigen Blog über Spring 4 und WebSocket wurde ein Beispiel gezeigt mit einem JavaScript Client und Spring 4 als Server.

Mit dem Spring 4 WebSocket Support lässt sich aber nicht nur der serverseitige Teil einer WebSocket Verbindung programmieren, sondern kann auch die Client Seite übernehmen. Damit lassen sich Daten zwischen zwei entfernten Spring Applikationen über das Internet oder über ein Firmen LAN/WAN austauschen. Oder eine Spring Applikation muss Daten mit einer WebSocket Server Anwendung austauschen die in einer anderen Programmiersprache oder einem anderen Framework geschrieben wurde.

Im folgenden Beispiel sendet der Server jede Sekunde neue Börsenkurse für 8 ausgewählte Aktien. Die Applikation sendet die Daten nicht als Textmessages sondern in einem binären Format das mit Hilfe der Library MessagePack erstellt wird. MessagePack ist ein Format das ähnlich wie JSON funktioniert die Daten dabei aber platzsparender in einem binären Format ablegt. Implementationen von MessagePack gibt es für viele verschiedene Programmiersprachen.

Der Server Teil dieser Beispielapplikation besteht aus folgenden Klassen:

  • Quote.java : Dies ist das Data Transfer Objekt, welches das Kürzel und den Kurs der Aktie enthält
  • QuoteHandler.java : Ist eine Implementation des BinaryWebSocketHandler Interfaces. Diese Klasse registriert die verbundenen Clients in einer ConcurrentHashMap und stellt eine Methode sendToAll zur Verfügung welche die Daten an alle Clients sendet.
  • QuoteService.java : Diese Klasse erstellt jede Sekunde zufallsgesteuert neue Kurse zusammen und sendet sie mit Hilfe des QuoteHandler an alle Clients.

Um eine clientseitige Spring WebSocket Applikation zu schreiben wird, wie serverseitig, eine Implementation des WebSocketHandler Interfaces benötigt. Diese muss mit der Serverseite übereinstimmen. Wenn der Server Text sendet wird ein TextWebSocketHandler und wenn er binäre Daten sendet ein BinaryWebSocketHandler benötigt.

Der Handler deserialisiert die erhaltenen binären Daten mit Hilfe von MessagePack in das Data Transfer Objekt Quote und gibt sie aus.

Um von einem Client aus eine WebSocket Verbindung zu öffnen stellt Spring die Klasse WebSocketConnectionManager zur Verfügung. Dieser Manager verbindet zu einem WebSocket Server wenn die start() Methode aufgerufen wird. stop() beendet die Verbindung wieder. Anstatt die Verbindung manuell zu starten und zu stoppen kann man setAutoStartup(true) aufrufen und die Verbindung wird automatisch aufgebaut wenn der Spring ApplicationContext refreshed wird.

Damit der WebSocketConnectionManager seine Aufgabe ausführen kann benötigt er eine Implementation des WebSocketClient Interfaces. Das Springframework enthält zwei Implementationen zum einen JettyWebSocketClient, die auf die Jetty eigene WebSocket Implementierung aufbaut, und zum anderen StandardWebSocketClient welche eine JSR 356 Library benötigt.

Zusätzlich benötigt der ConnectionManager eine WebSocketHandler Instanz, in unserem Beispiel ist das die oben aufgeführte ClientWebSocketHandler Klasse, sowie die WebSocket URL des Servers.

Die folgende @Configuration Klasse setzt alle besprochenen Teile zusammen und startet mit Hilfe von AnnotationConfigApplicationContext die Applikation. Die Beispielapplikation wird automatisch nach einer Minute beendet.

Wie oben erwähnt existieren zwei Implementationen des WebSocketClient Interfaces. JettyWebSocketClient und StandardWebSocketClient. Beide Klassen benötigen zusätzliche Libraries damit sie funktionieren.

JettyWebSocketClient benutzt die Jetty eigene WebSocket Implementierung. Diese lässt sich mit folgender Dependency in das Programm holen.

StandardWebSocketClient benötigt eine JSR 356 Library. Hier eine Auswahl von JSR 356 Implementationen und deren Dependency.

Jetty

Tomcat 7

Tomcat 8

Glassfish (Tyrus)

Sourcecode der Beispielapplikation findet man auf GitHub:
Client: https://github.com/ralscha/playground/tree/master/springwebsocketclient
Server: https://github.com/ralscha/playground/tree/master/springwebsocket

WebSocket mit Spring 4

Am 12. Dezember 2013 ist die Version 4 des Spring Frameworks veröffentlicht worden. Was geändert und neu hinzugefügt wurde findet man in der What’s New Sektion der offiziellen Dokumentation.

Unter den Neuerungen findet man auch die Unterstützung für WebSockets. WebSocket ist eine Technologie die mit HTML5 Einzug in die Browser gefunden hat. Mit WebSocket ist es möglich einen bidirektionalen Kommunikationskanal zwischen Client und Server zu öffnen. Dieser Kanal ist Full-Duplex das heisst Client und Server können gleichzeitig Daten senden und empfangen. Dabei handelt sich um eine TCP Connection die zwischen den beiden Partnern aufgebaut wird.

WebSocket wird in zwei Spezifikationen beschrieben. Zum einen im RFC 6455 verfasst von der IETF. Dieses RFC beschreibt das Protokoll, also welche Daten wie über das Netz versendet werden.
Die zweite Spezifikation, verfasst vom W3C, beschreibt das WebSocket API, eine Javascript API mit der man WebSockets im Browser ansprechen kann.

Den aktuellen Stand der Browser Unterstützung findet man auf caniuse: http://caniuse.com/#search=websocket
Wie man sieht unterstützen praktische alle aktuellen Browser WebSocket.

WebSockets sind keine HTTP Verbindungen, benötigen aber am Beginn HTTP um die Verbindung zu etablieren. Dazu sendet der Client einen speziellen HTTP Request zum Server. Dieser enthält einen Upgrade Header, welcher den Server anweist das eine WebSocket Verbindung aufgebaut werden möchte. Der Server kann nun entweder den Request ablehnen, wenn er WebSocket nicht unterstützt, oder er kann dem Request Folge leisten und die HTTP Verbindung in eine WebSocket Verbindung upgraden. Ab diesem Zeitpunkt ist HTTP aus dem Spiel und WebSocket übernimmt die Kommunikation.

WebSocket Graphic

WebSocket ist nützlich für Anwendungen die Daten in Real-Time behandeln müssen. Ein vielgenanntes Beispiel ist eine Aktienkursapplikation, bei der die Kurse möglichst schnell zu den Clients gesendet werden müssen. Weitere denkbare Anwendungen sind Chat Applikationen und Mulitplayer Spiele. Da die TCP Verbindung zwischen Client und Server ständig offen ist haben WebSockets eine sehr tiefe Latenzzeit. Zusätzlich wurde bei der Spezifikation darauf geachtet das der Overhead sehr klein ist, im Gegensatz zu einer HTTP Verbindung bei der mehrere hundert Bytes an Header Daten gesendet werden.

In der Java Welt wurde für WebSocket auch ein Standard definiert: JSR-356. Dieser Standard wurde im Rahmen von JEE 7 eingeführt. Im offiziellen JEE 7 Tutorial von Oracle findet man eine Beschreibung wie JSR 356 funktioniert.

Der Spring 4 WebSocket Support ist kompatibel mit JSR 356 und benötigt entweder ein JSR 356 kompatibles Runtime wie Tomcat 7.0.47+ und GlassFish 4.0+ oder ein Adapter für andere native WebSocket Implementation. Ein Adapter existiert für die Jetty 9.0 eigene WebSocket Implementation.

Server

Um einen WebSocket Endpoint zu programmieren muss das WebSocketHandler Interface implementiert werden. Dazu erstellt man am einfachsten eine Subklasse von der Klasse TextWebSocketHandler oder BinaryWebSocketHandler, je nachdem ob Strings oder binäre Daten verarbeitet werden müssen. Alternativ kann man auch AbstractWebSocketHandler, die Superklasse dieser beiden Klassen, als Basis verwenden. Oder man implementiert das WebSocketHandler Interface komplett selber.

Das folgende Beispiel behandelt String Messages und benutzt daher als Superklasse TextWebSocketHandler.

Die Methode afterConnectionEstablished wird aufgerufen wenn die WebSocket Verbindung aufgebaut wurde und bereit ist Daten darüber zu empfangen und zu senden.

afterConnectionClosed wird aufgerufen wenn die Verbindung geschlossen wurde. WebSocket Verbindungen werden entweder vom Client oder vom Server durch Aufruf der close Methode normal oder durch einen Fehler automatisch durch das Framework geschlossen. Serverseitig schliesst man eine WebSocket Verbindung mit der Methode WebSocketSession.close. afterConnectionClosed wird auch dann aufgerufen wenn wir die Verbindung serverseitig schliesen. In der Methode afterConnectionClosed können wir durch Abfragen des CloseStatus herausfinden ob die Verbindung normal oder durch einen Fehler geschlossen wurde.

Die Methode handleTextMessage wird aufgerufen wenn der Client eine Textnachricht sendet.
Mit message.getPayload() wird die Message gelesen, die der Client gesendet hat. In diesem Beispiel wird der String in Grossbuchstaben umgewandelt und mit der Methode session.sendMessage gleich wieder an den Client zurückgesendet.

Eine weitere nützliche Methode, die man überschreiben kann, ist handleTransportError welche aufgerufen wird wenn ein Kommunikationsfehler auftratt.

Im nächsten Schritt wird der Handler konfiguriert. Mit JavaConfig kann dazu die neue @EnableWebSocket Annotation und das Interface WebSocketConfigurer verwendet werden. Es ist aber auch weiterhin möglich diese Konfiguration mit XML vorzunehmen.

Für dieses Beispiel wird der Handler auf den Endpoint /endpoint gemappt. So wie WebSocket in dem Beispiel konfiguriert wurde, wird zusätzlich Spring MVC benötigt. Aber der Spring WebSocket Support ist nicht von Spring MVC abhängig. Mit der Hilfe der WebSocketHttpRequestHandler Klasse kann Spring WebSocket in andere Servlet Frameworks integriert werden.

Als letzten Baustein wird Bootstrap Code benötigt, welcher Spring und Spring MVC startet. Dazu trägt man die entsprechende Konfiguration im web.xml ein oder erstellt eine WebApplicationInitializer Implementation. Dieses Beispiel benutzt die zweite Variante. Das DispatcherServlet von Spring MVC wird auf die URL /dispatcher/ gemappt.

Der Serverteil der Applikation ist damit komplett und kann deployed werden. Der Beispielcode auf GitHub enthält einen eingebetteten Tomcat der mit der Klasse StartTC gestartet werden kann.

Client

Das WebSocket API ist sehr einfach gehalten und besteht aus den Objekt WebSocket, CloseEvent und MessageEvent. Um eine Verbindung herzustellen erstellt man mit new ein neues WebSocket Objekt.

Die URL die man angeben muss beginnt entweder mit ws:// für eine unverschlüsselte oder mit wss:// für eine mit TLS verschlüsselte Verbindung. Danach folgen Serveradresse und Port (kann wie bei HTTP URLs weggelassen werden wenn Port 80 benutzt wird), sowie die Servlet Pfade. Hinter /dispatcher verbirgt sich in diesem Beispiel das DispatcherServlet und hinter /endpoint wartet der WebSocketHandler auf Anfragen.

new startet automatisch den Verbindungsaufbau mit dem Server. Es muss keine weitere Methode aufgerufen werden.

Im WebSocket Objekt können wir nun Event Handler registrieren. Diese werden aufgerufen wenn die Verbindung etabliert wurde (onopen), wenn sie geschlossen wurde (onclose), wenn der Server eine Nachricht gesendet hat (onmessage) und wenn ein Fehler aufgetreten ist (onerror).

Wie beim Server können wir auch clientseitig in der onclose Funktion herausfinden ob die Verbindung normal oder durch einen Fehler geschlossen wurde.

Zusätzlich enthält das WebSocket Objekt das Attribut readyState das den aktuellen Verbindungsstatus anzeigt.

  • 0 = WebSocket.CONNECTING : Die Verbindung wird aufgebaut ist aber noch nicht offen
  • 1 = WebSocket.OPEN : Verbindung offen und bereit um Daten zu empfangen und senden
  • 2 = WebSocket.CLOSING : Die Verbindung wird geschlossen
  • 3 = WebSocket.CLOSED : Verbindung geschlossen oder konnte gar nicht geöffnet werden.

Nachdem die Verbindung geöffnet wurde kann mit der Methode send(…) Daten zum Server gesendet werden.

Wenn der Server eine Message sendet wird onmessage mit einem Parameter vom Typ MessageEvent aufgerufen. Die Daten, die der Server gesendet hat, findet man im Attribut data

Geschlossen wird eine WebSocket Verbindung mit der close Methode. In diesem Beispiel wird vorher geprüft ob die Verbindung offen ist. Nach Aufruf von close() wird die onclose Callback Function aufgerufen sowie das serverseitige Pendant afterConnectionClosed.

Weiteres Beispiel

Das erste Beispiel benutzt einen typischen “HTTP ähnlichen” Aufbau. Der Client sendet Daten zum Server und dieser sendet eine Antwort zurück. Mit WebSockets sind wir natürlich nicht mehr auf solche Request/Response Szenarien beschränkt. Einzige Ausnahme ist der Verbindungsaufbau, welcher vom Client initiiert werden muss.

Das folgende Beispiel sendet nur Daten vom Server zum Client, aber dafür an alle verbundenen Clients. Um dies zu bewerkstelligen müssen wir serverseitig ein Registry implementieren wo die WebSocketSession Objekte abgelegt werden können. Dieses Beispiel benutzt eine ConcurrentHashMap als Ablage. Als Key wird der eindeutigen Session Identifier (WebSocketSession.getId()) benutzt. Benötigt wird diese ID in diesem Beispiel nicht aber man könnte sie zum Beispiel dazu verwenden um Daten nur an ausgewählte Clients zu senden. Die WebSocketSession wird aus der Map entfernt wenn die Verbindung geschlossen oder wenn ein Fehler auftratt.

Zusätzlich wurde hier eine sendToAll Methode implementiert die über die registrierten Sessions iteriert und die TextMessage versendet. Bei einem Fehler oder wenn die WebSocketSession geschlossen ist wird sie aus der Map entfernt. Die Methode ist mit @Async annotiert damit sie in einem eigenen Thread laufen kann und so den Aufrufer der Methode nicht blockiert.

Als Beispieldaten sendet das Programm den aktuell belegten Heap und NonHeap Speicher. Dazu wird folgendes Spring Bean implementiert in der die Methode observe() mit @Scheduled annotiert ist und damit einmal pro Sekunde aufgerufen wird. Die Meldung wird als JSON aufbereitet und mit Hilfe der sendToAll Methode an alle Clients versendet.

Um alle Bestandteile zusammenzufügen wird die @Configuration Klasse angepasst. Async und Scheduling Handling wird mit den entsprechenden @Enable.. Annotation eingeschaltet und ein ThreadPool für die Ausführung der @Async Methoden konfiguriert. Der RegistryHandler wird auf die URL /registry gemappt.

Der Client baut die WebSocket Verbindung auf registriert eine onmessage Callback Function. Da die Meldungen als JSON kodiert sind, kann man sie mit JSON.parse in ein Objekt umwandeln.

Weiterführende Links

Spring Referenzdokumentation über WebSocket

DZone Refcardz WebSocket

WebSocket Dokumentation im Mozilla Developer Network

Sourcecode für die in diesem Artikel beschriebenen Beispiele findet man auf GitHub:
https://github.com/ralscha/playground/tree/master/springwebsocket

Weitere WebSocket Beispiele findet man hier:
https://github.com/ralscha/spring4ws-demos

Erste Schritte mit der GS Collections Library

GS Collections ist eine Java Library die von Goldmann Sachs intern entwickelt und vor zwei Jahren als Open Source veröffentlicht wurde. Der Sourcecode ist auf GitHub unter der Adresse https://github.com/goldmansachs/gs-collections erreichbar.

GS Collections ist ein Java Library, die das Java Collections Framework (JCF) komplett ersetzen kann. GS Collections bietet zusätzliche Collections an die im JCF nicht existieren wie zum Beispiel Multimaps, BiMaps, Bags, Immutable Collections und Collections für primitive Typen. Alles Klassen die man bereits aus anderen Libraries wie Apache Commons Collections, Guava und Trove kennt, allerdings hier in einer Library zusammengefasst.

GS Collections ist im zentralen Maven Repository abgelegt und kann mit folgender Dependency in ein Maven und Gradle Projekt eingebunden werden.

Die meisten Collections in der GS Collections Library implementieren das RichIterable Interface, welches viele Methoden mitbringt, die bekannte Iteration Patterns, wie Filtering und Transforming, implementieren.

In reinem Java muss mit Schleifen wie for und while über Collections iteriert werden. Die Schleife umschliesst dabei die Logik die für jedes Element ausgeführt wird.

Mit GS Collections wird der Code in einer Klasse eingebettet die ein Single Abstract Method Interface implementiert. Dies ermöglicht es den Code besser wiederzuverwenden als wenn mit externen Schleifen über eine Collection iteriert wird.

Die nachfolgenden Beispiele verwenden diese Klasse als Elemente für die Collections.

Das folgende Beispiel erstellt eine FastList die zwei Person Objekte enthält. Dann filtert das Programm mit Hilfe der select Methode die Personen heraus die über 30 Jahre alt sind. select erwartet als Parameter eine Instanz einer Klasse die das Predicate Interfaces implementiert. Für jedes Element in der Collection wird die Methode accept aufgerufen. select verändert die bestehende Liste nicht sondern erzeugt eine neue Liste die alle Elemente enthält bei denen die accept Methode true zurückgibt.

Im zweiten Schritt wird von den Personen der Name extrahiert. Dazu existiert die Transformations Methode collect. collect erwartet als Parameter eine Instanz vom Typ Function.

makeString ist eine Methode die einen String erzeugt der alle Elemente mit dem übergebenen Separator Zeichen getrennt enthält. Wenn das Collection Element kein String ist wird zuerst toString() des Objektes aufgerufen.

Anonyme innere Klassen können den Code unübersichtlich machen. Mit Java 8 können wir den Code mit Lambda Expressions und Methoden Referenzen vereinfachen. Aber bis dies soweit ist empfiehlt es sich die Predicate und Function Implementation als Klassenvariable in der entsprechenden Klasse einzubetten.

Das Programm wird damit übersichtlicher. Dieses Beispiel kann dann in einer Zeile programmiert werden. Zusätzlich steigt die Wiederverwendbarkeit da diese Instanzen überall verwendet werden können.
String names = personList.select(Person.OVER30).collect(Person.TO_NAME ).makeString(", ");

Ein kleines Problem taucht hier auf, die Bedingung > 30 ist fest codiert. Was wenn zum Beispiel auf >40 , >50 usw. gefiltert werden soll. Die Fülle an Predicate Implementation wäre wohl nicht mehr überschau- und wartbar.

GS Collections bietet hierfür zwei Lösungen an. Anstatt select können wir die Methode selectWith verwenden. Diese Methode erwartet eine Instanz einer Klasse die das Predicate2 Interfaces implementiert. Die accept Methode dieses Interfaces erwartet einen zusätzlichen Parameter.

Dieser zusätzliche Parameter wird beim Aufruf mitgegeben personList.selectWith(Person.OVER, 30)

Eine zweite Möglichkeit ist es mit Hilfe der Predicates Klasse ein Predicate “on the fly” zu erzeugen.
personList.select(Predicates.attributeGreaterThan(Person.TO_AGE, 30))

Damit dies funktioniert benötigen wir eine Function die zu jeder Person das Alter zurückgibt.

Mit Hilfe der TO_AGE Function können wir viele weitere Filter und Transformation Operationen durchführen.

Gibt das höchste Alter zurück:
Integer maxAge = personList.collect(Person.TO_AGE).max();

Gibt die Person mit dem höchsten Alter zurück:
Person oldestPerson = personList.maxBy(Person.TO_AGE);

Gibt die erste gefundene Person zurück die über 10 Jahre alt ist:
Person anyPersonOver10 = personList.detect(Predicates.attributeGreaterThan(Person.TO_AGE, 10));

Existiert in der Collection eine Person die über 40 Jahre alt ist:
boolean personsOver40 = personList.anySatisfy(Predicates.attributeGreaterThan(Person.TO_AGE, 40));

Sind alle Personen in der Collection unter 40 Jahre alt:
boolean allPersonsUnder40 = personList.allSatisfy(Predicates.attributeLessThan(Person.TO_AGE, 40));

Anzahl der Personen die über 20 Jahre alt sind:
int numberOfPersonOver20 = personList.count(Predicates.attributeGreaterThan(Person.TO_AGE, 20));

Personen aufsplitten in eine Gruppe die unter 25 und eine Gruppe die über 25 Jahre alt ist. Die Methode partition gibt eine Klasse vom Typ PartitionMutableList zurück. Die Methode getRejected gibt dann die Items zurück bei denen das Predicate false und getSelected die Items bei denen das Predicate true zurückgegeben hat.

Neben den Filter und Transformations Methoden existiert eine forEach Methode die für jedes Element in der Collection eine Methode aufruft. forEach erwartet als Parameter ein Procedure Implementation. In dem Beispiel wird das Alter jeder Person um eins erhöht.

Eine weitere Methode ist injectInto. Damit lässt sich als Beispiel die Summe über das Feld age erzeugen. Dabei wird der Function2 Implementation immer der Wert des Vorgängers übergeben. Für das erste Element benötigt die injectInto Methode daher einen Initialwert.
int sumOfAges = personList.collect(Person.TO_AGE).injectInto(0, AddFunction.INTEGER);
Man kennt diese Methode aus anderen Libraries unter dem Namen fold und reduces.

Manipulationen an der Liste lassen sich auch durchführen. Zum Beispiel alle Elemente entfernen für die das Predicate true zurückgibt:
personList.removeIf(Predicates.attributeEqual(Person.TO_AGE, 23));

FastList, UnifiedSet und UnifiedMap

Die drei wohl am häufigsten verwendeten Klassen aus der GS Collections Library sind FastList, UnifiedSet und UnifiedMap. Diese ersetzen direkt die entsprechenden JCF Klassen ArrayList, HashSet und HashMap. Es handelt sich hierbei um neue Implementationen die auf Speicherverbrauch und Performance optimiert wurden. Die drei Klassen implementieren die JCF Interfaces List, Set bzw. Map. Damit lässt sich bestehender Code sehr einfach modifizieren um die GS Collections Klassen zu verwenden.

Eine FastList lässt sich auf verschiedene Wege erzeugen.

Eine Liste lässt sich auch direkt beim Erstellen mit Werten befüllen
List<String> aList = FastList.newListWith("one", "two")

Eine ImmutableList ist nach dem Erstellen nicht mehr veränderbar.

Diese Methoden geben nicht mehr ein Objekt mit dem Interface List zurück sondern mit dem Interface ImmutableList welches keine Methoden enthält um die Liste zu modifizieren.

Wenn man mit bestehendem Code arbeitet der ein List Interface erwartet und man trotzdem eine Read-Only Version der Collection übergeben möchte, kann mit asUnmodifiable eine Read-Only View auf die darunterliegende Liste erzeugt werden. Die darunterliegende Liste bleibt nach wie vor veränderbar, dies im Gegensatz zu einer ImmutableList.

List<String> aList = FastList.newListWith("one", "two").asUnmodifiable();

Beachten muss man dass das List Interface Methoden wie add und remove enthält, die beim Aufruf einer so erzeugten Liste eine Exception werfen.

Das hier Beschriebene lässt sich auch auf die UnifiedSet und UnifiedMap anwenden. Die Factoryklasse für UnifiedSet heisst Sets ( Sets.mutable.of("one", "two")) und für UnifiedMap Maps ( Maps.mutable.of())

Legacy

Für den Fall das man Geschmack an der GS Collections Library gefunden hat und man die vielen Möglichkeiten nutzen möchte, bestehender Code aber mit der Java Collection Library geschrieben wurde und eine Codeänderung nicht erwünscht oder nicht möglich ist, gibt es die Helper Klassen Iterate, ListIterate, MapIterate, ArrayIterate.

Diese Klassen kennen die gleichen Iteration Methoden wie sie weiter oben erwähnt wurden, erwarten aber als ersten Parameter zusätzlich die JCF Collection oder ein Array.

Eine Alternative zu den Iterate Klassen ist es die JCF Collections in ein Adapter zu verpacken.
Es existieren Adapter für Collection (CollectionAdapter), List (ListAdapter), Set (SetAdapter), Map (MapAdapter) und Arrays (ArrayAdapter).

In diesem Artikel wurde nur ein ganz kleiner Ausschnitt aus der GS Collections Library angesprochen. Wer tiefer in die Library eintauchen will dem empfiehlt es sich das Code Kata durchzuspielen. Dabei handelt es sich um eine PDF Präsentation und ein Java Projekt das eine Serie von Unit Tests enthält die man anpassen muss damit sie erfolgreich durchlaufen.

Die Referenzdokumentation findet man unter dieser Adresse: http://www.goldmansachs.com/gs-collections/documents/GS%20Collections%20Reference%20Guide%201.2.0.pdf

Ein Vergleich des Speicherverbrauchs zwischen den JCF und den GS Collections findet man hier: http://www.goldmansachs.com/gs-collections/presentations/GSC_Memory_Tests.pdf

Ein Interview mit dem Lead Entwickler der GS Collections findet man hier:
http://www.jclarity.com/2013/11/26/stalwarts-in-tech-an-interview-with-donald-raab-gs-collections-lead/