Spring Context and Dependency Injection

Überblick Spring Context and Dependency Injection

Mit Spring Context and Dependency Injection wurde 2003 ein Framework eingeführt, der JEE damals in vielen Bereichen überlegen war. Die grundsätzlichen Ideen wurden zwar größtenteils in den aktuellen Versionen Spezifikation nachgezogen, trotzdem bleibt Spring bis heute umfangreicher. Dieser Vorteil wird zwar durch eine Bindung an ein proprietäres Framework bezahlt, aber viele Architekten sind bereit, diese Konsequenz zu akzeptieren.

Spring Context and Dependency Injection unterstützt drei unterschiedliche Technologien:

  • XML
  • Annotations
  • Java Config

Diese können jederzeit untereinander beliebig gemischt werden.

XML

Die Definition der CDI-Objekte mit XML war der ursprüngliche Ansatz, der von der Spring Community gewählt wurde. Hierzu werden die Spring Beans in einer XML-Datei abgelegt:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<bean class="org.javacream.books.warehouse.impl.MapBooksService"
		id="mapBooksService" scope="singleton">
		<property name="isbnGenerator" ref="randomIsbnGenerator" />
		<property name="storeService" ref="simpleStoreService" />
	</bean>
	<bean class="org.javacream.books.isbngenerator.impl.RandomIsbnGenerator"
		id="randomIsbnGenerator" scope="singleton" init-method="initGenerator"
		destroy-method="destroyGenerator">
		<property name="prefix" value="${isbngenerator.prefix}" />
		<property name="countryCode" value="${isbngenerator.countryCode}" />
	</bean>
	<bean class="org.javacream.store.impl.SimpleStoreService" scope="singleton"
		id="simpleStoreService" p:stock="${storeService.stock}" />
	<context:property-placeholder location="bookswarehouse.properties" />
</beans>

Mit XML können Anwendungen sehr elegant und kompakt definiert werden. Allerdings wird zur Unterstützung eine komplexe IDE wie IntelliJ oder die Spring Tools Suite benötigt. Sonst werden elementare Operationen der Software-Entwicklung wie Refactoring oder Code-Assistenten nicht unterstützt.

Annotations

Mit der Einführung der Annotationen mit Java 5 wurde die Definition der Spring Beans auch direkt aus dem Java Code heraus möglich. Dazu wurde ein Satz von Spring-spezifischen Annotations-Typen eingeführt:

  • @Component
  • @Service
  • @Repository

Diese Typen sind im stereotypes-Paket definiert und ermöglichen deshalb eine sinnvolle Gruppierung und Dokumentation der Anwendungs-Klassen. Prinzipiell würde die@Component-Annotation genügen.

Die Annotations-basierte Definition ist bei den Anwendungs-Programmieren sehr beliebt und wird in vielen Projekten bevorzugt benutzt.

Java Config

Dies ist der bisher neueste Ansatz, Spring Beans zu definieren. Hierzu wird eine mit @Configurationannotierte Klasse eingeführt, welche mit@Beanannotierte Factory-Methoden enthält. Der Spring-Kontext instanziert die diese Klasse und ruft diese Method je nach Bedarf auf. Wichtig ist hierbei, dass die @Configuration-Klasse sowie die Factory-Methoden selber ebenfalls wieder Dependencies aufweisen dürfen. Ein einfaches Beispiel ist hier noch gegeben:

package org.javacream;

import org.javacream.books.isbngenerator.api.IsbnGenerator;
import org.javacream.books.isbngenerator.impl.RandomIsbnGenerator;
import org.javacream.books.warehouse.api.BooksService;
import org.javacream.books.warehouse.impl.MapBooksService;
import org.javacream.store.api.StoreService;
import org.javacream.store.impl.SimpleStoreService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfiguration {

	@Bean public BooksService booksService() {
		MapBooksService booksService = new MapBooksService();
		booksService.setIsbnGenerator(isbnGenerator());
		booksService.setStoreService(storeService());
		return booksService;
	}
	@Bean public IsbnGenerator isbnGenerator() {
		RandomIsbnGenerator isbnGenerator = new RandomIsbnGenerator();
		isbnGenerator.setPrefix("JAVA-ISBN");
		isbnGenerator.setCountryCode("-is");
		return isbnGenerator;
	}
	@Bean public StoreService storeService() {
		SimpleStoreService storeService = new SimpleStoreService();
		storeService.setStock(42);
		return storeService;
}
}

Java Config scheint zwar ein Rückschritt zu sein, da nun ja wieder der Entwickler selber den Lebenszyklus der Fachobjekte übernimmt. Allerdings stehen auch hier die weiteren Spring-Features zur Verfügung, so dass dieser Ansatz eine durchaus sinnvolle Ergänzung ist.

Zusammenspiel mit Spring Boot

Spring Context and Dependeny Injection wird selbstverständlich komplett von Spring Boot unterstützt. Ohne weitere Konfiguration werden automatisch alle Pakete unterhalb und inklusive des Application-Hauptpakets nach Klassen mit Spring-Annotationen durchforstet und analysiert. Für das Einbinden einer XML-Konfiguration kann @ImportResource benutzt werden.

 


Seminar zum Thema

Weiterlesen

Konfiguration von Spring-Anwendungen

Konfiguration von Spring-Anwendungen

Die Konfiguration von Spring-Anwendungen ist ein elementarer Bestandteil des Frameworks. Damit können auch komplexeste Anforderungen in produktiven Anwendungen umgesetzt werden.

Berücksichtigt werden dabei die folgenden Features:

  • Die eigentliche Konfiguration der Anwendung
  • Die Konfiguration der Laufzeitumgebung sowie deren Überwachung
  • Die Unterstützung verschiedener Profile der Anwendung.

Konfiguration der Anwendung

Bei Spring Boot-Anwendungen wird hierzu automatisch eine Konfigurationsdatei geladen. Dabei werden simple Properties-Dateien (die application.properties)   oder YAML-Dateien (application.yml) unterstützt.

isbngenerator.prefix=ISBN:
isbngenerator.countryCode=-dk

Innerhalb der Anwendung werden Properties mit der @Value-Annotation definiert. Diese lesen über eine Spring Expression einen Wert aus der Konfigurationsdatei:

public class RandomIsbnGenerator implements IsbnGenerator {

	@Value("${isbngenerator.prefix}")
	private String prefix;
	@Value("${isbngenerator.countryCode}")
        private String countryCode;

Profiles

Mit Profiles werden unterschiedliche Stages der Anwendung konfiguriert. Dabei kann jegliche Konfiguration (XML, Annotations oder Java Config) zusätzlich mit der @Profile-Annotation einem benannten Profil zugeordnet werden.

Profile werden innerhalb der Konfiguration oder durch einen Aufruf-Parameter der Anwendung aktiviert.

Die oben bereits angesprochenen Konfigurations-Dateien werden automatisch mit Profiles ergänzt. Existiert beispielsweise ein Profile namens “test”, so wirde neben der application.properties-Datei auch eineapplication-test.propertiesgelesen.

Konfiguration der Laufzeitumgebung

Auch die Laufzeitumgebung der Anwendung kann konsequent in der application-Konfiguration erfolgen. Beispiele hierfür sind

  • Logging
  • Datenbank-URLs
  • Server-Ports
  • Aktive Profile

Im Folgenden Beispiel ist eine Konfiguration für Logging gegeben. Weiterhin wird das aktive Profile gesetzt:

logging.level.root=INFO
logging.pattern.console= "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
spring.profiles.active=test

 

Setup für Test-Datenbanken

Wird eine Spring-Boot-Anwendung mit einer Datenbank-Anbindung konfiguriert, so können automatisch Skripte zur Schema-Erstellung sowie zum initialen Befüllen der Tabellen ausgeführt werden. Diese Dateien sind die schema.sql bzw. die data.sql.

Beispiel: Eine schema.sql

create table AUDIT (auditmessage varchar(2048))
create table ISBNCOUNTER (counter integer)

Beispiel: Eine data.sql

insert into ISBNCOUNTER values(1)

Auch hier ist wiederum eine Unterscheidung verschiedener Stages möglich. Dies wird jedoch nicht über ein Profil geregelt sonder durch den Eintrag einer Platform in der Anwendungs-Konfiguration. Die folgende Konfiguration zeigt eine komplette Datenbank-Konfiguration für eine Test-Datenbank:

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=sa
spring.datasource.platform=test
spring.jpa.hibernate.ddl-auto=create

 

Gelesen werden dann die beiden Dateienschema-test.sqlund data-test.sql.

Der Spring Konfigurationsserver

In einer Cloud-basierten Lösung können die Konfigurationsdateien von einem zentralen Server, dem Spring Cloud Config Server, geliefert werden. Die Standard-Implementierung des Spring-Cloud-Projekts liest die Konfigurationswerte einfach aus einem Git-Repository. Werden Änderungen in die Versionsverwaltung gepushed so wird damit die Anwendung neu konfiguriert. Eine wirklich interessante Lösung! Selbstverständlich können die Konfigurationen aber auch klassisch in einer Datenbank oder dem Dateisystem abgelegt werden.


Seminar zum Thema

Weiterlesen

Spring Data

Spring Data

Spring Data ist ein zentrales Projekt des Spring Frameworks und besticht durch seine Einfachheit und Konsistenz. So können selbst komplexe Datenabfragen mit wenigen Zeilen an Quellcode erstellt werden. Insbesondere im Zusammenspiel mit Spring Boot ist es so möglich, mit wenigen Programmzeilen eine komplette Anwendung wie beispielsweise einen Restful WebService zu realisieren.

Eine Übersicht

Der Begriff “Datenabfragen” ist im Übrigen nicht zufällig so allgemein gewählt: Spring Data selbst definiert nur eine relativ schlanke Abstraktionsschicht auf einen Datenbestand. So besitzen alle Datenobjekte eine eindeutige ID und werden über ein Repository mit  CRUD-Operationen verwaltet.

Alle weiteren Details sind abhängig von der konkret verwendeten Datenbank-Technologie. So unterscheidet sich selbstverständlich ein konkretes JPA-Repository mit einer transaktionsfähigen relationalen Datenbank im Hintergrund von einem Mongo-Repository, bei dem eine Dokumenten-orientierte Datenbank aus dem NoSQL-Umfeld angesprochen wird.

Spring Data Projekte

Die Spring-Entwickler haben damit nicht den Fehler gemacht zu versuchen, jegliche Datenzugriffs-Technologien allgemein zu abstrahieren. Dieser Versuch ist mit der allgemeinen JEE-Connector-Spezifikation bereits misslungen. Statt dessen gruppiert Spring Data verschiedene Implementierungen mit durchaus unterschiedlichen APIs unter einem gemeinsamen Namen.

Die Anzahl der Projekte ist selbst in der offiziellen Spring-Distribution bereits sehr umfassend und wird durch die Community permanent weiter ergänzt.

Gemeinsam ist all diesen Teilprojekten jedoch die grundsätzliche Arbeitsweise: Entitäten werden über ein dynamisch generiertes Repository im Storage abgelegt.

Ein einfaches Beispiel mit Spring Data JPA

Diese Arbeitsweise wird im folgenden Beispiel näher erläutert:

Wir beginnen hierzu mit einer einfachen Daten-Entität:

@Entity
@Table(name="PEOPLE")
public class Person {
	
	private String lastname;
	
	private String firstname;
	private int height;
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
        private Long id;

...
}

Zu beachten ist hierbei bereits, dass diese Entität die normalen Annotationen des Java Persistence APIs benutzt. Insbesondere kann @javax.persistence.Id benutzt werden, die Verwendung von @org.springframework.data.annotation.Id ist optional.

Das Repository selber ist für eine einfache Anwendung komplett trivial:

public interface PeopleRepository extends CrudRepository<Person, Long>{

}

Die Methoden des CrudRepository werden von Spring zur Laufzeit automatisch generiert, so dass das Repository ohne jegliche eigene Programmierung injiziert und sofort benutzt werden kann. Es ist nicht notwendig, das umfangreiche API zu implementieren! Zur Laufzeit generiert Spring hierfür einen generischen Proxy, der die Aufrufe in geeigneter Form an den gekapselten JPA-EntityManager delegiert.

Dies ist übrigens keine Prototyp- oder Dummy-Implementierung! Es ist weder notwendig noch gebräuchlich noch sinnvoll, für den  produktiven Einsatz diese Methoden durch eigene Sequenzen zu ersetzen und damit zu “verbessern”.

Genügen die im Basis-Interface definierten Methoden nicht, können diese durch eigene ergänzt werden. Hierbei werden folgende Möglichkeiten bereitgestellt:

  • Namenskonventionen: Eine Methode mit der Signatur findByLastnameOrderedByHeight(String lastname) kann einfach im Interface deklariert werden. Eine Implementierung ist nicht notwendig, da das im Endeffekt zu generierende JQL-Statement wohl offensichtlich ist.
  • Sollen diese Namenskonvention nicht verwendet werden, kann statt dessen eine Methode beliebiger Signatur mit der @Query-Annotation versehen werden.
    @Query("SELECT p FROM Person p WHERE p.lastname = :lastname order by p.height")
    public Collection<Person> getByLastname(@Param("lastname") String lastname);

    Statt der Annotationen können auch Named Queries der orm.xml benutzt werden.

  • Und schließlich kann das Interface in einer abstrakten Klasse selbst programiert werden. Dabei werden aber selbstverständlich nur die besonders zu behandelnden Fälle implementiert.

Seminar zum Thema

Weiterlesen

Spring Boot

Was ist Spring Boot?

Mit Spring Boot hat die Spring Community eine echte Vereinfachung bei der Erstellung Spring-basierter Anwendung erreicht. Damit können mit wenigen Programmzeilen Prototypen selbst komplexer Anwendungen erstellt werden, die anschließend zum fertigen Produkt konfiguriert werden können. Der Funktionsumfang von Spring Boot umfasst:

  • Build-Management
    • Ein Spring Boot-Projekt kann mit einer simplen Build-Definition gebaut werden.
  • Projekt-Generator
    • Zur Neuanlage eines Projektes kann die Build-Konfiguration mit einem Online-Dienst der Spring-Community erstellt werden.
  • Convention over Configuration und Autoconfiguration
    • Für Rapid Prototyping sind keine Konfigurationsdateien oder Ähnliches vonnöten. Die Anwendung ist nach der Erstellung sofort lauffähig. Dies gilt auch dann, wenn beispielsweise für eine Web-Anwendung ein Server benötigt wird: Die Autoconfiguration startet einfach einen Tomcat auf Port 8080.
  • Externe Konfiguration
    • Für die ausgelieferte Programmversion muss selbstverständlich eine explizite Konfiguration erfolgen, die Autoconfiguration ist hierfür bestenfalls bedingt geeignet. Dazu bietet Spring Boot einen einfachen Mechanismus über Properties- oder YAML-Dateien. Eine Annotation-basierte Konfiguration ist natürlich auch möglich.
  • Monitoring der Runtime
    • Die in Produktion laufende Anwendung wird prnzipiell wie alle Java-Anwendungen über JMX überwacht. Mit dem Spring Actuator werden zusätzliche Metriken hinzugefügt, die via REST-Schnittstelle abgegriffen werden können.

Die einzelnen Funktionen werden im Folgenden detaillierter beschrieben.

Features

 Build Management

Der Build-Prozess einer Spring Boot-Anwendung wird über Gradle oder über Maven definiert. Die Grundideen sind in beiden Systemen die selben und werden hier an einem Maven-Projekt beschrieben:

Als erstes stellt Spring Boot eine Parent-POM zur Verfügung, in der alle (!) möglichen Spring-Dependencies einer Applikation abstrakt deklariert sind:

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
		<relativePath />
	</parent>

Ein konkretes POM eines Projekts definiert als Dependencies die Teile des Frameworks, die benutzt werden sollen durch “Starter”. So wird im folgenden Beispiel ein Projekt mit der Spring Core-Komponente sowie der Test-Umgebung definiert:

 

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

Projekt Generator Spring Initializr

Mit Hilfe des online verfügbaren Projekt-Generators Spring Initializr wird ein POM für ein Projekt sowie eine simple Rahmenanwendung generiert. Die notwendigen Starter können durch eine einfache Oberfläche hinzugefügt werden. Das fertige Projekt wird als Archiv geladen werden.

Convention over Configuration und Autoconfiguration

Eine Spring Boot-Anwendung wird über eine zentrale Einstiegsklasse definiert, die mit @SpringBootApplication annotiert ist.  Damit sind die folgenden Features automatisch aktiviert:

  • Diese Klasse ist automatisch eine @Configuration
  • ComponentScan für alle Unterpakete, so dass automatisch alle @Components gefunden werden
  • Automatisches Laden der application.properties oder alternativ einer application.yaml zur Anwendungs-Konfiguration. Hierin werden Anwendungs-spezifische Konfigurationen definiert, aber auch die restliche Konfiguration (Logging, Ports, URLs…) werden hier zentral gepflegt.
  • Spring-Profile werden unterstützt. So werden Profile-abhängige Konfigurationen wie application-<profile>.properties automatisch erkannt und gemerged.

 

Zusätzlich wird eine Autokonfiguration angeboten, die einiges an Hintergrund-“Magie” durchführt. Dies sei an einem konkreten Beispiel demonstriert:

  • Die Anwendung benötigt eine Datenbank und deklariert somit in seinem POM den Starter für JDBC.
  • Zusätzlich wird ein Datenbanktreiber angegeben.
  • Jetzt startet die Autokonfiguration und erzeugt automatisch eine DataSource. Dazu wird untersucht, ob der angegebene  Datenbank-Treiber eine Embedded-Konfiguration ermöglicht. Hier sind aktuell H2, Derby und die HSQLDB unterstützt.

Diese Autokonfiguration kann aber beispielsweise auch einen kompletten Jetty-Server konfigurieren und starten. Dazu ist nur der Web-Starter zu inkludieren.

Externe Konfiguration

Die eben besprochene Autoconfiguration ist für Prototyping sehr praktisch. Für reale Anwendungen muss diese jedoch durch explizite Konfiguration ersetzt werden, also beispielsweise den Port des Jetty-Servers oder eine DataSource. Dazu bietet Spring Boot

  • die application.properties
    • spring.datasource.user=Hugo
  • Aufruf-Parameter der Anwendung
    • -Dspring.datasource.user=Hugo
  • Falls zumindest eine DataSource definiert ist wird die Autokonfiguration nicht durchgeführt.

Monitoring

Der Spring-Actuator stellt umfangreiche Informationen via JMX oder über eine REST-Schnittstelle zur Verfügung. Darin werden nicht nur die aktuellen Metriken des Java-Prozesses angezeigt sondern auch beispielsweise das Geflecht der Spring-Beans.

Das Hinzufügen des Actuators erfolgt wie bei Spring Boot üblich durch das Hinzufügen des Starters zum POM. Welche Endpoints aktiviert werden wird in der application.properties definiert.

Ebenso einfach können die Metriken über Jolokia abgegriffen werden.

Fazit

Spring Boot wird zwar als Ergänzung zum Spring-Framework angeboten, ist nach meiner Auffassung aber so überzeugend, dass selbst simple Projekte damit realisiert werden sollten. Selbst wenn “nur” der Build-Prozess mit Parent Startern benutzt wird, ist der Mehrwert den Aufwand der Einarbeitung in jedem Falle wert. Dies beschreibt ein anderer Artikel zu Spring Boot, darin wird ein RESTful Web Service mit Datenbank-Anbindung programmiert. Die Datenbank-Anbindung mit Spring Data ist Thema eines weiteren Artikels.


Seminar zum Thema

Weiterlesen

Jolokia – Simples Management von Java-Anwendungen

Jolokia – Simples Management von Java-Anwendungen

Mit Hilfe von Jolokia ist das moderne Management von Java-Anwendungen im Vergleich zu einer reinen JMX-Lösung deutlich simpler geworden. Weiterhin ist Jolokia sehr populär und wird von praktisch allen Java-basierten Produkten wie Applikationsservern und Spring Boot-Anwendungen unterstützt.

Eine Übersicht zu JMX

Die Java Management Extension ist die Grundlage jeglicher Überwachung eines Java-Prozesses. Die über das Betriebssystem erfassbaren Metriken (CPU, Speicher, I/O) sind für Java-Anwendungen viel zu grob bzw. messen durch die interne Verwaltung des Hauptspeichers durch die Java Virtual Machine schlicht und ergreifend falsch. Weiterhin stehen relevante Informationen wie die Garbage Collection so nicht zur Verfügung.

Diese Lücke schließt JMX. Dieses ursprünglich als Extension bereitgestellte Framework ist seit langem Bestandteil jeder Java-Installation. Die Grundidee ist, über einen JMX-Agenten quasi beliebige Informationen aus dem Java-Prozess lesen zu können. Dazu wird eine so genannte “Managed Bean” mit Attributen und Operationen definiert. Auf diese wird durch einen eindeutigen Namen zugegriffen. Das Format dieses Namens, der so genannte ObjectName, ermöglicht eine  hierarchische Struktur und damit auch ein Suchen und Filtern.

Obwohl diese Technik sehr praktisch und auch sehr einfach zu erweitern ist, bleiben im konkreten Einsatz ein paar Fragen offen:

  • Authentifizierung und Verschlüsselung sind in JMX realisiert, aber es fehlt ein Rollen-Konzept. So kann die Sicht auf die Hierarchie der Managed Beans oder der Zugriff auf Bean-Attribute und Operationen nicht eingeschränkt werden.
  • Das Netzwerk-Protokoll ist nicht REST-basiert. Damit ist es relativ kompliziert, Überwachungs- und Orchestrierungssoftware an das JMX-System zu koppeln.

Die Kernfunktionen von Jolokia

Jolokia realisiert diese Anforderungen. Dazu definiert das Framework ein REST-API zum lesenden und schreibenden Zugriff auf das interne JMX-System. Durch die Verwendung des standardisierten http-Protokolls werden die etablierten Security-Mechanismen zur Authentifizierung und Verschlüsselung benutzt. Zusätzlich können in einem Access-Restriktor Rollen-basierte Zugriffsbeschränkungen und Filter definiert werden.

Die Daten der Abfragen werden im JSON-Format geliefert, so dass eine Analyse und Weiterverarbeitung ohne großen Aufwand erfolgen kann. Damit ist die Anbindung an andere System (Monitoring, Orchestrierung) gegeben.

Integration von Jolokia

Für JEE-Applikationsserver stellt Jolokia ein Web-Archiv zur Verfügung. Dieses wird als zusätzliche Anwendung installiert und stellt den Jolokia-Endpunkt zur Verfügung. Authentifizierung und Verschlüsselung übernimmt hierbei der Applikationsserver.

Für Standalone-Anwendungen kann Jolokia über einen Java-Agent eingebunden werden. Dieser startet einen http-Server der Java Runtime. Netzwerk- und Port-Konfiguration erfolgen durch Aufruf-Parameter:

java   -javaagent:jolokia-agent.jar=port=${JAVA_JOLOKIA_PORT},host=0.0.0.0

Auch für Java-Anwendungen im Docker-Container ist Jolokia hervorragend geeignet. Hier muss nur der vom Server bereitgestellte Endpunkt durch ein Port-Mapping freigeschaltet werden.


Seminar zum Thema

Weiterlesen