,

Spring Boot Webanwendung: Die ersten Schritte (Tutorial)

Spring Boot Webanwendung

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.

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.

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.

Die hierarchische Projektstruktur der Wertpapierverwaltung (© Frank Rahn)

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örige DataSource.
<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.

Das Domänmodell der Wertpapierverwaltung

Das Domänmodell der Wertpapierverwaltung (© Frank Rahn)

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 Ausnahmen SecurityNotFoundException des Services mit der Annotation ResponseStatus 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.

Das Servicemodell der Wertpapierverwaltung

Das Servicemodell der Wertpapierverwaltung (© Frank Rahn)

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:

/**
 * 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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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

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
Frank Rahn
Letzte Artikel von Frank Rahn (Alle anzeigen)
3 Kommentare

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Ihre E-Mail-Adresse wird nicht veröffentlicht. Ihr Kommentar wird verschlüsselt an meinen Server gesendet. Erforderliche Felder sind mit * markiert.

Weitere Informationen und Widerrufshinweise finden Sie in meiner Datenschutzerklärung.