Spring Boot Webanwendung: Die ersten Schritte (Tutorial)
In diesem Beitrag zeige ich ein Beispiel mit einer Spring Boot Webanwendung. Diese Webanwendung ist eine einfache und unvollständige Wertpapierverwaltung mit Datenbankzugriffen (JPA) auf Basis von Spring Boot.
Die Komponenten der Spring Boot Webanwendung
Bei diesem Beispiel werden die folgenden (Spring-) Komponenten eingesetzt.
- Spring Boot für die Autokonfiguration und das Self-Containment
- Spring Data JPA mit Hibernate ORM und der Datenbank H2
- Weitergehende Informationen zum Thema in meinem Beitrag Spring mit JPA und Hibernate (Tutorial)
- Spring MVC für die Steuerung der Oberfläche
- Weitergehende Informationen zum Thema in meinem Beitrag Spring mit einer Webanwendung mit JPA und Validierung (Tutorial)
- Umsetzung des Architekturstils ROCA-Styles
- Thymeleaf als HTML5 Template Engine mit dem CSS Framework Bootstrap
- Die JavaScript Komponenten von Bootstrap basieren auf der JavaScript Bibliothek jQuery
- Als embedded Webserver wird der Apache Tomcat verwendet
- Java SE 8 mit Apache Maven und Eclipse IDE
Das Spring Framework wird natürlich als Basis eingesetzt. Das Beispiel wurde zusätzlich mit meinen Erfahrungen aus laufenden Projekten angereichert.
Was ist Spring Boot?
Mit Spring Boot können autonomen (autarke oder selbständig ausführbare) Java Anwendungen (JAR) auf Basis des Spring Ökosystems erstellt werden (Self-contained).
Die erstellten Programme beinhalten alle benötigten Komponenten und Bibliotheken. Der Webserver wird, wie in diesem Beispiel, vorkonfiguriert in die Anwendung integriert (Auto-configuration).
Durch das Verwenden des Paradigmas Konvention vor Konfiguration kommt die Anwendungen ohne XML-Konfiguration aus (JavaConfig).
Gesteuert wird der Zusammenbau der Spring Boot Webanwendung durch das Buildsystem Apache Maven mit einem speziellen Plugin von Spring.
Spring Boot eignet sich auf Grund dieser genannten Eigenschaften hervorragend als Grundlage für Microservices (Martin Fowler) oder Self-contained Systems (SCS).
Durch die genannten Eigenschaften werden folgende Ziele mit von Spring Boot erreicht.
- Schnelleres Aufsetzen von Spring Projekten durch Vereinfachungen.
- Out-of-the-box Verhalten durch Autokonfigurationen mit sinnvollen Anfangswerten.
- Nicht-funktionale Features werden bereitgestellt.
Wie starte ich?
Um eine Spring Boot Webanwendung zu Erstellen bieten sich die folgenden Möglichkeiten an.
- Der Webservice Spring Initializr.
- Ein Maven Projekt im Eigenbau.
Was ist Spring Initializr?
Über die bereits erwähnte Weboberfläche bzw. dem Webservice kann ein Spring Boot Projekt komfortabel zusammengestellt und als fertige Projektvorlage heruntergeladen werden. Die Weboberfläche ist auch in der Spring Tool Suite (STS) integriert.
In der folgenden Bildergalerie wird gezeigt, wie für dieses Beispiel die Weboberfläche genutzt werden könnte.
Wie wird ein Spring Boot Projekt mit Maven im Eigenbau erstellt?
Dieses Beispiel einer Spring Boot Webanwendung verwendet eine hierarchische Projektstruktur und ist in meinem Git Repository microservices auf GitHub veröffentlicht.
In der obersten Maven pom.xml
wird die Referenz auf das Parent-POM (Project Object Modell) von Spring Boot spring-boot-starter-parent
gesetzt. Durch diese Parent-POM wird auch die POM der Spring Boot Dependencies referenziert. In dieser POM werden alle Abhängigkeiten von Spring Boot verwaltet, die in den einzelnen Spring Modulen verwendet werden können – insbesondere die von Drittherstellern.
Tipp
Die POM spring-boot-dependency
kann für Unternehmen als unternehmensweiter Standard für die Nutzung von Fremdbibliotheken bzw. zum Management des Portfolios (BOM: Bill of Material) verwendet werden.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>de.rahn</groupId> <artifactId>microservices</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <name>Microservices</name> <description>Die Parent-POM für die Microservices auf Basis von Spring Boot.</description> <url>https://github.com/frank-rahn/microservices</url> <modules> <module>securities-management</module> </modules> ...
Zusätzlich wird noch die Java Version 8 definiert.
<properties> <!-- Java Version --> <java.version>1.8</java.version> <!-- APT-Generatoren --> <hibernate-jpamodelgen.version>${hibernate.version}</hibernate-jpamodelgen.version> <!-- Versione der Plugins --> <maven-processor-plugin.version>3.1.0</maven-processor-plugin.version> <jacoco-maven-plugin.version>0.7.6.201602180812</jacoco-maven-plugin.version> <coveralls-maven-plugin.version>4.1.0</coveralls-maven-plugin.version> </properties> <organization> <name>Frank W. Rahn</name> <url>https://www.frank-rahn.de/</url> </organization> <scm> <url>https://github.com/frank-rahn/microservices</url> <connection>scm:git:git://github.com/frank-rahn/microservices.git</connection> <developerConnection>scm:git:ssh://git@github.com:frank-rahn/microservices.git</developerConnection> <tag>master</tag> </scm> <issueManagement> <system>GitHub Issues</system> <url>https://github.com/frank-rahn/microservices/issues</url> </issueManagement> <ciManagement> <system>Travis CI</system> <url>https://travis-ci.org/frank-rahn/microservicess</url> </ciManagement> <licenses> <license> <name>Apache License, Version 2.0</name> <url>https://www.apache.org/licenses/LICENSE-2.0</url> <distribution>manual</distribution> </license> </licenses> <developers> <developer> <name>Frank Rahn</name> <email>frank+git at frank-rahn.de</email> <organization>Frank W. Rahn</organization> <roles> <role>Developer</role> <role>Contributor</role> </roles> </developer> </developers> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadSources>true</downloadSources> <!-- Workaround for http://jira.codehaus.org/browse/MECLIPSE-94 --> <eclipseProjectDir>.</eclipseProjectDir> <additionalProjectnatures> <projectnature>org.eclipse.m2e.core.maven2Nature</projectnature> <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature> </additionalProjectnatures> <additionalBuildcommands> <buildCommand> <name>org.eclipse.m2e.core.maven2Builder</name> </buildCommand> <buildCommand> <name>org.springframework.ide.eclipse.core.springbuilder</name> </buildCommand> </additionalBuildcommands> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>${maven-processor-plugin.version}</version> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>${hibernate-jpamodelgen.version}</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco-maven-plugin.version}</version> <configuration> <excludes> <exclude>**/*_.*</exclude> </excludes> </configuration> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>default-prepare-agent-integration</id> <goals> <goal>prepare-agent-integration</goal> </goals> </execution> <execution> <id>default-report</id> <goals> <goal>report</goal> </goals> </execution> <execution> <id>default-report-integration</id> <goals> <goal>report-integration</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <profiles> <profile> <id>travis</id> <activation> <property> <name>env.TRAVIS</name> <value>true</value> </property> </activation> <build> <plugins> <plugin> <groupId>org.eluder.coveralls</groupId> <artifactId>coveralls-maven-plugin</artifactId> <version>${coveralls-maven-plugin.version}</version> </plugin> </plugins> </build> </profile> </profiles> </project>
Zu Abschluss muss noch das Maven Plugin von Spring Boot für die Erstellung der Spring Boot Webanwendung konfiguriert werden. Dieses wird im Modul securities-management/server-web
durchgeführt. In diesem Modul befindet sich der ausführbare Teil der Spring Boot Webanwendung.
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> </configuration> </plugin> <plugin> <groupId>pl.project13.maven</groupId> <artifactId>git-commit-id-plugin</artifactId> </plugin> </plugins> </build> </project>
Dieses waren die wichtigsten Elemente aus der Maven Konfiguration. Die übrigen Einstellungen sind technologie- oder projektspezifisch.
Die Main Application Class
Die Main Application Class ist die Startklasse einer Spring Boot Anwendung, welche die main()
-Methode bereitstellt. In diesem Beispiel liegt sie im Modul securities-management/server-web
und startet die komplette Spring Boot Webanwendung.
/** * Die Startklasse für diesen Server. * * @author Frank W. Rahn */ @SpringBootApplication public class SecuritiesManagementApplication { /** * @param args */ public static void main(String[] args) { SpringApplication.run(SecuritiesManagementApplication.class, args); } }
Die Konfigurationsdatei application.properties
Diese Konfigurationsdatei befindet sich im Modul securities-management/server-web
und enthält einige wichtige Einträge. Elemente die mit @ geklammert sind, werden durch Werte aus den Maven-Properties ersetzt.
# Allgemeine Informationen spring.application.name = @project.name@ ...
Alternativ kann diese Konfigurationsdatei auch im Format YAML (YAML Ain’t Markup Language) angelegt werden.
Wie wird der Datenbankzugriff mit Spring Data JPA auf H2 realisiert?
Der Datenbankzugriff wurde im Modul securities-management/domains
ausgelagert. Für die Autokonfiguration des Datenbankzugriffs werden die folgenden Starter und Abhängigkeiten benötigt.
spring-boot-starter-data-jpa
Dieser Starter fügt die Abhängigkeiten für die Komponente Spring Data JPA hinzu und konfiguriert sie. Die Implementierung basiert auf dem O/R-Mapper von Hibernate und dem Java Standard JPA (Java Persistence API).spring-boot-starter-validation
Dieser Starter stellt die Bean Validierung v1.1 nach dem JSR-303 bzw. JSR-349 zu Verfügung. Dazu wird die Implementierung des Hibernate Validator verwendet.com.h2database:h2
Diese Abhängigkeit bindet die In-Memory Datenbank H2 in das Projekt ein und konfiguriert die zugehörigeDataSource
.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.rahn.finances</groupId> <artifactId>securities-management</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>..</relativePath> </parent> <artifactId>domains</artifactId> <name>Securities Management Domains</name> <description>Die Domänen der Wertpapierverwaltung.</description> <dependencies> <!-- Spring Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- Libraries --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/generated-sources/annotations</outputDirectory> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </processors> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Zusätzlich müssen in der application.properties
im Modul securities-management/server-web
einige Einträge hinzugefügt werden.
# Allgemeine Informationen spring.application.name = @project.name@ # JPA / Hibernate spring.jpa.show-sql = true # Database H2 spring.h2.console.enabled = true ...
Nach dem gleichen Muster, wie in meinem Beitrag Spring mit JPA und Hibernate (Tutorial) beschrieben, werden die JPA Entitäten im Package de.rahn.finances.domains.entities
definiert.
Anders, als im verwiesenen Beitrag, wird anstelle der abstrakten und generischen Datenzugriffsobjekte (DAO) eine Repository gemäß des Domain-Driven Design (*) von Eric J. Evans verwendet, die durch Spring Data JPA umgesetzt wird.
/** * Der Zugriff auf die Wertpapiere. * * @author Frank W. Rahn */ public interface SecuritiesRepository extends JpaRepository<Security, String> { /** * Liefere die {@link Page} der Wertpapiere unter der Berücksichtigung der Filterparameter. * * @param pageable die Information über die Paginierung * @param inventory Filter: <code>true</code>, nur der aktuelle Bestand wird angezeigt * @param type Filter: nur die Wertpapiere dieser Art anzeigen * @return Eine Seite der Liste aller gefilterten Wertpapiere */ Page<Security> findByInventoryOrType(Pageable pageable, boolean inventory, SecurityType type); }
Die Deklaration der Datenbankzugriffsschicht erfolgt in der Konfigurationsklasse DomainsConfiguration
.
/** * Die Spring Configuration für die Domains. * * @author Frank W. Rahn */ @Configuration @EntityScan(basePackageClasses = { de.rahn.finances.domains.entities.PackageMarker.class }) @EnableJpaRepositories(basePackageClasses = { de.rahn.finances.domains.repositories.PackageMarker.class }) public class DomainsConfiguration { // Leer }
Wie wird die Spring Business Service Facade erstellt?
Die Business Logik wurde im Modul securities-management/services
ausgelagert. Für die Autokonfiguration des Business Services werden die folgenden Starter und Abhängigkeiten benötigt.
spring-boot-starter-actuator
Dieser Starter stellt die Funktionen für das Management und Monitoring der Spring Boot Webanwendungen zu Verfügung. Auf diese Funktionen kann per HTTP REST-Schnittstelle (Beispiel:http://localhost:8000/manage/health
), per Java Management Extensions (JMX) oder über eine remote Shell (SSH oder Telnet) zugegriffen werden. Dazu werden allerdings weitere Starter benötigt.
Dieser Starter ist für das Erfassen der Metriken des Services der Wertpapierverwaltung erforderlich (Siehe auch im Kapitel über Das Erfassen der Metriken).spring-web
Diese Abhängigkeit wird benötigt, damit die AusnahmenSecurityNotFoundException
des Services mit der AnnotationResponseStatus
versehen werden kann. Dadurch kann später in der Oberfläche der HTTP-Statuscode 404 gesendet werden, falls ein Wertpapier in der Datenbank nicht gefunden wird.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.rahn.finances</groupId> <artifactId>securities-management</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>..</relativePath> </parent> <artifactId>services</artifactId> <name>Securities Management Services</name> <description>Die Services der Wertpapierverwaltung.</description> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>de.rahn.finances</groupId> <artifactId>domains</artifactId> </dependency> </dependencies> </project>
Zusätzlich werden in der application.properties
im Modul securities-management/server-web
folgende Einträge hinzugefügt.
# JSON Formatieren spring.jackson.serialization.indent-output = true # Actuator management.address = 127.0.0.1 management.context-path = /manage # Actuator /info info.application.name = @project.name@ info.application.groupId = @project.groupId@ info.application.artifactId = @project.artifactId@ info.application.version = @project.version@ info.application.description = @project.description@ # Actuator /shutdown endpoints.shutdown.enabled = true # Actuator /health endpoints.health.sensitive = false # Actuator /env, /configprops, /autoconfig endpoints.env.keys-to-sanitize = password,secret,key,passwort
Der Service der Wertpapierverwaltung wird, nach dem gleichen Muster wie in meinem Beitrag Spring an einem einfachem Beispiel (Tutorial) beschrieben, erstellt. Ein Unterschied besteht nur in der Konfiguration. Im Beitrag wird die Spring-XML-Konfiguration verwendet. Für die Spring Boot Webanwendung wurde jedoch die JavaConfig gewählt.
Die Deklaration des Services erfolgt in der Konfigurationsklasse ServicesConfiguration
per @ComponentScan
.
/** * Die Spring Configuration für die Services. * * @author Frank W. Rahn */ @Configuration @ComponentScan(basePackageClasses = { de.rahn.finances.services.securities.PackageMarker.class }) public class ServicesConfiguration { // Leer }
Das Erfassen der Metriken
Das Erfassen der Metriken wurde im Aspekt SecuritiesServiceMetricsAspect
definiert. Der Aspekt erfasst die Daten und leitet sie an den Metrikenservice von Spring Boot Actuator weiter.
Weitere Informationen dazu siehe:
- Typen von Metriken: Counter, Meter, Timer, Gauge und Histogram bei Dropwizard Metrics von Coda Hale
- Namenskonventionen von Metriken bei Matt Aimonetti
/** * Ein Aspekt für die Metriken des {@link SecuritiesService}. * * @author Frank W. Rahn */ @Component @Aspect public class SecuritiesServiceMetricsAspect { /** Der Prefix für die Aufrufzähler. */ public static final String PREFIX_METRICNAME_CALLS = "counter.securities.services.securities.invoked"; /** Der Prefix für die Aufrufzähler. */ public static final String PREFIX_METRICNAME_CALL = "counter.securities.services.securities.invoke."; /** Der Prefix für die Aufrufrate. */ public static final String PREFIX_METRICNAME_EVENTS = "meter.securities.services.securities.used"; /** Der Prefix für die Aufrufrate. */ public static final String PREFIX_METRICNAME_TIMER = "timer.securities.services.securities.getsecurities.executed"; /** Der Prefix für die Fehlerzähler. */ public static final String PREFIX_METRICNAME_ERRORS = "counter.securities.services.securities.failed"; /** Der Prefix für die Fehlerzähler. */ public static final String PREFIX_METRICNAME_ERROR = "counter.securities.services.securities.failure."; /** Spring Boot Service für Counter und Meter. */ @Autowired private CounterService counterService; /** Spring Boot Service für Guage, Timer und Histogram. */ @Autowired private GaugeService gaugeService; /** * Für den Zugriff auf die Schnittstelle. */ @Pointcut("this(de.rahn.finances.services.SecuritiesService)") private void onSecuritiesService() { } /** * Zähle die erfolgreichen lesenden Zugriffe. */ @AfterReturning(pointcut = "execution(* de.rahn.finances.services.SecuritiesService.get*(..))") public void afterCallingSecuritiesServiceRead() { counterService.increment(PREFIX_METRICNAME_EVENTS); counterService.increment(PREFIX_METRICNAME_CALLS); counterService.increment(PREFIX_METRICNAME_CALL + "read"); } /** * Zähle die erfolgreichen verändernden Zugriffe. */ @AfterReturning( pointcut = "execution(* de.rahn.finances.services.SecuritiesService.save(..)) || execution(* de.rahn.finances.services.SecuritiesService.delete(..))") public void afterCallingSecuritiesServiceModified() { counterService.increment(PREFIX_METRICNAME_EVENTS); counterService.increment(PREFIX_METRICNAME_CALLS); counterService.increment(PREFIX_METRICNAME_CALL + "modified"); } /** * Zähle alle geworfenen Ausnahmen. */ @AfterThrowing(pointcut = "onSecuritiesService()", throwing = "exception") public void afterThrowsException(Exception exception) { counterService.increment(PREFIX_METRICNAME_ERRORS); counterService.increment(PREFIX_METRICNAME_ERROR + exception.getClass().getSimpleName().toLowerCase()); } @Around("execution(org.springframework.data.domain.Page de.rahn.finances.services.SecuritiesService.getSecurities(org.springframework.data.domain.Pageable))") public Object doGetSecuritiesTimer(ProceedingJoinPoint joinPoint) throws Throwable { long start = currentTimeMillis(); Object returnValue = joinPoint.proceed(); gaugeService.submit(PREFIX_METRICNAME_TIMER, currentTimeMillis() - start); return returnValue; } }
Wie erstelle ich eine Weboberfläche mit Spring Boot?
In diesem Beispiel soll eine Weboberfläche auf der Basis von HTML5 mit dem CSS Framework Bootstrap von Twitter erstellt und zusätzlich die JavaScript Bibliothek jQuery verwendet werden. Die Oberfläche befindet sich, mit der eigentlichen Spring Boot Webanwendung, im Modul securities-management/server-web
.
In dieser Bildergalerie werden die Masken dieses Beispiels gezeigt.
Der embedded Webserver (Apache Tomcat)
Für eine Spring Boot Webanwendung wird ein Webserver benötigt, der die HTML Seiten ausliefert. Bei Spring Boot wird dieses mit einem embedded Webserver durchgeführt. Dazu wird der Spring Boot Starter spring-boot-starter-web
benötigt, der die komplette Laufzeitumgebung mit dem Apachen Tomcat bereitstellt. Einen Deployment Descriptor web.xml
muss nicht erstellt werden. Zusätzlich werden in der application.properties
im Modul securities-management/server-web
folgende Einträge definiert.
In Zeile 11 wird beispielsweise der Standard-Port des Servers überschrieben.
# Allgemeine Informationen spring.application.name = @project.name@ # JPA / Hibernate spring.jpa.show-sql = true # Database H2 spring.h2.console.enabled = true # Tomcat server.port = 8000 server.tomcat.accesslog.enabled = true server.tomcat.accesslog.pattern = %h %t "%r" %s %b ...
Wie wird die Template Engine Thymeleaf konfiguriert?
Für die Autokonfiguration der Template Engine, in diesem Beispiel wird Thymeleaf verwendet, muss der Spring Boot Starter spring-boot-starter-thymeleaf
verwendet werden. Da eine Abhängigkeit zum Starter spring-boot-starter-web
besteht, kann dieser entfernt werden.
<artifactId>server-web</artifactId> <name>Securities Management Server Web</name> <description>Der Webserver der Wertpapierverwaltung.</description> <dependencies> <dependency> <groupId>de.rahn.finances</groupId> <artifactId>commons</artifactId> </dependency> <dependency> <groupId>de.rahn.finances</groupId> <artifactId>services</artifactId> </dependency> <!-- Spring Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> </configuration> </plugin> <plugin> <groupId>pl.project13.maven</groupId> <artifactId>git-commit-id-plugin</artifactId> </plugin> </plugins> </build> </project>
In der application.properties
wird das Zwischenspeichern der generierten Seiten abgeschaltet. Dieses sollte aber nur in der Entwicklungsumgebung geschehen, damit geänderte Webseiten direkt begutachtet werden können. In Produktionssystemen ist das Zwischenspeichern wieder einzuschalten.
# Allgemeine Informationen spring.application.name = @project.name@ # JPA / Hibernate spring.jpa.show-sql = true # Database H2 spring.h2.console.enabled = true # Tomcat server.port = 8000 server.tomcat.accesslog.enabled = true server.tomcat.accesslog.pattern = %h %t "%r" %s %b # Thymeleaf spring.thymeleaf.cache = false ...
Wie funktioniert ein Thymeleaf-Template?
Ein Template unter Thymeleaf ist eine valide HTML-Datei, die um den XML Namesraums von Thymeleaf erweitert wurde. Die Engine von Thymeleaf hält im Speicher für jedes Template einen DOM-Tree vor. Dieser DOM-Tree wird an den markierten Stellen mit dynamischen Inhalten angereichert.
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout" > <head> <meta charset="utf-8" /> <title>Home</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" /> <link rel="stylesheet" href="../static/css/style.css" th:href="@{/css/style.css}" /> </head> <body> <div class="container"> <h1 layout:fragment="header">Wertpapierverwaltung</h1> <div layout:fragment="content"> <section> <h2>Wertpapierverwaltung</h2> <p><a href="securities.html" th:href="@{/securities}">Liste aller Wertpapiere</a></p> </section> <section> <h2>Management</h2> <p><a href="info.html" th:href="@{/info}">Management API</a></p> </section> </div> </div> </body> </html>
Thymeleaf unterstützt dabei eine Technik, die natural templating genannt wird. Wird ein Template direkt in den Browser geladen, so ignoriert der Browser den zusätzlichen XML Namesraum mit den Kontrollstrukturen und stellt die Webseite dar. Ein- und Ausgabefelder können im Template mit beispielhaften Werten belegt werden. Diese werden zur Laufzeit durch die dynamischen Inhalte ersetzt. Durch diese Technik ist es möglich, die Oberfläche der Spring Boot Webanwendung in Form eines klickbaren Prototypen zu erstellen. Die Webseiten können direkt im Browser angesehen werden.
Die Bildergalerie des klickbaren Prototypen für dieses Beispiel wird direkt nach dem Template security.html
gezeigt.
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout" > <head> <meta charset="utf-8" /> <title>Wertpapier</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" /> <link rel="stylesheet" href="../static/css/style.css" th:href="@{/css/style.css}" /> </head> <body> <div class="container"> <h1 layout:fragment="header">Wertpapier</h1> <div layout:fragment="content"> <section class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">Wertpapier</h2> </div> <div class="panel-body"> <form id="security" th:object="${security}" th:action="@{/security}" action="#" method="post" role="form"> <div th:if="${#fields.hasErrors('${security}')}" class="alert alert-danger" role="alert"> <a href="#" class="close" data-dismiss="alert" aria-label="close">×</a><p th:errors="${security}">Allgemeiner Fehler</p> </div> <div class="form-group" th:classappend="${#fields.hasErrors('id')}? 'has-error has-feedback'"> <label for="id" class="control-label">ID:</label> <input type="text" class="form-control" th:field="*{id}" value="8ad72f6f-2a39-4846-8940-f6139f3d5597" readonly="readonly"/><span th:if="${#fields.hasErrors('id')}" class="glyphicon glyphicon-remove form-control-feedback"></span> <div th:if="${#fields.hasErrors('id')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{id}">Fehler in der ID</span></div> </div> <div class="form-group" th:classappend="${#fields.hasErrors('isin')}? 'has-error has-feedback'"> <label for="isin" class="control-label">ISIN:</label> <input type="text" class="form-control" th:field="*{isin}" value="DE0001234560" /><span th:if="${#fields.hasErrors('isin')}" class="glyphicon glyphicon-remove form-control-feedback"></span> <div th:if="${#fields.hasErrors('isin')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{isin}">Fehler in der ISIN</span></div> </div> <div class="form-group" th:classappend="${#fields.hasErrors('wkn')}? 'has-error has-feedback'"> <label for="wkn" class="control-label">Wertpapier-Kennnummer:</label> <input type="text" class="form-control" th:field="*{wkn}" value="123456" /><span th:if="${#fields.hasErrors('wkn')}" class="glyphicon glyphicon-remove form-control-feedback"></span> <div th:if="${#fields.hasErrors('wkn')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{wkn}">Fehler in der Wertpapier-Kennnummer</span></div> </div> <div class="form-group" th:classappend="${#fields.hasErrors('name')}? 'has-error has-feedback'"> <label for="name" class="control-label">Name des Wertpapiers:</label> <input type="text" class="form-control" th:field="*{name}" value="Firma A AG" /><span th:if="${#fields.hasErrors('name')}" class="glyphicon glyphicon-remove form-control-feedback"></span> <div th:if="${#fields.hasErrors('name')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{name}">Fehler im Name</span></div> </div> <div class="form-group" th:classappend="${#fields.hasErrors('symbol')}? 'has-error has-feedback'"> <label for="symbol" class="control-label">Symbol des Wertpapiers:</label> <input type="text" class="form-control" th:field="*{symbol}" value="A01" /><span th:if="${#fields.hasErrors('symbol')}" class="glyphicon glyphicon-remove form-control-feedback"></span> <div th:if="${#fields.hasErrors('symbol')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{symbol}">Fehler im Symbol</span></div> </div> <div class="form-group" th:classappend="${#fields.hasErrors('type')}? 'has-error has-feedback'"> <label for="type" class="control-label">Wertpapierart:</label> <select class="form-control" th:field="*{type}"> <option value="stock" th:each="e: ${securityTypeList}" th:value="${e.key}" th:text="${e.value}">Aktie</option> </select> <div th:if="${#fields.hasErrors('type')}" class="alert alert-danger" role="alert"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <span th:errors="*{type}">Fehler in der Wertpapierart</span></div> </div> <div class="text-center"> <a class="btn btn-default" href="securities.html" th:href="@{/securities}">Abbrechen</a> <button type="submit" class="btn btn-danger">Speichern</button> </div> </form> </div> </section> </div> </div> </body> </html>
Die Masken (Oberfläche, View, UI) und die Steuerung (Controller)
Durch die vollständig Integration von Thymeleaf in Spring MVC, werden die Controller, wie bei Spring gewohnt, implementiert (Siehe Beitrag Spring mit einer Webanwendung mit JPA und Validierung). Bei der Entwicklung wurde der Architekturstil ROCA-Styles berücksichtigt. Dieser Architekturstil besteht aus einer Sammlung von Empfehlungen zur Entkopplung von Server und Client. Diese Empfehlungen basieren auf den REST-Prinzipien für den Server und dem Prinzip von Progressive Enhancement für den Client.
Weitergehende Informationen zum Thema in meinem Beitrag Spring mit einer Webanwendung mit JPA und Validierung (Tutorial).
Die Literaturempfehlungen für dieses Beispiel
- Spring Boot Documentation
- Using Thymeleaf
- Microservices: Grundlagen flexibler Softwarearchitekturen (*)
- Microservices: Konzeption und Design (*)
- Domain-Driven Design (*)
Der Quellcode und Download des Beispiels
Quellcode ansehen bei GitHub:
microservices
Download einer ZIP-Datei von GitHub:
microservices
Die Maven Befehle
Eclipse Konfiguration neu erzeugen: $ mvn eclipse:clean eclipse:eclipse
Anwendung bauen: $ mvn clean package
Anwendung ausführen:
$ cd securities-management/server-web $ mvn spring-boot:run
- Wer ist der optimale Java Bean Mapper? - Freitag, 22. September 2023
- Spring Boot Webanwendung: Die ersten Schritte (Tutorial) - Montag, 28. März 2016
- Mainframe-Zugriff via Java - Sonntag, 04. Mai 2014
Herzlichen Dank für die ganzen Informationen auf Ihrer Seite. Ich lese mich gerade zum Thema Spring & Java ein und finde mich hier sehr gut zurecht.
Vielen Dank und viel Spaß mit Spring und Java.
Viele Grüße
Frank Rahn
awesome blog.. really helpful for all i got more clear ideas from this blog