Spring Boot 2

+ andere TechDocs
+ Spring-Projekte
+ Spring DI und AOP
+ Spring Boot 1.5
+ Spring Boot 1.4
+ Spring Boot Project
+ Spring Batch
+ Docker
+


Spring unterstützt die Softwareentwicklung von Enterprise-tauglichen JVM-basierenden Anwendungen durch Vereinfachungen, Effektivität, Flexibilität, Portabilität und Förderung guter Programmierpraktiken. Spring besteht aus mehreren Spring-Projekten.

Die Basis bildet das Spring Framework, welches Dependency Injection (DI), Aspect-Oriented Programming (AOP), Declarative Transaction Management, MVC Web Application, RESTful Web Services und vieles mehr bietet.

Spring Data erleichtert die Anbindung an Datenbanken, sowohl relationale als auch NoSQL. Programmierbeispiele hierzu finden Sie unter Spring-Boot-Kommandozeilenanwendung mit JPA und MongoDB mit Spring.

Spring Boot erleichtert die einfache Entwicklung eigenständig lauffähiger Anwendungen per Convention over Configuration, die ohne XML-Konfiguration auskommen und alle nötigen Klassenbibliotheken mitbringen. Mit Hilfe einfacher Annotationen werden benötigte Eigenschaften hinzugefügt. Beispielsweise genügen wenige Annotationen, um einen embedded Webserver zu integrieren, der REST-Services anbietet, ohne dass ein Application Server benötigt wird. Eigenständig lauffähige Anwendungen werden beispielsweise benötigt, um schwer wartbare Monolithen zu vermeiden, und stattdessen die Gesamtanwendung zu modularisieren, also in kleinere Einheiten zu gliedern, die einzeln unabhängig voneinander deployt, upgedatet und ausgetauscht werden können. Sie können beispielsweise in Docker-Containern ausgeführt werden. Weiteres hierzu siehe Docker und Microservices.

Diese Webseite behandelt die Spring-Boot-Version 2.2.4. Falls Sie sich für ältere Spring-Boot-Versionen interessieren, sehen Sie sich Spring Boot 1.5 oder Spring Boot 1.4 an.



Inhalt

  1. Doku zu Spring, Spring Boot und Spring Data
  2. Einige Unterschiede zwischen Spring Boot 1.4.x, 1.5.x und 2.x
  3. Spring-Boot-Kommandozeilenanwendung mit JPA
  4. Spring-Boot-Kommandozeilenanwendung mit MongoDB
  5. Spring-Boot-Webanwendung mit Tomcat
  6. Spring-Boot-Webanwendung mit Jetty
  7. Spring-Boot mit RestController
  8. Spring-Boot mit REST per Spring-MVC
  9. Spring-Boot mit REST-Client
  10. Spring-Boot mit REST-Client mit verschiedenen Mediatypen
  11. Spring-Boot mit REST-Client mit JsonObject
  12. Spring-Boot mit REST per JAX-RS
  13. Spring-Boot-Webanwendung mit JavaServer Faces (JSF)
  14. Spring-Boot-Webanwendung mit MVC und Thymeleaf
  15. JUnit-Modultest mit Mock für Spring-Boot-Webanwendung
  16. Integrationstest mit embedded Webserver für Spring-Boot-Webanwendung
  17. Test einer Webanwendung mit MVC-Model, MVC-View und Spring Security
  18. Externe WAR in Spring-Boot-Webanwendung deployen
  19. Actuator zur Ausgabe von Health, Status, Props und Metriken
  20. Konfiguration per application.properties
  21. Properties-Priorisierung und Profile
  22. ApplicationListener und EventListener
  23. Anzeige des Spring-Environments und der Spring-Properties
  24. Weitere Beispiele



Doku zu Spring, Spring Boot und Spring Data

Weiterführende Doku finden Sie beispielsweise unter:
Spring Getting Started Guides,
Spring Framework Reference Documentation,
Spring DI und AOP,
Spring Boot Getting Started,
Spring Boot Reference Guide,
Spring Boot 1.4 Release Notes,
Spring Boot 1.5 Release Notes,
Spring Boot 2.0 Release Notes,
Spring Boot 2.0 Migration Guide,
Spring Boot Samples,
Spring Boot Maven Plugin Reference Guide,
Spring Boot Maven Plugin Doku,
Spring Boot in Action von Craig Walls,
Spring Boot Cookbook von Alex Antonov,
Spring Data,
Spring Data Commons - Reference Documentation,
Spring Data JPA - Reference Documentation,
Spring Data Examples.


Einige Unterschiede zwischen Spring Boot 1.4.x, 1.5.x und 2.x

Die folgenden beiden Tabellen listen ein paar Beispiele für einige Unterschiede zwischen den Spring-Boot-Versionen 1.4.x, 1.5.x und 2.x auf.
Genaueres zu den Unterschieden siehe: Spring Boot 1.4 Release Notes, Spring Boot 1.5 Release Notes, Spring Boot 2.0 Release Notes und Spring Boot 2.0 Migration Guide.


 Spring Boot 1.4.x Spring Boot 1.5.x
Modultest
ohne Web
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=ApplicationMain.class)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
Modultest
mit Web-Mock
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.MOCK)
Integrations-
test mit Web
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=ControllerUndMain.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
Integrations-
test mit REST
org.springframework.boot.test.TestRestTemplate org.springframework.boot.test.web.client.TestRestTemplate
JSF-Config org.springframework.boot.context.embedded.ServletContextInitializer org.springframework.boot.web.servlet.ServletContextInitializer
org.springframework.boot.context.web.SpringBootServletInitializer org.springframework.boot.web.support.SpringBootServletInitializer
org.springframework.boot.context.embedded.ServletRegistrationBean org.springframework.boot.web.servlet.ServletRegistrationBean

 Spring Boot 1.5.x Spring Boot 2.x
UserDetailsService UserDetails userDetails = jpaRepository.findOne( username ); UserDetails userDetails = jpaRepository.findById( username ).get();
shutdown endpoints.shutdown.enabled=true management.endpoint.shutdown.enabled=true
Actuator-Aktivierung management.security.enabled=false management.endpoints.web.exposure.include=*
Actuator-URL http://localhost:8080/health http://localhost:8080/actuator/health
JSF-Config org.springframework.boot.web.support.SpringBootServletInitializer org.springframework.boot.web.servlet.support.SpringBootServletInitializer
embedded
TomcatWebServer
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer org.springframework.boot.web.embedded.tomcat.TomcatWebServer
org.springframework.boot.context.embedded.EmbeddedServletContainerFactory org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory


Spring-Boot-Kommandozeilenanwendung mit JPA

Das folgende Beispiel demonstriert:

Sie können die im Folgenden gezeigten Beispiele wahlweise entweder downloaden oder wie beschrieben Schritt für Schritt ausführen.

Führen Sie folgende Schritte aus:

  1. JDK und Maven müssen installiert sein.

  2. Wir könnten uns ein erstes Programmgerüst automatisch vom "Spring Initializr" unter http://start.spring.io erstellen lassen. Im Folgenden werden jedoch alle Schritte manuell ausgeführt, um die Transparenz und Nachvollziehbarkeit zu erhöhen.

  3. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootJpa

    cd SpringBootJpa

    md src\main\java\springbootdemo

    md src\test\java\springbootdemo

    md src\main\resources

    md src\test\resources

    tree /F

  4. Erstellen Sie im SpringBootJpa-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootJpa</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    

    Sehen Sie sich die Erläuterungen unter Creating an executable jar an.

    Sehen Sie sich die Dokus an zu: spring-boot-starter-parent-POM, Build systems / Maven / spring-boot-starter-parent, spring-boot-maven-plugin-POM, Spring Boot Maven Plugin Doku, Spring Boot Maven plugin im Reference Guide, spring-boot-starter-data-jpa-POM, Accessing Data with JPA.

    Sie können im Maven-Repo nachsehen, ob es mittlerweile eine neuere Version für spring-boot-starter-parent gibt.

    Falls es in Ihrem Projekt nicht möglich ist, spring-boot-starter-parent als <parent> einzubinden, sehen Sie sich an: Using Spring Boot without the parent POM.

  5. Erzeugen Sie im Resources-Verzeichnis src\main\resources für die Datenbankparameter die Spring-Konfigurationsdatei: application.properties

    spring.datasource.url         = jdbc:h2:./target/h2-db;DB_CLOSE_ON_EXIT=FALSE
    spring.datasource.username    = sa
    spring.datasource.password    =
    spring.jpa.hibernate.ddl-auto = update
    spring.main.show-banner       = false
    

    Sehen Sie sich die Erläuterungen unter H2 Database Features, H2 Database URL Overview und Spring Database initialization an.

  6. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine JPA-Entity-Klasse hinzu: MeineEntity.java

    package springbootdemo;
    
    import java.util.Date;
    import javax.persistence.*;
    
    @Entity
    public class MeineEntity
    {
       @Id @GeneratedValue
       private Long   id;
       private Date   datum;
       private String text;
    
       public Long   getId()    { return id;    }
       public Date   getDatum() { return datum; }
       public String getText()  { return text;  }
       public void setId(    Long id     ) { this.id    = id;    }
       public void setDatum( Date datum  ) { this.datum = datum; }
       public void setText(  String text ) { this.text  = text;  }
    
       @Override
       public String toString()
       {
          return "[id=" + id + ", datum=" + datum + ", text=" + text + "]";
       }
    }
    

    Dies ist eine einfache übliche JPA-Entity-Klasse, welche eine Datenbanktabelle repräsentiert. Sehen Sie sich die Erläuterungen unter Java Persistence API (JPA), @Entity, @Id und @GeneratedValue an.

  7. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Spring-Repository-Klasse hinzu: MeineEntityRepository.java

    package springbootdemo;
    
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface MeineEntityRepository extends CrudRepository<MeineEntity,Long>
    {
       public MeineEntity findByText( String text );
    }
    

    Sehen Sie sich die Erläuterungen unter @Repository, Working with Spring Data Repositories, Spring Data JPA Repositories, DAO / Repository und CrudRepository an.

    Die Deklaration von "findByText( String text )" führt dazu, dass Spring automatisch eine entsprechende Methode implementiert. So können viele weitere Methoden auch mit komplizierteren Abfragebedingungen sehr einfach hinzugefügt werden. Ein ausführlicheres Beispiel hierzu finden Sie in PersonRepository.java.

  8. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Application-Main-Klasse hinzu: ApplicationMain.java

    package springbootdemo;
    
    import java.util.Date;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ApplicationMain implements CommandLineRunner
    {
       @Autowired
       private MeineEntityRepository repo;
    
       public static void main( String[] args )
       {
          SpringApplication.run( ApplicationMain.class, args );
       }
    
       @Override
       public void run( String... args ) throws Exception
       {
          if( args != null && args.length > 1 ) {
             if( "add".equals( args[0] ) ) {
                for( int i = 1; i < args.length; i++ ) {
                   MeineEntity me = new MeineEntity();
                   me.setText( args[i] );
                   me.setDatum( new Date() );
                   me = repo.save( me );
                   System.out.println( "---- " + me + " gespeichert." );
                }
                return;
             } else if( "get".equals( args[0] ) ) {
                System.out.println( "---- " + repo.findByText( args[1] ) );
                return;
             } else {
                System.out.println( "Fehlerhafter Parameter." );
             }
          }
          System.out.println( "---- Alle Eintraege:" );
          Iterable<MeineEntity> itr = repo.findAll();
          for( MeineEntity me : itr ) {
             System.out.println( "     " + me );
          }
       }
    }
    

    Sehen Sie sich die einleitenden Erläuterungen in Structuring your code an.

    Die Annotation @SpringBootApplication beinhaltet die drei Annotationen:

    Die Annotation @Autowired sorgt dafür, dass in die Variable repo eine Instanz der Klasse MeineEntityRepository per Dependency Injection (DI) injiziert wird.

    Über SpringApplication.run() wird die Spring-Boot-Anwendung gestartet, und es wird CommandLineRunner.run() ausgeführt.

    Die Datenbankzugriffe erfolgen über das in die Variable repo injizierte Spring-Repository. Die Methoden repo.save() und repo.findAll() sind in einem CrudRepository immer vorhanden. Die Methode repo.findByText() wurde automatisch erstellt.

    Per Kommandozeilenparameter add werden Einträge hinzugefügt, per get wird ein einzelner Eintrag ausgelesen und ohne Kommandozeilenparameter werden alle Einträge angezeigt.

  9. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootJpa]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       |- ApplicationMain.java
     |       |       |- MeineEntity.java
     |       |       '- MeineEntityRepository.java
     |       '- [resources]
     |           '- application.properties
     '- pom.xml
    
  10. Durch das spring-boot-maven-plugin wird eine ausführbare jar-Datei erzeugt, welche alle benötigten Abhängigkeiten beinhaltet.
    Führen Sie im Kommandozeilenfenster beispielsweise aus:

    mvn clean package

    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar add Anton Berta Caesar

    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar add Dora

    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar

    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar get Berta

  11. Sie erhalten (gekürzt):

    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar add Anton Berta Caesar
    ---- [id=1, datum=...2016, text=Anton] gespeichert.
    ---- [id=2, datum=...2016, text=Berta] gespeichert.
    ---- [id=3, datum=...2016, text=Caesar] gespeichert.
    
    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar add Dora
    ---- [id=4, datum=...2016, text=Dora] gespeichert.
    
    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar
    ---- Alle Eintraege:
         [id=1, datum=2016..., text=Anton]
         [id=2, datum=2016..., text=Berta]
         [id=3, datum=2016..., text=Caesar]
         [id=4, datum=2016..., text=Dora]
    
    java -Dlogging.level.root=WARN -jar target/SpringBootJpa-1.0-SNAPSHOT.jar get Berta
    ---- [id=2, datum=2016..., text=Berta]
    
  12. Sehen Sie sich die verwendeten Libs an:

    mvn dependency:tree

  13. In dem weiter unten gezeigten Beispiel Spring-Boot-Webanwendung mit MVC und Thymeleaf wird eine sehr ähnliche JPA-Anwendung zu einer Webanwendung erweitert.

  14. Da die H2-DB so konfiguriert ist, dass sie ihre Daten in einer Datei im target-Verzeichnis speichert, bleiben die Daten so lange persistent, bis Sie das target-Verzeichnis löschen, z.B. per mvn clean.

  15. Statt der H2-DB können Sie natürlich auch andere Datenbanken verwenden. Ersetzen Sie in der pom.xml die beiden Zeilen

          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
    

    und passen Sie in der application.properties folgende Zeilen an:

    spring.datasource.url         = jdbc:h2:./target/h2-db;DB_CLOSE_ON_EXIT=FALSE
    spring.datasource.username    = sa
    spring.datasource.password    =
    

    Führen Sie anschließend wieder obige Kommandos aus.

    Hier ein paar willkürliche Beispiele für mögliche Einträge für andere Datenbanken.
    Seien Sie vorsichtig mit tatsächlich genutzten Datenbanken: Je nach Konfiguration löscht das Programmierbeispiel DB-Tabellen!
    Beachten Sie, dass bei "In-Memory"-DBs der Datenbankinhalt bei jedem Programmende verloren geht.

    DBpom.xml:
    <dependency>
    application.properties:
    spring.datasource.url, ...username, ...password
    H2
    (in Datei)
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    jdbc:h2:./target/h2-db;DB_CLOSE_ON_EXIT=FALSE
    sa
    H2
    (in Memory)
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    jdbc:h2:mem:h2-db;DB_CLOSE_ON_EXIT=FALSE
    sa
    HSQLDB
    (in Datei)
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    jdbc:hsqldb:./target/hsqldb
    sa
    HSQLDB
    (in Memory)
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    jdbc:hsqldb:mem:MeineDb
    sa
    Derby <groupId>org.apache.derby</groupId>
    <artifactId>derby</artifactId>
    jdbc:derby:./target/Derby-DB;create=true
    MySQL <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    jdbc:mysql://localhost:3306/MeineDb
    root, mysqlpwd
    Oracle XE <groupId>com.oracle.jdbc</groupId>
    <artifactId>ojdbc7</artifactId>
    jdbc:oracle:thin:@localhost:1521:XE
    ..., ...
  16. Sie können das Beispiel leicht um einen JUnit-Modultest erweitern.

    Erweitern Sie im SpringBootJpa-Projektverzeichnis die Maven-Projektkonfigurationsdatei pom.xml um die Test-Dependency:

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

    Fügen Sie im src\test\java\springbootdemo-Verzeichnis eine JUnit-Testklasse hinzu: ApplicationMainTest.java

    package springbootdemo;
    
    import java.util.Date;
    import org.junit.*;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith( SpringRunner.class )
    @SpringBootTest( webEnvironment = WebEnvironment.NONE )
    public class ApplicationMainTest
    {
       @Autowired
       private DataSourceProperties dsp;
       @Autowired
       private MeineEntityRepository repo;
    
       @Test
       public void test()
       {
          MeineEntity me1 = new MeineEntity();
          me1.setText( "Mein Test-Text" );
          me1.setDatum( new Date() );
          me1 = repo.save( me1 );
          Assert.assertNotNull( me1.getId() );
          MeineEntity me2 = repo.findByText( me1.getText() );
          Assert.assertNotNull( me2 );
          Assert.assertEquals( 1, repo.count() );
          System.out.println( "---- DriverClassName: " + dsp.getDriverClassName() );
          System.out.println( "---- URL, Username:   " + dsp.getUrl() + ", " + dsp.getUsername() );
          System.out.println( "---- Daten:           " + repo.findAll() );
       }
    }
    

    Sehen Sie sich die Javadoc an zu den Klassen, Annotationen und Parametern: @RunWith, SpringRunner, @SpringBootTest, WebEnvironment.NONE, @Autowired, DataSourceProperties und @Test.

    Fügen Sie im src\test\resources-Verzeichnis eine Test-Konfigurationsdatei hinzu: application.properties

    spring.datasource.url = jdbc:h2:mem:h2-db;DB_CLOSE_ON_EXIT=FALSE
    
  17. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootJpa]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [springbootdemo]
     |   |   |       |- ApplicationMain.java
     |   |   |       |- MeineEntity.java
     |   |   |       '- MeineEntityRepository.java
     |   |   '- [resources]
     |   |       '- application.properties
     |   '- [test]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- ApplicationMainTest.java
     |       '- [resources]
     |           '- application.properties
     '- pom.xml
    
  18. Führen Sie den JUnit-Modultest aus:

    mvn clean test



Spring-Boot-Kommandozeilenanwendung mit MongoDB

Ein Spring-Boot-/Spring-Data-Beispiel zur NoSQL-Datenbank MongoDB finden Sie unter: MongoDB mit Spring.



Spring-Boot-Webanwendung mit Tomcat

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. JDK und Maven müssen installiert sein.

  2. Wir könnten uns ein erstes Programmgerüst automatisch vom "Spring Initializr" unter http://start.spring.io erstellen lassen. Im Folgenden werden jedoch alle Schritte manuell ausgeführt, um die Transparenz und Nachvollziehbarkeit zu erhöhen.

  3. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootWeb

    cd SpringBootWeb

    md src\main\java\springbootdemo

    tree /F

  4. Erstellen Sie im SpringBootWeb-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootWeb</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    

    Sehen Sie sich die Erläuterungen unter Creating an executable jar an.

    Sie können im Maven-Repo nachsehen, ob es mittlerweile eine neuere Version für spring-boot-starter-parent gibt.

    Falls es in Ihrem Projekt nicht möglich ist, spring-boot-starter-parent als <parent> einzubinden, sehen Sie sich an: Using Spring Boot without the parent POM.

  5. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Controller-Klasse hinzu: ControllerUndMain.java

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    @SpringBootApplication
    public class ControllerUndMain
    {
       @RequestMapping( path = "/web", produces = "text/plain;charset=UTF-8" )
       @ResponseBody
       String halloSpringBootWeb()
       {
          return "Hallo Spring-Boot-Web-Welt!";
       }
    
       public static void main( String[] args )
       {
          SpringApplication.run( ControllerUndMain.class, args );
       }
    }
    

    Sehen Sie sich folgende einleitende Erläuterungen an: Structuring your code und Writing the code, @RequestMapping annotations.
    Die Annotation @SpringBootApplication beinhaltet die Annotationen @Configuration, @EnableAutoConfiguration und @ComponentScan.
    Sehen Sie sich die Javadoc an zu den Annotationen @Controller, @SpringBootApplication, @RequestMapping und @ResponseBody, sowie zu der Klasse SpringApplication.

  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [springbootdemo]
     |               '- ControllerUndMain.java
     '- pom.xml
    
  7. Durch das spring-boot-maven-plugin wird eine ausführbare jar-Datei erzeugt, welche alle benötigten Abhängigkeiten beinhaltet.
    Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    Und zeigen Sie die vom embedded Tomcat generierte Webseite an:

    start http://localhost:8080/web

  8. Sie erhalten:

    Hallo Spring-Boot-Web-Welt!
    
  9. Alternativ können Sie auch mit curl testen:

    curl http://localhost:8080/web

    (Falls Sie das sehr hilfreiche Kommandozeilentool curl noch nicht installiert haben: Downloaden Sie z.B. Win64 - Generic, Win64 ia64 zip 7.33.0 binary, ohne SSL (curl-7.33.0-win64-nossl.zip), entzippen Sie die Datei und kopieren Sie die resultierende curl.exe entweder in ein Verzeichnis, welches sich im PATH befindet, oder in Ihr Projektverzeichnis. Sehen Sie sich das curl-Manual und mit "curl --help" die Kommandozeilenoptionen an.)

  10. Beenden Sie die Anwendung mit Strg+C.

  11. Die Ausgabe in der Webserver-Konsole protokolliert den Start von Tomcat und der Anwendung:

    ...
    ... springbootdemo.ControllerUndMain         : Starting ControllerUndMain ...
    ...
    ... s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
    ...
    ... org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.30
    ...
    ... s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/web]}" onto java.lang.String springbootdemo.ControllerUndMain.halloSpringBootWeb()
    ...
    ... s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
    ... springbootdemo.ControllerUndMain         : Started ControllerUndMain in 3.063 seconds (JVM running for 3.447)
    ...
    
  12. Sehen Sie sich die verwendeten Libs an:

    mvn dependency:tree

  13. Weiter unten wird beschrieben, wie Sie die Webanwendung mit einem JUnit-Modultest mit Mock oder alternativ mit einem Integrationstest mit embedded Webserver testen können.



Spring-Boot-Webanwendung mit Jetty

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Das Beispiel erweitert das vorherige Beispiel Spring-Boot-Webanwendung mit Tomcat.

  2. Ergänzen Sie im Projektverzeichnis in der Maven-Projektkonfigurationsdatei pom.xml bei den Dependencies folgende Dependency:

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    
  3. Führen Sie wie oben gezeigt im Kommandozeilenfenster aus:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    start http://localhost:8080/web

    curl http://localhost:8080/web

  4. Während die Ausgabe im Kommandozeilenfenster vorher den Start von Tomcat meldete, wird jetzt Jetty gestartet:

    ...
    ... springbootdemo.ControllerUndMain         : Starting ControllerUndMain ...
    ...
    ... e.j.JettyEmbeddedServletContainerFactory : Server initialized with port: 8080
    ... org.eclipse.jetty.server.Server          : jetty-9.2.14.v20151106
    ...
    ... s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/web]}" onto java.lang.String springbootdemo.ControllerUndMain.halloSpringBootWeb()
    ...
    ... o.eclipse.jetty.server.ServerConnector   : Started ServerConnector@1743526e{HTTP/1.1}{0.0.0.0:8080}
    ... .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8080 (http/1.1)
    
  5. Falls Sie sich für die Details der automatischen Spring-Konfiguration interessieren, können Sie sich mit -Ddebug den Auto-Configuration Report ansehen:

    java -Ddebug -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    =========================
    AUTO-CONFIGURATION REPORT
    =========================
    
    Positive matches:
    -----------------
    ...
       EmbeddedServletContainerAutoConfiguration.EmbeddedJetty matched
          - @ConditionalOnClass classes found: javax.servlet.Servlet,org.eclipse.jetty.server.Server,org.eclipse.jetty.util.Loader,
                                               org.eclipse.jetty.webapp.WebAppContext (OnClassCondition)
          - @ConditionalOnMissingBean (types: org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
                                       SearchStrategy: current) found no beans (OnBeanCondition)
    ...
    
    Negative matches:
    -----------------
    ...
       EmbeddedServletContainerAutoConfiguration.EmbeddedTomcat did not match
          - @ConditionalOnClass classes found: javax.servlet.Servlet,org.apache.catalina.startup.Tomcat (OnClassCondition)
          - @ConditionalOnMissingBean (types: org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
                                       SearchStrategy: current) found the following [jettyEmbeddedServletContainerFactory] (OnBeanCondition)
    ...
    


Spring-Boot mit RestController

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Für dieses Beispiel dient ein beliebiges der beiden vorherigen Beispiele Spring-Boot-Webanwendung mit Tomcat oder Spring-Boot-Webanwendung mit Jetty als Ausgangspunkt.

  2. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine RestController-Klasse hinzu: DemoRestController.java

    package springbootdemo;
    
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class DemoRestController
    {
       @RequestMapping( "/rest" )
       String halloSpringBootRest( @RequestParam( required=false, defaultValue="Spring-Boot-REST-Welt" ) String name )
       {
          return "Hallo " + name + "!";
       }
    }
    

    Sehen Sie sich die einleitenden Erläuterungen zu @RestController and @RequestMapping annotations an, und sehen Sie sich die Javadoc an zu den Annotationen @RestController, @RequestMapping und @RequestParam.

  3. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [springbootdemo]
     |               |- ControllerUndMain.java
     |               '- DemoRestController.java
     '- pom.xml
    
  4. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    Testen Sie die REST-GET-Methode mit curl:

    curl http://localhost:8080/rest

    curl http://localhost:8080/rest?name=MeinName

    Sie können die REST-GET-Methode auch per Webbrowser abfragen:

    start http://localhost:8080/rest

    start http://localhost:8080/rest?name=MeinName

  5. Sie erhalten:

    Hallo Spring-Boot-REST-Welt!
    

    bzw.

    Hallo MeinName!
    
  6. Die vorherige Funktionalität steht weiterhin zusätzlich zur Verfügung:

    curl http://localhost:8080/web

    start http://localhost:8080/web



Spring-Boot mit REST per Spring-MVC

Das folgende Beispiel demonstriert:

Allgemeine Informationen zu REST, GET, PUT und DELETE finden Sie unter RESTful Web Services mit JAX-RS und REST-konforme Verwendung von GET, PUT, POST und DELETE.

Führen Sie folgende Schritte aus:

  1. Für dieses Beispiel dient ein beliebiges der drei vorherigen Beispiele Spring-Boot-Webanwendung mit Tomcat, Spring-Boot-Webanwendung mit Jetty oder Spring-Boot mit RestController als Ausgangspunkt.

  2. Fügen Sie im src\main\java\springbootdemo-Verzeichnis einen MVC-REST-Service hinzu: DemoMvcRestService.java

    package springbootdemo;
    
    import java.util.*;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping( "/mvc-rest" )
    public class DemoMvcRestService
    {
       private List<String> lst = new ArrayList<String>();
    
       @RequestMapping( method=RequestMethod.GET )
       public List<String> getListe()
       {
          return lst;
       }
    
       @RequestMapping( value="/{idx}", method=RequestMethod.GET )
       public String getElementAusListe( @PathVariable int idx )
       {
          return ( idx < lst.size() ) ? lst.get( idx ) : null;
       }
    
       @RequestMapping( value="/{val}", method=RequestMethod.PUT )
       public void putElementZuListe( @PathVariable String val )
       {
          lst.add( val );
       }
    
       @RequestMapping( value="/{idx}", method=RequestMethod.DELETE )
       public String deleteElementInListe( @PathVariable int idx )
       {
          return ( idx < lst.size() ) ? lst.remove( idx ) : null;
       }
    }
    

    Sehen Sie sich die Erläuterungen unter Spring Web MVC, Spring MVC, JSON REST service und Spring Web MVC framework an.

  3. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F) (je nach Ausgangsprojekt können weitere Dateien vorhanden sein):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [springbootdemo]
     |               |- ControllerUndMain.java
     |               '- DemoMvcRestService.java
     '- pom.xml
    
  4. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    Testen Sie in einem zweiten Kommandozeilenfenster die REST-Methoden mit curl:

    curl --request PUT http://localhost:8080/mvc-rest/Anton

    curl --request PUT http://localhost:8080/mvc-rest/Berta

    curl --request PUT http://localhost:8080/mvc-rest/Caesar

    curl http://localhost:8080/mvc-rest

    curl http://localhost:8080/mvc-rest/1

    curl --request DELETE http://localhost:8080/mvc-rest/1

    curl http://localhost:8080/mvc-rest

    Zuletzt wird angezeigt:

    ["Anton","Caesar"]
    

    Überprüfen Sie, dass die GET-Liste-REST-Methode im JSON-Format returniert:

    curl -i http://localhost:8080/mvc-rest

    HTTP/1.1 200 OK
    ...
    Content-Type: application/json;charset=UTF-8
    ...
    
    ["Anton","Caesar"]
    

    Sie können die REST-GET-Methoden auch per Webbrowser abfragen:

    start http://localhost:8080/mvc-rest

    start http://localhost:8080/mvc-rest/1



Spring-Boot mit REST-Client

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Voraussetzung ist der REST-Service vom letzten Beispiel Spring-Boot mit REST per Spring-MVC (im Verzeichnis SpringBootWeb oder SpringBootWeb-04a-REST-Spring-MVC).

  2. Der REST-Service und der REST-Client befinden sich in getrennten Projektmodulen, da sie in der Regel auf verschiedenen Rechnern ausgeführt werden. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und legen Sie die Projektstruktur für das REST-Client-Projekt an:

    cd \MeinWorkspace

    md SpringBootWebRestClient

    cd SpringBootWebRestClient

    md src\main\java\springbootdemo

    md src\main\java\springbootdemoclient

    tree /F

  3. Erstellen Sie im neuen SpringBootWebRestClient-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootWebRestClient</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <properties>
        <java.version>1.8</java.version>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    
  4. Erzeugen Sie im src\main\java\springbootdemoclient-Verzeichnis die Application-Main-Klasse: ApplicationMain.java

    package springbootdemoclient;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    public class ApplicationMain
    {
       public static void main( String args[] )
       {
          SpringApplication.run( ApplicationMain.class );
       }
    
       @Bean
       public RestTemplate restTemplate( RestTemplateBuilder builder )
       {
          return builder.build();
       }
    
       @Bean
       public CommandLineRunner run( RestTemplate restTemplate )
       {
          return args -> {
             (new RestClient()).run( restTemplate );
          };
       }
    }
    
  5. Erzeugen Sie im src\main\java\springbootdemoclient-Verzeichnis die REST-Client-Klasse: RestClient.java

    package springbootdemoclient;
    
    import org.springframework.web.client.RestTemplate;
    import java.util.List;
    
    public class RestClient
    {
       public void run( RestTemplate restTemplate )
       {
          List<String> lst = restTemplate.getForObject( "http://localhost:8080/mvc-rest", List.class );
          System.out.println( "\n----------------------\n" + lst + "\n----------------------\n" );
       }
    }
    
  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootWebRestClient]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           |- [springbootdemo]
     |           '- [springbootdemoclient]
     |               |- ApplicationMain.java
     |               '- RestClient.java
     '- pom.xml
    
  7. Wechseln Sie in das Verzeichnis vom REST-Service-Server (SpringBootWeb oder SpringBootWeb-04a-REST-Spring-MVC), und bauen und starten Sie den REST-Service-Server:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

  8. Wechseln Sie in einem zweiten Kommandozeilenfenster in das Verzeichnis vom REST-Client (SpringBootWebRestClient oder SpringBootWeb-04b-RestClient-fuer-REST-Spring-MVC), und fügen Sie mit curl Daten zum REST-Service hinzu:

    cd \MeinWorkspace\SpringBootWebRestClient

    curl --request PUT http://localhost:8080/mvc-rest/Anton

    curl --request PUT http://localhost:8080/mvc-rest/Berta

    curl --request PUT http://localhost:8080/mvc-rest/Caesar

    curl http://localhost:8080/mvc-rest

    Zuletzt wird angezeigt:

    ["Anton","Berta","Caesar"]
    
  9. Bauen und starten Sie den neuen REST-Client:

    mvn clean package

    java -jar target/SpringBootWebRestClient-1.0-SNAPSHOT.jar

    Sie erhalten:

    ----------------------
    [Anton, Berta, Caesar]
    ----------------------
    


Spring-Boot mit REST-Client mit verschiedenen Mediatypen

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Voraussetzung ist:

  2. Zuerst wird der REST-Server im SpringBootWeb-Projektmodul um neue REST-Services erweitert.

    Fügen Sie im SpringBootWeb\src\main\java\springbootdemo-Verzeichnis der REST-Service-Klasse DemoMvcRestService.java folgende vier GET-Methoden hinzu:

       @RequestMapping( path="ext", method=RequestMethod.GET, produces="application/json;charset=UTF-8" )
       public ElementeListeObjekt getElementeListeObjekt()
       {
          ElementeListeObjekt elo = new ElementeListeObjekt();
          elo.setLst( lst );
          return elo;
       }
    
       @RequestMapping( path="ext", method=RequestMethod.GET, produces="text/plain;charset=UTF-8" )
       public String getListeText()
       {
          return "" + lst;
       }
    
       @RequestMapping( path="ext", method=RequestMethod.GET, produces="text/html;charset=UTF-8" )
       public String getListeHtml()
       {
          int i = 0;
          String ret = "";
          for( String s : lst ) {
             ret += "  <tr><td>" + i++ + ":</td><td>" + s + "</td></tr>\n";
          }
          return "<html><body><table>\n" + ret + "</table></body></html>\n";
       }
    
       @RequestMapping( path="ext", method=RequestMethod.GET, produces="application/xml;charset=UTF-8" )
       public String getListeXml()
       {
          String ret = "";
          for( String s : lst ) {
             ret += "  <element>" + s + "</element>\n";
          }
          return "<ElementeListeObjekt>\n" + ret + "</ElementeListeObjekt>\n";
       }
    
  3. Der neue REST-Client ruft die neuen REST-Services auf.

    Ersetzen Sie im im SpringBootWebRestClient-Projektmodul im SpringBootWebRestClient\src\main\java\springbootdemoclient-Verzeichnis den Inhalt der REST-Client-Klasse RestClient.java durch:

    package springbootdemoclient;
    
    import org.springframework.http.*;
    import org.springframework.web.client.RestTemplate;
    import java.util.*;
    import springbootdemo.ElementeListeObjekt;
    
    public class RestClient
    {
       static final String REST_URL_BASIS = "http://localhost:8080/mvc-rest";
       static final String REST_URL_EXT   = REST_URL_BASIS + "/ext";
    
       public void run( RestTemplate restTemplate )
       {
          List<String> lst = restTemplate.getForObject( REST_URL_BASIS, List.class );
          System.out.println( "\n---------------------- JSON-Liste:\n" + lst + "\n----------------------\n" );
    
          ElementeListeObjekt elo = restTemplate.getForObject( REST_URL_EXT, ElementeListeObjekt.class );
          System.out.println( "---------------------- Liste aus JSON-Objekt:\n" + elo.getLst() + "\n----------------------\n" );
    
          HttpHeaders headers = new HttpHeaders();
          headers.setAccept( Arrays.asList( MediaType.TEXT_PLAIN ) );
          HttpEntity<String> httpEntityText = new HttpEntity<String>( "parameters", headers );
          ResponseEntity<String> resultText = restTemplate.exchange( REST_URL_EXT, HttpMethod.GET, httpEntityText, String.class );
          System.out.println( "---------------------- Plain-Text:\n" + resultText.getBody() + "\n----------------------\n" );
    
          headers.setAccept( Arrays.asList( MediaType.TEXT_HTML ) );
          HttpEntity<String> httpEntityHtml = new HttpEntity<String>( "parameters", headers );
          ResponseEntity<String> resultHtml = restTemplate.exchange( REST_URL_EXT, HttpMethod.GET, httpEntityHtml, String.class );
          System.out.println( "---------------------- HTML:\n" + resultHtml.getBody() + "----------------------\n" );
    
          headers.setAccept( Arrays.asList( MediaType.APPLICATION_XML ) );
          HttpEntity<String> httpEntityXml = new HttpEntity<String>( "parameters", headers );
          ResponseEntity<String> resultXml = restTemplate.exchange( REST_URL_EXT, HttpMethod.GET, httpEntityXml, String.class );
          System.out.println( "---------------------- XML:\n" + resultXml.getBody() + "----------------------\n" );
       }
    }
    
  4. Folgende Entity-Klasse ElementeListeObjekt.java wird sowohl vom REST-Service-Server als auch vom REST-Client benötigt. Kopieren Sie die Klasse sowohl in das SpringBootWeb\src\main\java\springbootdemo-Verzeichnis als auch in das SpringBootWebRestClient\src\main\java\springbootdemo-Verzeichnis.

    package springbootdemo;
    
    import java.util.ArrayList;
    import java.util.List;
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class ElementeListeObjekt
    {
       List<String> lst = new ArrayList<String>();
    
       public List<String> getLst() {
          return lst;
       }
    
       public void setLst( List<String> lst ) {
          this.lst = lst;
       }
    }
    
  5. Wechseln Sie in das Verzeichnis vom REST-Service-Server (SpringBootWeb), und bauen und starten Sie den REST-Service-Server:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

  6. Wechseln Sie in einem zweiten Kommandozeilenfenster in das Verzeichnis vom REST-Client (SpringBootWebRestClient oder SpringBootWeb-04b-RestClient-fuer-REST-Spring-MVC), und fügen Sie mit curl Daten zum REST-Service hinzu:

    cd \MeinWorkspace\SpringBootWebRestClient

    curl --request PUT http://localhost:8080/mvc-rest/Anton

    curl --request PUT http://localhost:8080/mvc-rest/Berta

    curl --request PUT http://localhost:8080/mvc-rest/Caesar

    curl http://localhost:8080/mvc-rest

    Zuletzt wird angezeigt:

    ["Anton","Berta","Caesar"]
    
  7. Testen Sie die neuen REST-Services mit curl und überprüfen Sie dabei jeweils den Content-Type:

    curl -i http://localhost:8080/mvc-rest

    curl -i http://localhost:8080/mvc-rest/ext

    curl -i -H "Accept:text/plain" http://localhost:8080/mvc-rest/ext

    curl -i -H "Accept:text/html" http://localhost:8080/mvc-rest/ext

    curl -i -H "Accept:application/xml" http://localhost:8080/mvc-rest/ext

    Sie erhalten (gekürzt);

    Content-Type: application/json
    ["Anton","Berta","Caesar"]
    
    Content-Type: application/json
    {"lst":["Anton","Berta","Caesar"]}
    
    Content-Type: text/plain
    [Anton, Berta, Caesar]
    
    Content-Type: text/html
    <html><body><table>
      <tr><td>0:</td><td>Anton</td></tr>
      <tr><td>1:</td><td>Berta</td></tr>
      <tr><td>2:</td><td>Caesar</td></tr>
    </table></body></html>
    
    Content-Type: application/xml
    <ElementeListeObjekt>
      <element>Anton</element>
      <element>Berta</element>
      <element>Caesar</element>
    </ElementeListeObjekt>
    
  8. Bauen und starten Sie den neuen REST-Client:

    mvn clean package

    java -jar target/SpringBootWebRestClient-1.0-SNAPSHOT.jar

    Sie erhalten:

    ---------------------- JSON-Liste:
    [Anton, Berta, Caesar]
    ----------------------
    
    ---------------------- Liste aus JSON-Objekt:
    [Anton, Berta, Caesar]
    ----------------------
    
    ---------------------- Plain-Text:
    [Anton, Berta, Caesar]
    ----------------------
    
    ---------------------- HTML:
    <html><body><table>
      <tr><td>0:</td><td>Anton</td></tr>
      <tr><td>1:</td><td>Berta</td></tr>
      <tr><td>2:</td><td>Caesar</td></tr>
    </table></body></html>
    ----------------------
    
    ---------------------- XML:
    <ElementeListeObjekt>
      <element>Anton</element>
      <element>Berta</element>
      <element>Caesar</element>
    </ElementeListeObjekt>
    ----------------------
    


Spring-Boot mit REST-Client mit JsonObject

Das folgende Beispiel demonstriert:

Unter JSON-REST-Client mit HttpURLConnection und JsonObject finden Sie sehr ähnliche Beispiele, allerdings wird dort nicht das Spring RestTemplate verwendet, sondern stattdessen HttpURLConnection.

Führen Sie folgende Schritte aus:

  1. Voraussetzung ist einer der beiden REST-Clients der beiden letzten Beispiele im Verzeichnis SpringBootWebRestClient, also entweder Spring-Boot mit REST-Client, oder Spring-Boot mit REST-Client mit verschiedenen Mediatypen.

  2. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und kopieren Sie einen der beiden REST-Clients in ein neues Projekt:

    cd \MeinWorkspace

    xcopy SpringBootWebRestClient SpringBootWebRestClientJson\ /S

    cd SpringBootWebRestClientJson

    tree /F

  3. Fügen Sie im neuen SpringBootWebRestClientJson-Projektverzeichnis in der pom.xml folgende Dependency hinzu:

        <dependency>
          <groupId>org.glassfish</groupId>
          <artifactId>javax.json</artifactId>
          <version>1.1</version>
        </dependency>
    
  4. Ersetzen Sie im src\main\java\springbootdemoclient-Verzeichnis den Inhalt von RestClient.java durch:

    package springbootdemoclient;
    
    import org.springframework.web.client.RestTemplate;
    import javax.json.*;
    import java.io.StringReader;
    
    /**
     * JSON-REST-Client fuer den Bitcoin-Kurs, Powered by CoinDesk,
     * {@link "http://www.coindesk.com/api/"}
     * {@link "http://www.coindesk.com/price/"}
     * {@link "http://api.coindesk.com/v1/bpi/currentprice.json"}
     */
    public class RestClient
    {
       public void run( RestTemplate restTemplate )
       {
          String jsonStr = restTemplate.getForObject( "http://api.coindesk.com/v1/bpi/currentprice.json", String.class );
          JsonObject jsonObj = null;
          try( JsonReader jsonRdr = Json.createReader( new StringReader( jsonStr ) ) ) {
             jsonObj = jsonRdr.readObject();
          }
    
          System.out.println( "\n\n------------ Ausgabe aller Root-Key/Values:\n" );
          jsonObj.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ausgabe aller Key/Values zu 'time':\n" );
          JsonObject time = jsonObj.getJsonObject( "time" );
          time.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ausgabe aller Key/Values zu 'bpi':\n" );
          JsonObject bpi = jsonObj.getJsonObject( "bpi" );
          bpi.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ausgabe aller Key/Values zu 'bpi.EUR':\n" );
          JsonObject bpiEur = bpi.getJsonObject( "EUR" );
          bpiEur.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ermittlung einzelner Elemente:\n" );
          String     zeitpunkt  = time.getString( "updatedISO" );
          String     name       = jsonObj.getString( "chartName" );
          String     bpiEurCode = bpiEur.getString( "code" );
          JsonNumber bpiEurRate = bpiEur.getJsonNumber( "rate_float" );
          System.out.println( "Ein " + name + " kostet " + bpiEurRate + " " + bpiEurCode + " (" + zeitpunkt.replace( 'T', ' ' ) + ")." );
    
          System.out.println( "\n-------------------------------------------------------------\n\n" );
       }
    }
    
  5. Bauen und starten Sie den neuen REST-Client:

    mvn clean package

    java -jar target/SpringBootWebRestClient-1.0-SNAPSHOT.jar

    Sie erhalten (gekürzt):

    ------------ Ausgabe aller Root-Key/Values:
    
    key=time,       val={"updated": ...
    key=disclaimer, val=" ...
    key=chartName,  val="Bitcoin"
    key=bpi,        val={ ...
    
    ------------ Ausgabe aller Key/Values zu 'time':
    
    key=updated,    val=...
    key=updatedISO, val=...
    
    ------------ Ausgabe aller Key/Values zu 'bpi':
    
    key=USD, val={"code":"USD",...
    key=GBP, val={"code":"GBP",...
    key=EUR, val={"code":"EUR",...
    
    ------------ Ausgabe aller Key/Values zu 'bpi.EUR':
    
    key=code,        val="EUR"
    key=symbol,      val="&euro;"
    key=rate,        val="1,961.1835"
    key=description, val="Euro"
    key=rate_float,  val=1961.1835
    
    ------------ Ermittlung einzelner Elemente:
    
    Ein Bitcoin kostet 1961.1835 EUR (2017-07-19 20:00:00+00:00).
    
    -------------------------------------------------------------
    
  6. Da der Umgang mit JsonArray etwas anders erfolgt, folgt ein zweites Beispiel. Ersetzen Sie im src\main\java\springbootdemoclient-Verzeichnis den Inhalt von RestClient.java durch:

    package springbootdemoclient;
    
    import org.springframework.web.client.RestTemplate;
    import javax.json.*;
    import java.io.StringReader;
    
    /**
     * JSON-REST-Client fuer Laenderkuerzel,
     * {@link "http://www.groupkt.com/post/f2129b88/free-restful-web-services-to-consume-and-test.htm"}
     * {@link "http://www.groupkt.com/post/c9b0ccb9/country-and-other-related-rest-webservices.htm"}
     * {@link "http://services.groupkt.com/country/get/all"}
     */
    public class RestClient
    {
       public void run( RestTemplate restTemplate )
       {
          String jsonStr = restTemplate.getForObject( "http://services.groupkt.com/country/get/all", String.class );
          JsonObject jsonObj = null;
          try( JsonReader jsonRdr = Json.createReader( new StringReader( jsonStr ) ) ) {
             jsonObj = jsonRdr.readObject();
          }
    
          System.out.println( "\n\n------------ Ausgabe aller Root-Key/Values:\n" );
          jsonObj.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ausgabe aller Key/Values zu 'RestResponse':\n" );
          JsonObject restResponse = jsonObj.getJsonObject( "RestResponse" );
          restResponse.entrySet().forEach( e -> System.out.println( "key=" + e.getKey() + ", val=" + e.getValue() + "\n" ) );
    
          System.out.println( "\n------------ Ausgabe der Inhalte aller Elemente des 'RestResponse.result'-Arrays:\n" );
          JsonArray result = restResponse.getJsonArray( "result" );
          result.getValuesAs( JsonObject.class ).forEach(
                e -> System.out.println( e.getString( "alpha2_code" ) + " / " +
                                         e.getString( "alpha3_code" ) + " : " + e.getString( "name" ) ) );
    
          System.out.println( "\n-------------------------------------------------------------\n\n" );
       }
    }
    
  7. Bauen und starten Sie den geänderten REST-Client:

    mvn clean package

    java -jar target/SpringBootWebRestClient-1.0-SNAPSHOT.jar

    Sie erhalten (gekürzt):

    ------------ Ausgabe aller Root-Key/Values:
    
    key=RestResponse, val={ ...
    
    ------------ Ausgabe aller Key/Values zu 'RestResponse':
    
    key=messages, val=[" ...
    key=result, val=[{ ...
    
    ------------ Ausgabe der Inhalte aller Elemente des 'RestResponse.result'-Arrays:
    
    AF / AFG : Afghanistan
    AX / ALA : Åland Islands
    ...
    DE / DEU : Germany
    ...
    ZW / ZWE : Zimbabwe
    
    -------------------------------------------------------------
    


Spring-Boot mit REST per JAX-RS

Das folgende Beispiel demonstriert:

Informationen zu JAX-RS, REST, GET, PUT und DELETE finden Sie unter RESTful Web Services mit JAX-RS und REST-konforme Verwendung von GET, PUT, POST und DELETE.

Führen Sie folgende Schritte aus:

  1. Um Verwirrung zwischen REST per Spring-MVC und REST per JAX-RS zu vermeiden, starten wir ein neues Projekt.
    Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootJaxRs

    cd SpringBootJaxRs

    md src\main\java\springbootdemo

    tree /F

  2. Erstellen Sie im SpringBootJaxRs-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootJaxRs</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    
  3. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine SpringBootApplication-Klasse hinzu: ApplicationMain.java

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ApplicationMain
    {
       public static void main( String[] args )
       {
          SpringApplication.run( ApplicationMain.class, args );
       }
    }
    
  4. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Spring-ResourceConfig-Klasse hinzu: JerseyConfig.java

    package springbootdemo;
    
    import org.glassfish.jersey.server.ResourceConfig;
    import org.springframework.stereotype.Component;
    
    @Component
    public class JerseyConfig extends ResourceConfig
    {
       public JerseyConfig()
       {
          register( DemoJaxRsRestService.class );
       }
    }
    
  5. Fügen Sie im src\main\java\springbootdemo-Verzeichnis einen JAX-RS-REST-Service hinzu: DemoJaxRsRestService.java

    package springbootdemo;
    
    import java.util.*;
    import javax.ws.rs.*;
    import javax.ws.rs.core.MediaType;
    import org.springframework.stereotype.Component;
    
    @Component
    @Path( "/jaxrs-rest" )
    @Produces( MediaType.APPLICATION_JSON )
    public class DemoJaxRsRestService
    {
       private List<String> lst = new ArrayList<String>();
    
       @GET
       public List<String> getListe()
       {
          return lst;
       }
    
       @GET @Path("{idx}")
       public String getElementAusListe( @PathParam("idx") int idx )
       {
          return ( idx < lst.size() ) ? lst.get( idx ) : null;
       }
    
       @PUT @Path("{val}")
       public void putElementZuListe( @PathParam("val") String val )
       {
          lst.add( val );
       }
    
       @DELETE @Path("{idx}")
       public String deleteElementInListe( @PathParam("idx") int idx )
       {
          return ( idx < lst.size() ) ? lst.remove( idx ) : null;
       }
    }
    

    Sehen Sie sich die Erläuterungen unter RESTful Web Services mit JAX-RS und JAX-RS and Jersey an.

  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootJaxRs]
     |- [src]
     |   '- [main]
     |       '- [java]
     |           '- [springbootdemo]
     |               |- ApplicationMain.java
     |               |- DemoJaxRsRestService.java
     |               '- JerseyConfig.java
     '- pom.xml
    
  7. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootJaxRs-1.0-SNAPSHOT.jar

    Testen Sie die REST-Methoden mit curl:

    curl --request PUT http://localhost:8080/jaxrs-rest/Anton

    curl --request PUT http://localhost:8080/jaxrs-rest/Berta

    curl --request PUT http://localhost:8080/jaxrs-rest/Caesar

    curl http://localhost:8080/jaxrs-rest

    curl http://localhost:8080/jaxrs-rest/1

    curl --request DELETE http://localhost:8080/jaxrs-rest/1

    curl http://localhost:8080/jaxrs-rest

    Zuletzt wird angezeigt:

    ["Anton","Caesar"]
    

    Überprüfen Sie, dass die GET-Liste-REST-Methode im JSON-Format returniert:

    curl -i http://localhost:8080/jaxrs-rest

    HTTP/1.1 200 OK
    ...
    Content-Type: application/json;charset=UTF-8
    ...
    
    ["Anton","Caesar"]
    

    Sie können die REST-GET-Methoden auch per Webbrowser abfragen:

    start http://localhost:8080/jaxrs-rest

    start http://localhost:8080/jaxrs-rest/1



Spring-Boot-Webanwendung mit JavaServer Faces (JSF)

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. JDK und Maven müssen installiert sein.

  2. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootJsf

    cd SpringBootJsf

    md src\main\java\springbootdemo

    md src\main\resources\META-INF

    md src\main\webapp

    tree /F

  3. Erstellen Sie im SpringBootJsf-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootJsf</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <properties>
        <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
        <java.version>1.8</java.version>
      </properties>
      <dependencies>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-api</artifactId>
          <version>2.2.20</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-impl</artifactId>
          <version>2.2.20</version>
          <scope>compile</scope>
          <optional>true</optional>
        </dependency>
        <dependency>
          <groupId>org.apache.tomcat.embed</groupId>
          <artifactId>tomcat-embed-jasper</artifactId>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    

    Sehen Sie sich zur JSF-Implementierung und zum JSF-API an: Mojarra JavaServer Faces, JavaServer Faces API und Java EE 7 APIs.

  4. Erzeugen Sie im META-INF-Verzeichnis src\main\resources\META-INF die JSF-Konfigurationsdatei: faces-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
                  version="2.2">
      <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> 
      </application>
      <lifecycle>
        <phase-listener>org.springframework.web.jsf.DelegatingPhaseListenerMulticaster</phase-listener>
      </lifecycle>
    </faces-config>
    

    Sehen Sie sich hierzu an: Application Configuration Resource File, Expression Language (EL), SpringBeanFacesELResolver und DelegatingPhaseListenerMulticaster.

  5. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine SpringBootApplication-Main-Klasse hinzu: ApplicationMain.java

    package springbootdemo;
    
    import javax.servlet.*;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletContextInitializer;
    import org.springframework.context.annotation.*;
    
    @SpringBootApplication
    public class ApplicationMain
    {
      public static void main( String[] args )
      {
        SpringApplication.run( ApplicationMain.class, args );
      }
    
      @Configuration
      static class ConfigureJsfContextParameters implements ServletContextInitializer
      {
        @Override
        public void onStartup( ServletContext servletContext ) throws ServletException
        {
          servletContext.setInitParameter( "facelets.DEVELOPMENT",                    "false"      );
          servletContext.setInitParameter( "javax.faces.PROJECT_STAGE",               "Production" );
          servletContext.setInitParameter( "javax.faces.DEFAULT_SUFFIX",              ".xhtml"     );
          servletContext.setInitParameter( "javax.faces.PARTIAL_STATE_SAVING_METHOD", "true"       );
          servletContext.setInitParameter( "javax.faces.FACELETS_REFRESH_PERIOD",     "-1"         );
        }
      }
    }
    

    Sehen Sie sich hierzu die Javadoc zum ServletContextInitializer und ServletContext an, sowie zu den Parametern das Kapitel "11.1.3 Application Configuration Parameters" in der JavaServer Faces Specification.

  6. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine ServletInitializer-Klasse hinzu: ServletInitializer.java

    package springbootdemo;
    
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
    
    public class ServletInitializer extends SpringBootServletInitializer
    {
      @Override
      protected SpringApplicationBuilder configure( SpringApplicationBuilder application )
      {
        return application.sources( ApplicationMain.class );
      }
    }
    

    Sehen Sie sich die Javadocs an zu: SpringBootServletInitializer und SpringApplicationBuilder.

  7. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Faces-Servlet-Registration-Klasse hinzu: ConfigureJsfServletRegistration.java

    package springbootdemo;
    
    import java.util.*;
    import javax.servlet.*;
    import org.springframework.boot.web.servlet.ServletContextInitializer;
    import org.springframework.context.annotation.*;
    import com.sun.faces.config.FacesInitializer;
    
    @Configuration
    public class ConfigureJsfServletRegistration
    {
      @Bean
      public ServletContextInitializer facesServletRegistration()
      {
        return new JsfServletContextInitializer();
      }
    
      public class JsfServletContextInitializer extends ServletContextInitializer
      {
        public JsfServletContextInitializer()
        {
          super();
        }
    
        @Override
        public void onStartup( ServletContext servletContext ) throws ServletException
        {
          FacesInitializer facesInitializer = new FacesInitializer();
          Set<Class<?>> clazz = new HashSet<Class<?>>();
          clazz.add( ConfigureJsfServletRegistration.class );
          facesInitializer.onStartup( clazz, servletContext );
        }
      }
    }
    

    Sehen Sie sich die Javadocs an zu: ServletContextInitializer und ServletContext, sowie zum FacesInitializer-Parent-Interface ServletContainerInitializer.

  8. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine JSF-Backing-Bean-Klasse hinzu: JsfBackingBean.java

    package springbootdemo;
    
    import javax.annotation.ManagedBean;
    import javax.faces.bean.RequestScoped;
    
    @ManagedBean
    @RequestScoped
    public class JsfBackingBean
    {
      public String getMessageFromBackingBean()
      {
        return "Hallo JSF mit Spring Boot";
      }
    }
    

    Sehen Sie sich hierzu an: Managed Bean / Backing Bean, JSF-Scopes, ManagedBean und RequestScoped.

  9. Fügen Sie im src\main\webapp-Verzeichnis eine JSF-Facelets-XHTML-Seite hinzu: index.xhtml

    <!DOCTYPE html>
    <f:view xmlns="http://www.w3.org/1999/xhtml"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:h="http://xmlns.jcp.org/jsf/html"
            xmlns:f="http://xmlns.jcp.org/jsf/core"
            xmlns:jsf="http://xmlns.jcp.org/jsf"
            encoding="UTF-8">
      <html>
        <head jsf:id="head" />
        <body jsf:id="body">
          <h1>Spring-Boot-JSF-Demo</h1>
          <h3>#{jsfBackingBean.messageFromBackingBean}</h3>
        </body>
      </html>
    </f:view>
    

    Sehen Sie sich die Erläuterungen an unter Facelets, Expression Language (EL) und Java EE Expression Language.

  10. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootJsf]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       |- ApplicationMain.java
     |       |       |- ConfigureJsfServletRegistration.java
     |       |       |- JsfBackingBean.java
     |       |       '- ServletInitializer.java
     |       |- [resources]
     |       |   '- [META-INF]
     |       |       '- faces-config.xml
     |       '- [webapp]
     |           '- index.xhtml
     '- pom.xml
    
  11. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootJsf-1.0-SNAPSHOT.jar

    start http://localhost:8080/index.jsf

  12. Die Webseite zeigt an:

    Spring-Boot-JSF-Demo
    Hallo JSF mit Spring Boot
    


Spring-Boot-Webanwendung mit MVC und Thymeleaf

Das folgende Beispiel demonstriert:

Falls Sie Spring-MVC ohne Spring Boot verwenden wollen, sehen Sie sich auch das sehr ähnliche Beispiel Web-Demo mit MVC und Thymeleaf an.

Das Spring Web MVC Framework wurde bereits im Beispiel Spring-Boot mit REST per Spring-MVC eingesetzt. Es ist ein Model-View-Controller-Webframework basierend auf einem DispatcherServlet, welches konfigurierbar Requests zu Handlern weiterleitet.

Thymeleaf ist eine Template-Engine und verwendet eine eigene DOM-Implementierung, um das eingelesene Template als DOM-Tree zu laden und dynamische Inhalte zu ersetzen. Thymeleaf verwendet keine eigenen Tags und keinen Inline-Code, sondern stattdessen spezielle Thymeleaf-Attribute mit eigenem Namespace in den HTML-Tags. Dadurch kann das Design der HTML-Seite unabhängig vom Programmcode erstellt werden, Designer und Programmierer können unabhängig voneinander arbeiten. Die Expression Language von Thymeleaf basiert normalerweise auf der Object-Graph-Navigation Language (OGNL). Aber in Spring-Anwendungen wird SpEL verwendet. Es gibt vier Typen von Expressions:

Führen Sie folgende Schritte aus:

  1. JDK und Maven müssen installiert sein.

  2. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootThymeleaf

    cd SpringBootThymeleaf

    md src\main\java\springbootdemo

    md src\main\resources\static

    md src\main\resources\templates

    tree /F

  3. Erstellen Sie im SpringBootThymeleaf-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootThymeleaf</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    
  4. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die schon bekannte Main-Klasse: ApplicationMain.java

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ApplicationMain
    {
       public static void main( String[] args )
       {
          SpringApplication.run( ApplicationMain.class, args );
       }
    }
    
  5. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die ebenfalls schon bekannte JPA-Entity-Klasse: MeineEntity.java

    package springbootdemo;
    
    import java.util.Date;
    import javax.persistence.*;
    
    @Entity
    public class MeineEntity
    {
       @Id @GeneratedValue
       private Long   id;
       private Date   datum;
       private String text;
    
       public Long   getId()    { return id;    }
       public Date   getDatum() { return datum; }
       public String getText()  { return text;  }
       public void setId(    Long id     ) { this.id    = id;    }
       public void setDatum( Date datum  ) { this.datum = datum; }
       public void setText(  String text ) { this.text  = text;  }
    
       @Override
       public String toString()
       {
          return "[id=" + id + ", datum=" + datum + ", text=" + text + "]";
       }
    }
    

    Erläuterungen hierzu finden Sie hier.

  6. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die auch schon fast identisch bekannte MeineEntity-JPA-Repository-Klasse: MeineEntityRepository.java

    package springbootdemo;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface MeineEntityRepository extends JpaRepository<MeineEntity,Long>
    {
       public MeineEntity findByText( String text );
    }
    

    Anders als in der oben verwendeten MeineEntityRepository.java wird diesmal nicht CrudRepository erweitert, sondern stattdessen JpaRepository, damit findAll() nicht einen Iterable, sondern eine List returniert.

  7. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die Controller-Klasse: MeineEntityController.java

    package springbootdemo;
    
    import java.util.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    
    @Controller
    @RequestMapping( "/mvc-th" )
    public class MeineEntityController
    {
       @Autowired
       private MeineEntityRepository repo;
    
       @RequestMapping( method=RequestMethod.POST )
       public String addToListe( MeineEntity newEntity )
       {
          newEntity.setDatum( new Date() );
          repo.save( newEntity );
          return "redirect:/mvc-th";
       }
    
       @RequestMapping( method=RequestMethod.GET )
       public String getListe( Model model )
       {
          model.addAttribute( "entitiesListe", repo.findAll() );
          return "MeineEntityWebseite";
       }
    }
    

    Erläuterungen zu den REST-Annotationen finden Sie hier.
    Beachten Sie, dass die REST-Methoden praktischerweise MeineEntity- und Model-Objekte als Parameter erhalten, was sehr einfache Implementierungen ermöglicht.

  8. Erzeugen Sie im Ressourcen-Verzeichnis src\main\resources die bekannte Konfigurationsdatei: application.properties

    spring.datasource.url         = jdbc:h2:./target/h2-db;DB_CLOSE_ON_EXIT=FALSE
    spring.datasource.username    = sa
    spring.datasource.password    =
    spring.jpa.hibernate.ddl-auto = update
    

    Erläuterungen hierzu finden Sie hier.
    Natürlich können Sie auch andere Datenbanken verwenden, siehe hier.

  9. Erzeugen Sie im src\main\resources\static-Verzeichnis die CSS-Datei: style.css

    body          { font-family: arial,helvetica,sans-serif; }
    table, th, td { border: 1px solid black;
                    border-collapse: collapse;
                    padding: 5px;
                    text-align: left; }
    th            { background-color: #eeeeee }
    
  10. Erzeugen Sie im src\main\resources\templates-Verzeichnis die Thymeleaf-Template-Datei für das Web-GUI: MeineEntityWebseite.html

    <html xmlns:th="http://www.thymeleaf.org">
      <head>
        <title>Spring-Boot-MVC-Thymeleaf-Webanwendung</title>
        <link rel="stylesheet" th:href="@{/style.css}" />
      </head>
      <body onload='document.f.text.focus();'>
        <hr />
        <h2>Spring-Boot-MVC-Thymeleaf-Webanwendung</h2>
        <hr />
        <h3>Erstelle neues Datenelement</h3>
        <form name='f' method="POST">
          <input type="hidden" th:if="${_csrf}" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
          <label for="text">Text des neuen Datenelements:</label>
          <input type="text" name="text" size="30" />
          <input type="submit" />
        </form>
        <hr />
        <h3>Gespeicherte Datenelemente</h3>
        <div th:if="${#lists.isEmpty( entitiesListe )}"><p>Keine Datenelemente vorhanden.</p></div>
        <div th:unless="${#lists.isEmpty( entitiesListe )}">
          <table>
            <tr>
              <th>Id</th>
              <th>Datum</th>
              <th>Text</th>
            </tr>
            <tr th:each="me : ${entitiesListe}">
              <td th:text="${me.id}">Id</td>
              <td th:text="${me.datum}">Datum</td>
              <td th:text="${me.text}">Text</td>
            </tr>
          </table>
        </div>
        <hr />
      </body>
    </html>
    

    Sehen Sie sich hierzu an: Tutorial: Using Thymeleaf, Tutorial: Thymeleaf + Spring, Conditionals: “if” and “unless”, Iteration, Using th:each.
    Beachten Sie folgende magischen Vereinfachungen:
    Das HTML-Formular enthält ein Textfeld, woraus automatisch ein MeineEntity-Objekt für die addToListe(MeineEntity)-REST-Methode erstellt wird,
    und die im Model gespeicherte entitiesListe kann direkt zur Darstellung in der Tabelle verwendet werden.

  11. Erzeugen Sie im src\main\resources\templates-Verzeichnis die Thymeleaf-Template-Datei für den Fehlerfall: error.html

    <html xmlns:th="http://www.thymeleaf.org">
      <head>
        <title>Oops!</title>
        <link rel="stylesheet" th:href="@{/style.css}"></link>
      </head>
    
      <html>
        <h1>Oops!</h1>
        <img th:src="@{/MissingPage.png}" />
        <p>Es gibt ein Problem mit der angefragten Webseite: <span th:text="${path}" /></p>
        <p th:text="${'Fehler:    ' + status + ', ' + error + ', ' + exception}" />
        <p th:text="${'Meldung:   ' + message}" />
        <p th:text="${'Zeitpunkt: ' + #dates.format( timestamp, 'yyyy-MM-dd HH:mm:ss' )}" />
      </html>
    
    </html>
    
  12. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootThymeleaf]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       |- ApplicationMain.java
     |       |       |- MeineEntity.java
     |       |       |- MeineEntityController.java
     |       |       '- MeineEntityRepository.java
     |       '- [resources]
     |           |- [static]
     |           |   '- style.css
     |           |- [templates]
     |           |   |- error.html
     |           |   '- MeineEntityWebseite.html
     |           '- application.properties
     '- pom.xml
    
  13. Führen Sie im Kommandozeilenfenster aus:

    mvn package

    java -jar target/SpringBootThymeleaf-1.0-SNAPSHOT.jar

    start http://localhost:8080/mvc-th

  14. Tragen Sie auf der Webseite mehrmals Text ein und speichern Sie.
    Die Webseite zeigt beispielsweise an:

  15. Da die H2-DB so konfiguriert ist, dass sie ihre Daten in einer Datei im target-Verzeichnis speichert, bleiben die Daten so lange persistent, bis Sie das target-Verzeichnis löschen, z.B. per mvn clean.



JUnit-Modultest mit Mock für Spring-Boot-Webanwendung

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Für die erste Testklasse in diesem Beispiel kann ein beliebiges der vier "SpringBootWeb"-Beispiele Spring-Boot-Webanwendung mit Tomcat, Spring-Boot-Webanwendung mit Jetty, Spring-Boot mit RestController oder Spring-Boot mit REST per Spring-MVC als Ausgangspunkt verwendet werden. Wenn Sie auch die zweite Testklasse testen wollen, müssen Sie Spring-Boot mit REST per Spring-MVC als Ausgangspunkt verwenden.

  2. Erzeugen Sie den Test-Verzeichnisbaum:

    cd \MeinWorkspace\SpringBootWeb

    md src\test\java\springbootdemo

    tree /F

  3. Ergänzen Sie im Projektverzeichnis in der Maven-Projektkonfigurationsdatei pom.xml bei den Dependencies folgende Dependency:

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
    
  4. Fügen Sie im src\test\java\springbootdemo-Testverzeichnis einen JUnit-Test hinzu: ControllerUndMainTest.java

    package springbootdemo;
    
    import static org.hamcrest.Matchers.equalTo;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    
    @RunWith( SpringRunner.class )
    @SpringBootTest( webEnvironment = WebEnvironment.MOCK )
    public class ControllerUndMainTest
    {
       private MockMvc mockMvc;
    
       @Before
       public void setupMockMvc()
       {
          mockMvc = MockMvcBuilders.standaloneSetup( new ControllerUndMain() ).build();
       }
    
       @Test
       public void testHalloString() throws Exception
       {
          mockMvc.perform( MockMvcRequestBuilders.get( "/web" ).accept( MediaType.APPLICATION_JSON ) )
                 .andExpect( status().isOk() )
                 .andExpect( content().contentType( MediaType.APPLICATION_JSON + ";charset=UTF-8" ) )
                 .andExpect( content().string( equalTo( "Hallo Spring-Boot-Web-Welt!" ) ) );
       }
    }
    

    Sehen Sie sich die Javadoc an zu den Klassen, Annotationen und Parametern: @RunWith, SpringRunner, @SpringBootTest, WebEnvironment.MOCK, MockMvc, @Before, MockMvcBuilders, @Test, MockMvcRequestBuilders und MockMvcResultMatchers.

  5. Falls Sie als Ausgangsprojekt Spring-Boot mit REST per Spring-MVC gewählt haben, sollten Sie noch einen weiteren Test hinzufügen: DemoMvcRestServiceTest.java

    package springbootdemo;
    
    import static org.hamcrest.Matchers.equalTo;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    import org.junit.*;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @RunWith( SpringRunner.class )
    @SpringBootTest( webEnvironment = WebEnvironment.MOCK )
    public class DemoMvcRestServiceTest
    {
       @Autowired
       private WebApplicationContext webContext;
    
       private MockMvc mockMvc;
    
       @Before
       public void setupMockMvc()
       {
          mockMvc = MockMvcBuilders.webAppContextSetup( webContext ).build();
       }
    
       @Test
       public void testPutDeleteGet() throws Exception
       {
          // "Anton" hinzufuegen:
          mockMvc.perform( put( "/mvc-rest/Anton" ).contentType( MediaType.APPLICATION_JSON ) )
                 .andExpect( status().isOk() );
          // "Berta" hinzufuegen:
          mockMvc.perform( put( "/mvc-rest/Berta" ).contentType( MediaType.APPLICATION_JSON ) )
                 .andExpect( status().isOk() );
          // "Anton" und "Berta" auslesen:
          mockMvc.perform( get( "/mvc-rest" ).accept( MediaType.APPLICATION_JSON ) )
                 .andExpect( status().isOk() )
                 .andExpect( content().contentType( MediaType.APPLICATION_JSON ) )
                 .andExpect( content().string( equalTo( "[\"Anton\",\"Berta\"]" ) ) );
          // "Berta" loeschen:
          mockMvc.perform( delete( "/mvc-rest/1" ) )
                 .andExpect( status().isOk() );
          // Nach Loeschung ist nur noch "Anton" vorhanden:
          mockMvc.perform( get( "/mvc-rest" ).accept( MediaType.APPLICATION_JSON ) )
                 .andExpect( status().isOk() )
                 .andExpect( content().string( equalTo( "[\"Anton\"]" ) ) );
       }
    }
    

    Beachten Sie, dass anders als bei der vorherigen Testklasse ControllerUndMainTest diesmal "mockMvc = MockMvcBuilders.webAppContextSetup()" statt "mockMvc = MockMvcBuilders.standaloneSetup()" verwendet wurde. Während bei standaloneSetup() die konkreten zu testenden Klassen übergeben werden, verwendet webAppContextSetup() den kompletten WebApplicationContext, und somit können auch nicht explizit angegebene Klassen wie im Beispiel DemoMvcRestService getestet werden.

  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F) (je nach Ausgangsprojekt können weniger oder weitere Dateien vorhanden sein):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [springbootdemo]
     |   |           |- ControllerUndMain.java
     |               '- DemoMvcRestService.java
     |   '- [test]
     |       '- [java]
     |           '- [springbootdemo]
     |               |- ControllerUndMainTest.java
     |               '- DemoMvcRestServiceTest.java
     '- pom.xml
    
  7. Führen Sie die JUnit-Modultests aus:

    mvn clean test



Integrationstest mit embedded Webserver für Spring-Boot-Webanwendung

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert das vorherige Beispiel JUnit-Modultest mit Mock für Spring-Boot-Webanwendung.

  2. Ergänzen Sie im Projektverzeichnis in der Maven-Projektkonfigurationsdatei pom.xml in der <plugins>-Sektion folgendes Plug-in:

          <plugin>
            <!-- failsafe 2.19.1 funktioniert nicht mit Spring Boot 1.5.x -->
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.18.1</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                  <goal>verify</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
    
  3. Fügen Sie im src\test\java\springbootdemo-Testverzeichnis einen Integrationstest hinzu: ControllerUndMainIT.java

    package springbootdemo;
    
    import static org.hamcrest.Matchers.equalTo;
    import static org.junit.Assert.assertThat;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.http.ResponseEntity;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith( SpringRunner.class )
    @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT )
    public class ControllerUndMainIT
    {
       @Value( "${local.server.port}" ) private int port;
       @Autowired private TestRestTemplate restTemplate;
    
       @Test
       public void testHalloString()
       {
          String url = "http://localhost:" + port + "/web";
          ResponseEntity<String> response = restTemplate.getForEntity( url, String.class );
          assertThat( response.getBody(), equalTo( "Hallo Spring-Boot-Web-Welt!" ) );
       }
    }
    

    Sehen Sie sich die Javadoc an zu den Klassen, Annotationen und Parametern: @RunWith, SpringRunner, @SpringBootTest, WebEnvironment.RANDOM_PORT, @Value, @Autowired, TestRestTemplate, @Test, ResponseEntity.

  4. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F) (je nach Ausgangsprojekt können weitere Dateien vorhanden sein):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   |- [main]
     |   |   '- [java]
     |   |       '- [springbootdemo]
     |   |           '- ControllerUndMain.java
     |   '- [test]
     |       '- [java]
     |           '- [springbootdemo]
     |               |- ControllerUndMainIT.java
     |               '- ControllerUndMainTest.java
     '- pom.xml
    
  5. Führen Sie den Integrationstest aus:

    mvn clean verify

  6. Falls Sie eine Fehlermeldung erhalten ähnlich zu:

    [ERROR] Failed to execute ...: Execution default of goal org.apache.maven.plugins:maven-failsafe-plugin:2.19.1:integration-test failed: There was an error in the forked process
    [ERROR] java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

    Oder:

    initializationError... java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

    Dann liegt das daran, dass Sie eine Version vom maven-failsafe-plugin verwenden, die ohne weitere Maßnahmen nicht für die verwendete Spring-Boot-Version geeignet ist.
    Im maven-failsafe-plugin 2.19.x ist das target/classes-Verzeichnis nicht mehr im Classpath, weil stattdessen die Jar-Datei verwendet wird.
    Ab Spring Boot 1.4.x hat sich das Layout in der repackaged Jar-Datei geändert.
    Sie können wahlweise entweder andere Versionen verwenden. Oder Sie versuchen andere Konfigurationseinstellungen, beispielsweise <configuration><classifier>exec</classifier></configuration> beim spring-boot-maven-plugin.



Externe WAR in Spring-Boot-Webanwendung deployen

Das folgende Beispiel demonstriert:

Optional: Erstellung einer Test-WAR-Datei

Sie können für diese Demo eine beliebige WAR-Datei verwenden. Für erste Schritte empfiehlt es sich, mit der im Folgenden erzeugten WAR-Datei zu beginnen, damit gezeigt werden kann, wie die externe WAR-Webapp auf Ressourcen der Spring-Boot-Webapp zugreifen kann.

Führen Sie folgende Schritte aus:

  1. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md MeineExterneWebapp

    cd MeineExterneWebapp

    md src\main\webapp\WEB-INF

    tree /F

  2. Erstellen Sie im MeineExterneWebapp-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
       <groupId>springbootdemo</groupId>
       <artifactId>MeineExterneWebapp</artifactId>
       <version>1.0-SNAPSHOT</version>
       <packaging>war</packaging>
       <name>MeineExterneWebapp</name>
       <properties>
          <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
       </properties>
       <build>
          <finalName>${project.artifactId}</finalName>
       </build>
    </project>
    
  3. Erzeugen Sie im src\main\webapp\WEB-INF-Verzeichnis die Webapp-Konfigurationsdatei: web.xml

    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>
       <display-name>MeineExterneWebapp</display-name>
    </web-app>
    
  4. Erzeugen Sie im src\main\webapp-Verzeichnis die JSP-Datei, welche versucht, eine Ressourcen-Datei zu lesen, welche nicht Bestandteil dieser Webapp ist: index.jsp

    <%@ page import = "java.io.*" %>
    <html>
    <body>
    <h3>MeineExterneWebapp</h3>
    <pre>
    <%= "Java-Version:       " + System.getProperty( "java.runtime.version" ) %><br>
    <%= "ServerInfo:         " + getServletContext().getServerInfo() %><br>
    <%= "RealPath:           " + getServletContext().getRealPath( "/" ) %><br>
    <%= "Actual Path:        " + (new File( "." )).getAbsolutePath() %><br>
    <%
    String s1 = System.getProperty( "java.class.path" );
    if( s1 != null && s1.length() > 400 ) { s1 = s1.substring( 0, 100 ) + " ... " + s1.substring( s1.length() - 100 ); }
    out.println( "java.class.path:    " + s1 + "<br>" );
    String s2 = null;
    try( InputStream    is = getClass().getClassLoader().getResourceAsStream( "/MeineRessource.txt" );
         BufferedReader in = new BufferedReader( new InputStreamReader( is ) ) ) {
       s2 = in.readLine();
    } catch( Exception ex ) {}
    out.println( "MeineRessource.txt: " + (( s2 != null ) ? s2 : "nicht gefunden!") );
    %>
    </pre>
    </body>
    </html>
    
  5. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\MeineExterneWebapp]
     |- [src]
     |   '- [main]
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- index.jsp
     '- pom.xml
    
  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\MeineExterneWebapp

    mvn clean package

    dir target\MeineExterneWebapp.war

Erstellung der Spring-Boot-Webanwendung

Führen Sie folgende Schritte aus:

  1. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootWebExtWarDepl

    cd SpringBootWebExtWarDepl

    md src\main\java\springbootdemo

    md src\main\resources

    md war

    tree /F

  2. Erstellen Sie im SpringBootWebExtWarDepl-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>2.2.4.RELEASE</version>
       </parent>
       <groupId>springbootdemo</groupId>
       <artifactId>SpringBootWebExtWarDepl</artifactId>
       <version>1.0-SNAPSHOT</version>
       <properties>
          <java.version>1.8</java.version>
          <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
       </properties>
       <dependencies>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-tomcat</artifactId>
          </dependency>
          <dependency>
             <groupId>org.apache.tomcat.embed</groupId>
             <artifactId>tomcat-embed-jasper</artifactId>
          </dependency>
       </dependencies>
       <build>
          <plugins>
             <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
          </plugins>
       </build>
    </project>
    
  3. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die Klasse: EmbeddedTomcatWarDeplMain.java

    package springbootdemo;
    
    import org.apache.catalina.Context;
    import org.apache.catalina.startup.Tomcat;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
    import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import java.io.File;
    
    /** Externe WAR in Spring-Boot-Webanwendung deployen */
    @Controller
    @SpringBootApplication
    public class EmbeddedTomcatWarDeplMain
    {
       /** Hier den Namen der externen Webanwendungs-WAR-Datei eintragen: */
       private static final String WAR_DATEI = "MeineExterneWebapp.war";
    
       /** SpringBootApplication: main(): */
       public static void main( String[] args )
       {
          SpringApplication.run( EmbeddedTomcatWarDeplMain.class, args );
       }
    
       /** Dieser REST-Service ist nur dazu da, um zu zeigen, dass die normalen Spring-Boot-Webanwendung-Webservices weiterhin funktionieren,
           Aufruf mit: http://localhost:8080/web */
       @RequestMapping( path = "/web" )
       @ResponseBody
       String halloSpringBootWeb()
       {
          return "Hallo Spring-Boot-Web-Welt!";
       }
    
       /** Hier wird zusätzlich eine externe Webanwendung-WAR-Datei in den embedded Tomcat der Spring-Boot-Webanwendung deployt,
           Aufruf mit: http://localhost:8080/externeWar */
       @Bean
       public AbstractServletWebServerFactory servletContainerFactory()
       {
          return new TomcatServletWebServerFactory()
          {
             @Override
             protected TomcatWebServer getTomcatWebServer( Tomcat tomcat )
             {
                new File( tomcat.getServer().getCatalinaBase(), "webapps" ).mkdirs();
                try {
                   Context tomcatContext = tomcat.addWebapp( "/externeWar", (new File( "war/" + WAR_DATEI )).getAbsolutePath() );
                   tomcatContext.setParentClassLoader( getClass().getClassLoader() );
                } catch( ServletException ex ) {
                   throw new IllegalStateException( "Fehler: Webapp-WAR " + WAR_DATEI + " kann nicht deployt werden.", ex );
                }
                return super.getTomcatWebServer( tomcat );
             }
          };
       }
    }
    

    Falls Ihre externe Webapp-WAR-Datei nicht MeineExterneWebapp.war heißt, tragen Sie bei WAR_DATEI = "MeineExterneWebapp.war" den korrekten Namen ein.

  4. Optional können Sie im src\main\resources-Verzeichnis eine Ressourcen-Datei anlegen (damit können Sie zeigen, dass die externe Webapp auf Ressourcen der Spring-Boot-Webapp zugreifen kann): MeineRessource.txt

    Inhalt meiner Ressource ...
    
  5. Sie benötigen eine beliebige funktionierende Webanwendungs-WAR-Datei. Kopieren Sie diese in das war-Verzeichnis. Falls Sie die oben beschriebene MeineExterneWebapp.war erstellt haben, kopieren Sie sie so:

    cd \MeinWorkspace\SpringBootWebExtWarDepl

    copy ..\MeineExterneWebapp\target\MeineExterneWebapp.war war\

  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootWebExtWarDepl]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- EmbeddedTomcatWarDeplMain.java
     |       '- [resources]
     |           '- MeineRessource.txt
     |- [war]
     |   '- MeineExterneWebapp.war
     '- pom.xml
    
  7. Durch das spring-boot-maven-plugin wird eine ausführbare jar-Datei erzeugt.
    Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootWebExtWarDepl-1.0-SNAPSHOT.jar

    Und zeigen Sie sowohl die Webseite vom normalen Spring-Boot-Webanwendung-Webservice als auch die Webseite der zusätzlich deployten externen WAR-Datei an:

    start http://localhost:8080/web

    start http://localhost:8080/externeWar

    Falls Sie die oben beschriebene MeineExterneWebapp.war verwenden: Kontrollieren Sie, dass auf der Webseite der Inhalt der Ressourcentextdatei MeineRessource.txt angezeigt wird.

Classpath-Erweiterung für einen Zugriff auf externe Ressourcen außerhalb der Spring-Boot-Anwendung

  1. Sie können den Classpath erweitern, indem Sie in obiger EmbeddedTomcatWarDeplMain.java die AbstractServletWebServerFactory-Bean ersetzen durch (passen Sie den Pfad "config" an Ihr Konfigurationsverzeichnis an):

       @Bean
       public AbstractServletWebServerFactory servletContainerFactory()
       {
          return new TomcatServletWebServerFactory()
          {
             @Override
             protected TomcatWebServer getTomcatWebServer( Tomcat tomcat )
             {
                new File( tomcat.getServer().getCatalinaBase(), "webapps" ).mkdirs();
    
                try {
                   Context tomcatContext = tomcat.addWebapp( "/externeWar", (new File( "war/" + WAR_DATEI )).getAbsolutePath() );
                   tomcatContext.setParentClassLoader( getClass().getClassLoader() );
    
                   DirResourceSet drs = new DirResourceSet();
                   drs.setBase( (new File( "config" )).getAbsolutePath() );
                   drs.setInternalPath( "/" );
                   drs.setWebAppMount( "/WEB-INF/classes" );
                   WebResourceRoot resourceRoot = new StandardRoot( tomcatContext );
                   resourceRoot.addPreResources( drs );
                   tomcatContext.setResources( resourceRoot );
    
                } catch( ServletException ex ) {
                   throw new IllegalStateException( "Fehler: Webapp-WAR " + WAR_DATEI + " kann nicht deployt werden.", ex );
                }
                return super.getTomcatWebServer( tomcat );
             }
          };
       }
    
  2. Ergänzen Sie in derselben Klasse die Imports:

    import org.apache.catalina.WebResourceRoot;
    import org.apache.catalina.webresources.DirResourceSet;
    import org.apache.catalina.webresources.StandardRoot;
    
  3. Um das Lesen der externen Ressorce zu testen, müssen Sie die zu lesende Ressourcendatei aus dem resources-Verzeichnis entfernen und in das neu zu erstellende config-Verzeichnis verschieben:

    cd \MeinWorkspace\SpringBootWebExtWarDepl

    md config

    move src\main\resources\MeineRessource.txt config\

    tree /F

    Falls Ihnen diese Beschreibung zu fragmentarisch ist: Die vollständig ausprogrammierte Demo finden Sie im Projekte-Download im Projekt "SpringBootWebExtWarDepl-02".

Authentifizierung und Autorisierung

  1. Um Authentifizierung per Benutzer/Passwort und rollenbasierte Autorisierung hinzuzufügen, muss etwas speziell vorgegangen werden, damit es sich auf deployte WARs auswirkt.

  2. Fügen Sie in obiger AbstractServletWebServerFactory-Bean im "try { ... }"-Zweig als letzte Anweisung hinzu:

                   // Diese Zeile hinzufuegen, falls Authentifizierung benoetigt wird
                   // (dabei MEIN_PASSWORT und MEIN_ROLLENNAME anpassen):
                   addSimpleAuthorization( tomcatContext, createSimpleRealm( MEIN_PASSWORT, MEIN_ROLLENNAME ), MEIN_ROLLENNAME, "/*" );
    

    (Definieren Sie die Konstanten MEIN_PASSWORT und MEIN_ROLLENNAME geeignet.)

  3. Fügen Sie in obiger EmbeddedTomcatWarDeplMain-Klasse diese beiden Methoden hinzu:

       /** Sehr einfacher Realm, nur fuer erste Tests geeignet */
       private static Realm createSimpleRealm( String password, String role )
       {
          return new RealmBase() {
             @Override protected String getName() { return "MeinRealm"; }
             @Override protected String getPassword( String username ) { return password; }
             @Override protected Principal getPrincipal( String username ) {
                return new GenericPrincipal( username, password, Arrays.asList( role ) );
             }
          };
       }
    
       /** Einfache Authentifizierung/Autorisierung */
       private static void addSimpleAuthorization( Context tomcatContext, Realm realm, String role, String urlPattern )
       {
          LoginConfig loginConfig = new LoginConfig();
          loginConfig.setAuthMethod( "BASIC" );
    
          SecurityCollection securityCollection = new SecurityCollection();
          securityCollection.addPattern( urlPattern );
          SecurityConstraint securityConstraint = new SecurityConstraint();
          securityConstraint.addCollection( securityCollection );
          securityConstraint.addAuthRole( role );
          securityConstraint.setDisplayName( "Authentifizierung erforderlich" );
    
          tomcatContext.setRealm( realm );
          tomcatContext.addSecurityRole( role );
          tomcatContext.addConstraint( securityConstraint );
          tomcatContext.setLoginConfig( loginConfig );
       }
    

    (Fügen Sie außerdem die benötigten Import-Anweisungen hinzu.)

  4. Falls Ihnen diese Beschreibung zu fragmentarisch ist: Die vollständig ausprogrammierte Demo finden Sie im Projekte-Download im Projekt "SpringBootWebExtWarDepl-03".

  5. Wenn Sie jetzt das Projekt bauen und die Webseite der externen WAR aufrufen, gelangen Sie nur nach erfolgreicher Authentifizierung und Autorisierung auf die Webseite:

    mvn clean package

    java -jar target/SpringBootWebExtWarDepl-1.0-SNAPSHOT.jar

    start http://localhost:8080/externeWar

    Geben Sie als Benutzername irgendetwas ein, und als Passwort das, was Sie bei MEIN_PASSWORT definiert haben.

HTTPS-Transportverschlüsselung

  1. Damit bei obiger Authentifizierung nicht die Passwörter unverschlüsselt übertragen werden, müssen Sie unbedingt HTTPS-Transportverschlüsselung hinzufügen.

  2. Fügen Sie in obiger AbstractServletWebServerFactory-Bean am Anfang des "try { ... }"-Blocks hinzu:

                   // Diese zwei Zeilen hinzufuegen, falls HTTPS-Transportverschluesselung benoetigt wird
                   // (dabei keystoreFile, keyAlias und keystorePass an Key anpassen):
                   tomcat.getService().findConnectors()[0].setRedirectPort( 8443 );
                   tomcat.getService().addConnector( createHttpsConnector(
                         8443, "./config/MeinKeystore.jks", "MeinAlias01", "geheim" ) );
    

    (Definieren Sie die Parameter keystoreFile, keyAlias und keystorePass entsprechend Ihres Keys, siehe unten.)

  3. Ergänzen Sie in obiger addSimpleAuthorization()-Methode:

          // Diese Zeile hinzufuegen, falls Auto-Redirect von HTTP auf HTTPS benoetigt wird:
          securityConstraint.setUserConstraint( "CONFIDENTIAL" );
    
  4. Fügen Sie in obiger EmbeddedTomcatWarDeplMain-Klasse diese Methode hinzu:

       /** HTTPS-Connector */
       private static Connector createHttpsConnector( int port, String keystoreFile, String keyAlias, String keystorePass )
       {
          Connector connector = new Connector();
          connector.setPort( port );
          connector.setScheme( "https" );
          connector.setSecure( true );
          connector.setAttribute( "keystoreFile", (new File( keystoreFile )).getAbsolutePath() );
          connector.setAttribute( "keyAlias", keyAlias );
          connector.setAttribute( "keystorePass", keystorePass );
          connector.setAttribute( "keystoreType", "JKS" );
          connector.setAttribute( "SSLEnabled", true );
          connector.setAttribute( "sslProtocol", "TLS" );
          connector.setAttribute( "protocol", "org.apache.coyote.http11.Http11AprProtocol" );
          connector.setAttribute( "maxThreads", "200" );
          return connector;
       }
    

    (Fügen Sie außerdem die benötigte Import-Anweisung für org.apache.catalina.connector.Connector hinzu.)

  5. Falls Ihnen diese Beschreibung zu fragmentarisch ist: Die vollständig ausprogrammierte Demo finden Sie im Projekte-Download im Projekt "SpringBootWebExtWarDepl-04".

  6. Um für einen ersten Test ein Schlüsselpaar und ein selbst signiertes Zertifikat zu generieren, führen Sie (wie bereits weiter oben schon so ähnlich beschrieben) im Kommandozeilenfenster aus (das keytool-Kommando in einer einzigen Kommandozeile) (dabei keystore, alias und storepass anpassen an obige keystoreFile, keyAlias und keystorePass):

    cd \MeinWorkspace\SpringBootWebExtWarDepl

    md config

    cd config

    keytool -genkey -v -keyalg RSA -alias MeinAlias01 -keystore MeinKeystore.jks -keypass geheim -storepass geheim -dname "CN=Mein Name, OU=-, O=-, L=-, S=-, C=DE"

    cd ..

    Merken Sie sich die beiden vergebenen Passwörter (im Beispiel "geheim"). Die anderen Eingaben spielen in diesem Beispiel keine Rolle.

    Erläuterungen hierzu finden Sie unter keytool - Key and Certificate Management Tool und Tomcat SSL/TLS Configuration.

  7. Wenn Sie jetzt das Projekt bauen und die Webseite der externen WAR aufrufen, werden Sie automatisch auf die HTTPS-Portnummer umgeleitet, und je nach Webbrowser erscheint vielleicht vor der URL-Adresse ein Schloss-Symbol:

    mvn clean package

    java -jar target/SpringBootWebExtWarDepl-1.0-SNAPSHOT.jar

    start http://localhost:8080/externeWar

  8. Weil der generierte Schlüssel nicht mit einem dem Webbrowser bekannten Sicherheitszertifikat zertifiziert ist, erhalten Sie beim ersten Aufruf je nach Webbrowser eine Fehlermeldung ähnlich zu folgender:

    Diese Verbindung ist nicht sicher.
    Der Inhaber von localhost hat die Website nicht richtig konfiguriert. Firefox hat keine Verbindung mit dieser Website aufgebaut, um Ihre Informationen vor Diebstahl zu schützen.
    localhost:8443 verwendet ein ungültiges Sicherheitszertifikat.
    Dem Zertifikat wird nicht vertraut, weil es vom Aussteller selbst signiert wurde.
    Das Zertifikat ist nur gültig für ....
    Fehlercode: SEC_ERROR_UNKNOWN_ISSUER oder MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT
    Ausnahme hinzufügen...

    Erlauben Sie eine Ausnahme, um die Fehlermeldung zu vermeiden. Anschließend funktioniert die Webseite wie vorher. Allerdings ist sie nur noch über das verschlüsselte HTTPS-Protokoll erreichbar.

  9. Beachten Sie, dass einige Webbrowser den Zugriff auf unverschlüsselte HTTP-Webinhalte von einer verschlüsselten HTTPS-Webseite aus eventuell blockieren. Siehe hierzu: Mixed Content.

JUnit-Test zur im embedded Tomcat deployten WAR inklusive HTTPS-Verschlüsselung und Authentifizierung

  1. Erweitern Sie im Projektverzeichnis die Maven-Projektkonfigurationsdatei pom.xml um diese Dependency:

          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
          </dependency>
    
  2. Fügen Sie ein Test-Verzeichnis hinzu:

    md src\test\java\springbootdemo

  3. Fügen Sie im Test-Verzeichnis src\test\java\springbootdemo einen JUnit-Test hinzu: EmbeddedTomcatWarDeplMainTest.java

    package springbootdemo;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.http.HttpStatus;
    import org.springframework.test.context.junit4.SpringRunner;
    import javax.net.ssl.*;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.X509Certificate;
    import java.util.Base64;
    import java.util.stream.Collectors;
    import static org.hamcrest.Matchers.equalTo;
    import static org.hamcrest.Matchers.is;
    import static org.junit.Assert.assertThat;
    
    @RunWith( SpringRunner.class )
    @SpringBootTest( webEnvironment = WebEnvironment.DEFINED_PORT )
    public class EmbeddedTomcatWarDeplMainTest
    {
       private static final String USER = "BenutzerXX";
       private static final String PASSWORD = "xx";
    
       /** Test des Spring-Boot-REST-Services */
       @Test
       public void testRestHalloString() throws IOException
       {
          HttpURLConnection connection = (HttpURLConnection) (new URL( "http://localhost:8080/web" )).openConnection();
          assertThat( connection.getResponseCode(), equalTo( HttpStatus.OK.value() ) ); // 200 = Ok
          String response = null;
          try( BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream() ) ) ) {
             response = reader.lines().collect( Collectors.joining( "\n" ) );
          }
          assertThat( response.contains( "Hallo Spring-Boot-Web-Welt!" ), is( true ) );
       }
    
       /** Test der deployten externen WAR: HTTP-Port 8080 darf nicht mehr funktionieren */
       @Test
       public void testExterneWarFehlerAlterPortMoved302() throws IOException
       {
          HttpURLConnection connection = (HttpURLConnection) (new URL( "http://localhost:8080/externeWar" )).openConnection();
          assertThat( connection.getResponseCode(), equalTo( HttpStatus.FOUND.value() ) ); // 302 = Found/Moved
       }
    
       /** Test der deployten externen WAR: Ohne HTTPS und ohne Authentifizierung kein Zugang */
       @Test
       public void testExterneWarFehlerOhneHttpsOhneAuth400() throws IOException
       {
          HttpURLConnection connection = (HttpURLConnection) (new URL( "http://localhost:8443/externeWar" )).openConnection();
          assertThat( connection.getResponseCode(), equalTo( HttpStatus.BAD_REQUEST.value() ) ); // 400 = Bad Request
       }
    
       /** Test der deployten externen WAR: mit HTTPS und mit Authentifizierung erfolgreich */
       @Test
       public void testExterneWar() throws IOException, NoSuchAlgorithmException, KeyManagementException
       {
          allowUntrustedCertificate();
          String authString = USER + ":" + PASSWORD;
          String authStringEnc = new String( Base64.getEncoder().encode( authString.getBytes( "UTF-8" ) ) );
          HttpsURLConnection connection = (HttpsURLConnection) (new URL( "https://localhost:8443/externeWar" )).openConnection();
          connection.setRequestMethod( "GET" );
          connection.setRequestProperty( "Authorization", "Basic " + authStringEnc );
          assertThat( connection.getResponseCode(), equalTo( HttpStatus.OK.value() ) ); // 200 = Ok
          String response = null;
          try( BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream() ) ) ) {
             response = reader.lines().collect( Collectors.joining( "\n" ) );
          }
          System.out.println( response );
          assertThat( response.contains( "MeineRessource.txt: Inhalt meiner Ressource ..." ), is( true ) );
       }
    
       /** Nur fuer Test: Korrektes Zertifikat vortaeuschen */
       private void allowUntrustedCertificate() throws NoSuchAlgorithmException, KeyManagementException
       {
          TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
             public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
             public void checkClientTrusted( X509Certificate[] certs, String authType ) {}
             public void checkServerTrusted( X509Certificate[] certs, String authType ) {}
          } };
    
          SSLContext sc = SSLContext.getInstance( "SSL" );
          sc.init( null, trustAllCerts, new java.security.SecureRandom() );
          HttpsURLConnection.setDefaultSSLSocketFactory( sc.getSocketFactory() );
    
          HostnameVerifier allHostsValid = new HostnameVerifier() {
             public boolean verify( String hostname, SSLSession session ) { return true; }
          };
    
          HttpsURLConnection.setDefaultHostnameVerifier( allHostsValid );
       }
    }
    
  4. Führen Sie den JUnit-Test aus:

    mvn clean package

  5. Die vollständig ausprogrammierte Demo finden Sie im Projekte-Download im Projekt "SpringBootWebExtWarDepl-05".

Konfiguration des Server-Basisverzeichnisses des embedded Tomcat

  1. Normalerweise wird der embedded Tomcat in einem Unterverzeichnis des temporären Verzeichnis des Betriebssystems gestartet, beispielsweise in
    \tmp\tomcat.139241861273835468.8080 bzw. in C:\Users\User\AppData\Local\Temp\tomcat.139241861273835468.8080.

    Dies ist oft nicht erwünscht:
    - Die Arbeitsdateien sind in dem allgemeinen tmp-Verzeichnis nicht gut geschützt.
    - Insbesondere unter Linux kann der tmp-Speicherplatz limitiert sein.
    - Falls es Prozesse gibt, welche im tmp-Speicherplatz hin und wieder alte Dateien löschen, kann es zu schwer auffindbaren Fehlern kommen. Beispielsweise kann es sein, dass nach dem Löschen der tmp-Tomcat-Verzeichnisse der Tomcat inklusive aller serverseitigen Prozesse weiter läuft und seine Arbeit verrichtet, aber alle GUI-Webseiten nicht mehr ansprechbar sind, ohne dass es eine Fehlermeldung in den Logs gibt.

  2. Wenn Sie das Tomcat-Basisverzeichnis vorgeben wollen, ersetzen Sie in obiger EmbeddedTomcatWarDeplMain.java die Zeile

                new File( tomcat.getServer().getCatalinaBase(), "webapps" ).mkdirs();
    

    beispielsweise durch:

                String catalinaBase = (new File( "." + File.separator + "target" + File.separator + "catalinaBase" )).getAbsolutePath();
                String appBaseDir   = catalinaBase + File.separator + "webapps";
                tomcat.getServer().setCatalinaBase( new File( catalinaBase ) );
                tomcat.setBaseDir( appBaseDir );
                tomcat.getHost().setAppBase( appBaseDir );
                new File( appBaseDir ).mkdirs();
    


Actuator zur Ausgabe von Health, Status, Props und Metriken

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Für dieses Beispiel dient ein beliebiges der "SpringBootWeb"-Beispiele als Ausgangspunkt (z.B. Webanwendung mit Tomcat, mit Jetty, mit RestController, mit REST per Spring-MVC etc.).

  2. Ergänzen Sie im Projektverzeichnis in der Maven-Projektkonfigurationsdatei pom.xml bei den Dependencies folgende Dependency:

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    
  3. Falls noch nicht vorhanden, legen Sie ein resources-Verzeichnis an:

    md src\main\resources

    Und erzeugen Sie darin folgende Properties-Datei (bzw. fügen Sie folgenden Inhalt hinzu, falls es die Properties-Datei bereits gibt): application.properties

    management.endpoints.web.exposure.include=*
    
  4. Führen Sie wie oben gezeigt im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    curl http://localhost:8080/web

  5. Führen Sie zusätzlich folgende Kommandos einzeln aus:

    "Configuration Endpoints":

    curl http://localhost:8080/actuator/beans

    curl http://localhost:8080/actuator/env

    curl http://localhost:8080/actuator/configprops

    curl http://localhost:8080/actuator/mappings

    "Metrics Endpoints":

    curl http://localhost:8080/actuator/health

    curl http://localhost:8080/actuator/metrics

    "Miscellaneous Endpoints":

    curl http://localhost:8080/actuator/info

  6. Sie erhalten beispielsweise:

    { "status":"UP", "diskSpace":{"status":"UP", "total":1104530108416, "free":555103784960, "threshold":10485760} }

    { "mem":530860, "mem.free":189897, "processors":8, "instance.uptime":21685, "uptime":26319, "systemload.average":-1.0, "heap.committed":478720, "heap.init":196608, "heap.used":288822, "heap":2794496, "nonheap.committed":53696, "nonheap.init":2496, "nonheap.used":52141, "nonheap":0, "threads.peak":15, "threads.daemon":5, "threads.totalStarted":19, "threads":15, "classes":6388, "classes.loaded":6388, "classes.unloaded":0, "gc.ps_scavenge.count":9, "gc.ps_scavenge.time":67, "gc.ps_marksweep.count":1, "gc.ps_marksweep.time":49, "gauge.response.rest":4.0, "gauge.response.health":69.0, "gauge.response.web":40.0, "gauge.response.star-star.favicon.ico":1.0, "counter.status.200.star-star.favicon.ico":1, "counter.status.304.star-star.favicon.ico":2, "counter.status.200.rest":1, "counter.status.200.web":1, "counter.status.200.health":1 }

    ...

  7. Eine Liste der "Endpoints" finden Sie unter Actuator Endpoints. Erläuterungen zu den Metriken finden Sie unter Actuator Metrics.



Konfiguration per application.properties

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert das vorherige Beispiel Actuator zur Ausgabe von Health, Status, Props und Metriken.

  2. Überprüfen Sie, dass die bisherige URL noch funktioniert:

    cd \MeinWorkspace\SpringBootWeb

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    start http://localhost:8080/web

    Überprüfen Sie, dass ein Shutdown per curl nicht funktioniert:

    curl -X POST localhost:8080/actuator/shutdown

    Beenden Sie den Webserver mit Strg+C.

  3. Falls noch nicht vorhanden, legen Sie ein resources-Verzeichnis an:

    md src\main\resources

    Und erzeugen Sie darin folgende Properties-Datei (bzw. fügen Sie folgenden Inhalt hinzu, falls es die Properties-Datei bereits gibt): application.properties

    server.port=18080
    management.endpoints.web.exposure.include=*
    management.endpoint.shutdown.enabled=true
    
  4. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F) (je nach Ausgangsprojekt können weitere Dateien vorhanden sein):

    [\MeinWorkspace\SpringBootWeb]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- ControllerUndMain.java
     |       '- [resources]
     |           '- application.properties
     '- pom.xml
    
  5. Bauen Sie die Anwendung neu:

    mvn clean package

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

  6. Überprüfen Sie, dass die bisherige URL nicht mehr funktioniert:

    start http://localhost:8080/web

  7. Testen Sie die neue URL:

    start http://localhost:18080/web

  8. Lesen Sie die applicationConfig-Environment-Variablen aus:

    curl http://localhost:18080/actuator/env

    {
    ...
    "applicationConfig: [classpath:/application.properties]":{"endpoints.shutdown.enabled":"true","server.port":"18080"}
    }
    
  9. Überprüfen Sie, dass ein Shutdown per curl jetzt funktioniert:

    curl -X POST localhost:18080/actuator/shutdown

  10. Löschen Sie anschließend unbedingt die application.properties, damit die folgenden Beispiele wie gewohnt mit der Portnummer 8080 funktionieren:

    cd \MeinWorkspace\SpringBootWeb

    del src\main\resources\application.properties



Properties-Priorisierung und Profile

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert eines der beiden vorherigen Beispiele Actuator zur Ausgabe von Health, Status, Props und Metriken oder Konfiguration per application.properties.

  2. Erzeugen Sie Verzeichnisse für Properties-Dateien:

    cd \MeinWorkspace\SpringBootWeb

    md config

    md src\main\resources

  3. Ändern Sie im src\main\java\springbootdemo-Verzeichnis den Inhalt der Controller-Klasse ControllerUndMain.java, so dass der resultierende Wert der Variablen meine.test.prop angezeigt wird:

    package springbootdemo;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    @SpringBootApplication
    public class ControllerUndMain
    {
       @RequestMapping( "/web" )
       @ResponseBody
       String halloSpringBootWeb( @Value( "${meine.test.prop}" ) String meineTestProp )
       {
          return "Hallo Spring-Boot-Web-Welt: meine.test.prop=" + meineTestProp;
       }
    
       public static void main( String[] args )
       {
          SpringApplication.run( ControllerUndMain.class, args );
       }
    }
    
  4. Erzeugen Sie in den folgenden Unterverzeichnissen folgende Properties-Dateien:

    src\main\resources\application.properties

    meine.test.prop=20
    management.endpoints.web.exposure.include=*
    

    application.properties

    meine.test.prop=30
    

    config\application.properties

    meine.test.prop=35
    

    src\main\resources\application.yml

    meine:
      test:
        prop: 11
    ---
    spring:
      profiles: profilAbc
    meine:
      test:
        prop: 41
    

    src\main\resources\application-profilAbc.properties

    meine.test.prop=50
    

    application-profilAbc.properties

    meine.test.prop=60
    

    config\application-profilAbc.properties

    meine.test.prop=65
    
  5. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F) (je nach Ausgangsprojekt können weitere Dateien vorhanden sein):

    [\MeinWorkspace\SpringBootWeb]
     |- [config]
     |   |- application.properties
     |   '- application-profilAbc.properties
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- ControllerUndMain.java
     |       '- [resources]
     |           |- application.properties
     |           |- application.yml
     |           '- application-profilAbc.properties
     |- application.properties
     |- application-profilAbc.properties
     '- pom.xml
    
  6. Bauen Sie die Anwendung und rufen Sie die Webanwendung auf:

    mvn clean package

    set MEINE_TEST_PROP=

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    start http://localhost:8080/web

    Sie erhalten:

    Hallo Spring-Boot-Web-Welt: meine.test.prop=35
    
  7. Beenden Sie die Webanwendung mit Strg+C, und starten Sie die Webanwendung mit aktiviertem Profil profilAbc:

    set MEINE_TEST_PROP=

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar --spring.profiles.active=profilAbc

    start http://localhost:8080/web

    Sie erhalten:

    Hallo Spring-Boot-Web-Welt: meine.test.prop=65
    
  8. Beenden Sie die Webanwendung mit Strg+C, und starten Sie die Webanwendung mit gesetzter System-Environmentvariable MEINE_TEST_PROP:

    set MEINE_TEST_PROP=70

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar

    start http://localhost:8080/web

    Sie erhalten:

    Hallo Spring-Boot-Web-Welt: meine.test.prop=70
    
  9. Beenden Sie die Webanwendung mit Strg+C, und starten Sie die Webanwendung mit gesetzter System-Environmentvariable MEINE_TEST_PROP=70, mit der JVM-System-Property -Dmeine.test.prop=80 und zwei Kommandozeilenparametern --meine.test.prop=90 --spring.profiles.active=profilAbc:

    set MEINE_TEST_PROP=70

    java -Dmeine.test.prop=80 -jar target/SpringBootWeb-1.0-SNAPSHOT.jar --meine.test.prop=90 --spring.profiles.active=profilAbc

    start http://localhost:8080/web

    Sie erhalten:

    Hallo Spring-Boot-Web-Welt: meine.test.prop=90
    
  10. Lesen Sie die Properties und applicationConfig-Environment-Variablen aus:

    start http://localhost:8080/actuator/env

    {
    "profiles":["profilAbc"],
    "server.ports":{"local.server.port":8080},
    "commandLineArgs":{"meine.test.prop":"90","spring.profiles.active":"profilAbc"},
    "servletContextInitParams":{},
    "systemProperties":{...,"meine.test.prop":"80",...},
    "systemEnvironment":{...,"MEINE_TEST_PROP":"70",...},
    "applicationConfig: [file:./config/application-profilAbc.properties]":{"meine.test.prop":"65"},
    "applicationConfig: [file:./application-profilAbc.properties]":{"meine.test.prop":"60"},
    "applicationConfig: [classpath:/application-profilAbc.properties]":{"meine.test.prop":"50"},
    "applicationConfig: [classpath:/application.yml]#profilAbc":{"spring.profiles":"profilAbc","meine.test.prop":41},
    "applicationConfig: [file:./config/application.properties]":{"meine.test.prop":"35"},
    "applicationConfig: [file:./application.properties]":{"meine.test.prop":"30"},
    "applicationConfig: [classpath:/application.properties]":{"meine.test.prop":"20"},
    "applicationConfig: [classpath:/application.yml]":{"meine.test.prop":11}
    }
    
  11. Übrigens können Sie nicht nur im Ergebnis-Jar vorhandene Properties-Dateien verwenden, sondern mit spring.config.location beliebige im Dateiensystem verfügbare Properties-Dateien, beispielsweise so:

    set MEINE_TEST_PROP=

    java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar --spring.config.location=file:application-profilAbc.properties

    start http://localhost:8080/web

    Sie erhalten:

    Hallo Spring-Boot-Web-Welt: meine.test.prop=60
    

    Sehen Sie sich hierzu an: Externalized Configuration, Application property files und Properties & configuration, Change the location of external properties of an application.

  12. Beachten Sie Folgendes:



ApplicationListener und EventListener

Das folgende Beispiel demonstriert:

Im ersten Schritt ist in diesem Beispiel die einzige Funktion der ApplicationListener/EventListener, sich auf der Konsole zu melden. So kann verfolgt werden, zu welchem Zeitpunkt auf welches Event reagiert werden kann.

Weiterführendes finden Sie beispielsweise unter: Application events and listeners, Better application events in Spring Framework 4.2, Spring Boot Event Sample und Examples for ApplicationEnvironmentPreparedEvent.

Führen Sie folgende Schritte aus:

  1. JDK und Maven müssen installiert sein.

  2. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace

    md SpringBootEvent

    cd SpringBootEvent

    md src\main\java\springbootdemo

    md src\main\resources

    tree /F

  3. Erstellen Sie im SpringBootEvent-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <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>
      <groupId>springbootdemo</groupId>
      <artifactId>SpringBootEvent</artifactId>
      <version>1.0-SNAPSHOT</version>
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
        </plugins>
      </build>
    </project>
    

    Sehen Sie sich hierzu weiter oben die Erläuterungen unter Spring-Boot-Webanwendung mit Tomcat und die dortige pom.xml an.

  4. Erzeugen Sie im Resources-Verzeichnis src\main\resources die Konfigurationsdatei: application.properties

    server.port=18080
    management.endpoint.shutdown.enabled=true
    

    Sehen Sie sich hierzu die Erläuterungen unter Konfiguration per application.properties an.

  5. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine RestController-Klasse hinzu: MeinRestController.java

    package springbootdemo;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class MeinRestController
    {
       @Value( "${server.port}" )
       private int port;
    
       @RequestMapping( "/rest" )
       String halloSpringBootRest( @RequestParam( required=false, defaultValue="Spring-Boot-Event-App" ) String name )
       {
          return "Hallo " + name + "!  (port=" + port + ")";
       }
    }
    

    Sehen Sie sich hierzu die Erläuterungen unter DemoRestController an.

  6. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine SpringBootApplication-Main-Klasse hinzu: ApplicationMain.java

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ApplicationMain
    {
       public static void main( String[] args )
       {
          SpringApplication application = new SpringApplication( ApplicationMain.class );
          application.addListeners(
                new MeinConfigFileApplicationListener(),
                new SpringApplicationEventListenerDevUtil() );
          application.run( args );
       }
    }
    

    Anders als in den oben gezeigten ähnlichen Main-Klassen (z.B. ControllerUndMain, ApplicationMain, ApplicationMain) werden diesmal über SpringApplication.addListeners() zwei ApplicationListener hinzugefügt.

  7. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine ConfigFileApplicationListener-Klasse hinzu: MeinConfigFileApplicationListener.java

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.context.config.ConfigFileApplicationListener;
    import org.springframework.core.env.ConfigurableEnvironment;
    
    public class MeinConfigFileApplicationListener extends ConfigFileApplicationListener
    {
       @Override
       public void postProcessEnvironment( ConfigurableEnvironment env, SpringApplication application )
       {
          SpringEventListenerDevUtil.printEventNameAndClassAndSource( "ConfigFileApplicationListener", this );
       }
    }
    

    Sehen Sie sich die Javadoc zum ConfigFileApplicationListener an.

  8. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine ApplicationListener-Klasse hinzu: SpringApplicationEventListenerDevUtil.java

    package springbootdemo;
    
    import org.springframework.boot.context.event.*;
    import org.springframework.context.ApplicationListener;
    
    /**
     * Diese EventListener-Util-Klasse ist nur waehrend der Entwicklung sinnvoll.
     * Sie demonstriert das Abfangen von SpringApplicationEvents.<br>
     * <br>
     * Damit auch sehr fruehe Events abgefangen werden, beispielsweise
     * {@link ApplicationStartedEvent}, {@link ApplicationEnvironmentPreparedEvent} und
     * {@link ApplicationPreparedEvent}, wird der Listener nicht per {@link @EventListener}
     * eingebunden, sondern als eigene {@link ApplicationListener}-Klasse,
     * die ueber {@code (new SpringApplication()).addListeners(...)} eingebunden wird.
     * @see SpringEventListenerDevUtil
     */
    public class SpringApplicationEventListenerDevUtil implements ApplicationListener<SpringApplicationEvent>
    {
       @Override
       public void onApplicationEvent( SpringApplicationEvent event )
       {
          SpringEventListenerDevUtil.printEventNameAndClassAndSource( "SpringApplicationEvent", event );
       }
    }
    

    Sehen Sie sich die Javadoc zu ApplicationListener, und SpringApplicationEvent an, und sehen Sie sich insbesondere die dort aufgelisteten "Direct Known Subclasses" an.

  9. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine EventListener-Util-Klasse hinzu: SpringEventListenerDevUtil.java

    package springbootdemo;
    
    import org.springframework.boot.context.event.*;
    import org.springframework.context.*;
    import org.springframework.context.event.*;
    import org.springframework.stereotype.Component;
    
    /**
     * Diese EventListener-Util-Klasse ist nur waehrend der Entwicklung sinnvoll.
     * Sie demonstriert das Abfangen einiger Events.<br>
     * <br>
     * Bei einigen Events, z.B. beim {@link SpringApplicationEvent}, werden bei der im Folgenden
     * gezeigten Art der Einbindung sehr fruehe Events nicht angezeigt, beispielsweise
     * {@link ApplicationStartedEvent}, {@link ApplicationEnvironmentPreparedEvent} und
     * {@link ApplicationPreparedEvent}.
     * Falls diese Events benoetigt werden, muss eine eigene {@link ApplicationListener}-Klasse
     * erstellt werden und ueber {@code SpringApplication.addListeners(...)} eingebunden werden.
     * @see SpringApplicationEventListenerDevUtil
     */
    @Component
    public class SpringEventListenerDevUtil
    {
       /**
        * Da von ApplicationEvent mehrere Events abgeleitet sind, reagiert
        * diese Listener-Methode auf verschiedene Events, beispielsweise
        * {@link ContextRefreshedEvent}, {@link ApplicationReadyEvent},
        * {@link ServletRequestHandledEvent} und {@link ContextClosedEvent}
        */
       @EventListener
       public void onEvent( ApplicationEvent event )
       {
          printEventNameAndClassAndSource( "ApplicationEvent", event );
       }
    
       /**
        * Diese Listener-Methode reagiert nur auf ein einziges Event, naemlich auf das ContextRefreshedEvent
        */
       @EventListener
       public void onEvent( ContextRefreshedEvent event )
       {
          printEventNameAndClassAndSource( "ContextRefreshedEvent", event );
       }
    
       /** Anzeige von: Event-Name, Klasse und Source */
       static void printEventNameAndClassAndSource( String eventName, Object event )
       {
          String src = ( event instanceof ApplicationEvent ) ? (" von " + ((ApplicationEvent) event).getSource()) : "";
          System.out.println( "---- " + eventName + ": " + event.getClass().getSimpleName() + src );
       }
    }
    

    Sehen Sie sich die Javadoc zur Annotation @EventListener sowie zu den Klassen ApplicationEvent, der davon abgeleiteten Klasse ApplicationContextEvent, den dort aufgelisteten "Direct Known Subclasses" und der Klasse ContextRefreshedEvent an.

  10. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootEvent]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- ApplicationMain.java
     |       |       '- MeinConfigFileApplicationListener.java
     |       |       '- MeinRestController.java
     |       |       '- SpringApplicationEventListenerDevUtil.java
     |       |       '- SpringEventListenerDevUtil.java
     |       '- [resources]
     |           '- application.properties
     '- pom.xml
    
  11. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar

    curl http://localhost:18080/rest

    start http://localhost:18080/rest

  12. Der REST-Aufruf returniert:

    Hallo Spring-Boot-Event-App!  (port=18080)
    
  13. Die Webserver-Konsole zeigt an:

    ---- SpringApplicationEvent: ApplicationStartedEvent ...
    ---- ConfigFileApplicationListener: MeinConfigFileApplicationListener
    ---- SpringApplicationEvent: ApplicationEnvironmentPreparedEvent ...
    ...
    ---- SpringApplicationEvent: ApplicationPreparedEvent ...
    ...
    ---- ContextRefreshedEvent: ContextRefreshedEvent ...
    ---- ApplicationEvent: ContextRefreshedEvent ...
    ...
    ---- ApplicationEvent: EmbeddedServletContainerInitializedEvent von ...TomcatEmbeddedServletContainer...
    ---- ApplicationEvent: ApplicationReadyEvent ...
    ---- SpringApplicationEvent: ApplicationReadyEvent ...
    ...
    ---- ApplicationEvent: ServletRequestHandledEvent von org.springframework.web.servlet.DispatcherServlet...
    ...
    ---- ApplicationEvent: ContextClosedEvent ...
    


Anzeige des Spring-Environments und der Spring-Properties

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert das vorherige Beispiel.

  2. Ersetzen Sie im src\main\java\springbootdemo-Verzeichnis den Inhalt von ApplicationMain.java durch:

    package springbootdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    @SpringBootApplication
    public class ApplicationMain
    {
       public static void main( String[] args )
       {
          SpringApplication application = new SpringApplication( ApplicationMain.class );
          application.addListeners(
                new MeinConfigFileApplicationListener(),
                new SpringApplicationEventListenerDevUtil() );
          ConfigurableApplicationContext ctx = application.run( args );
          SpringEnvironmentPrintDevUtil.printSpringEnvironment( ctx.getEnvironment(), null );
       }
    }
    

    Sehen Sie sich die Javadoc zum ConfigurableApplicationContext an.

  3. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Util-Klasse hinzu: SpringEnvironmentPrintDevUtil.java

    package springbootdemo;
    
    import java.util.*;
    import java.util.Map.Entry;
    import org.springframework.core.env.*;
    
    public class SpringEnvironmentPrintDevUtil
    {
       /**
        * Anzeige von Spring-Environment-Properties (nur waehrend Entwicklung sinnvoll).
        * @param env ConfigurableEnvironment, z.B. mit: ConfigurableApplicationContext.getEnvironment()
        *                                          oder ConfigFileApplicationListener.postProcessEnvironment()
        *                                          oder ApplicationEnvironmentPreparedEvent.getEnvironment()
        * @param onlyPropSrcName Falls null: Anzeige aller Properties; sonst: nur Props aus PropertySource mit diesem Namen
        */
       public static void printSpringEnvironment( ConfigurableEnvironment env, String onlyPropSrcName )
       {
          for( Iterator<PropertySource<?>> itr = env.getPropertySources().iterator(); itr.hasNext(); ) {
             PropertySource<?> ps = itr.next();
             if( onlyPropSrcName != null && ps.getName() != null && !ps.getName().contains( onlyPropSrcName ) ) {
                continue;
             }
             System.out.println( "-----------------------------------------" );
             System.out.println( ps.getName() + " (" + ps.getClass() + ")" );
             if( ps instanceof EnumerablePropertySource ) {
                EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) ps;
                String[] pns = eps.getPropertyNames();
                Arrays.sort( pns );
                for( String pn : pns ) {
                   System.out.println( "    " + pn + " = " + eps.getProperty( pn ) );
                }
             } else {
                System.out.println( "    PropertySource: " + ps.getSource().getClass() );
             }
          }
          System.out.println( "-----------------------------------------" );
       }
    
       /** Anzeige von Key/Value-Paaren einer Map. */
       public static void printMap( String title, Map<String,Object> mp, String prefix, String delimiter )
       {
          if( title != null ) { System.out.println( title ); }
          for( Entry<String,Object> entry : mp.entrySet() ) {
             System.out.println( prefix + entry.getKey() + delimiter + entry.getValue() );
          }
       }
    }
    

    Sehen Sie sich die Javadoc zum ConfigurableEnvironment an.

  4. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\SpringBootEvent]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       '- ApplicationMain.java
     |       |       '- MeinConfigFileApplicationListener.java
     |       |       '- MeinRestController.java
     |       |       '- SpringApplicationEventListenerDevUtil.java
     |       |       '- SpringEnvironmentPrintDevUtil.java
     |       |       '- SpringEventListenerDevUtil.java
     |       '- [resources]
     |           '- application.properties
     '- pom.xml
    
  5. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

    java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar

    curl http://localhost:18080/rest

    start http://localhost:18080/rest

  6. Die Ausgabe in der Webserver-Konsole zeigt eine lange Liste vieler Properties und Environment-Variablen an, unter anderem:

    -----------------------------------------
    systemProperties (class org.springframework.core.env.MapPropertySource)
        java.runtime.name = Java(TM) SE Runtime Environment
        java.library.path = ...
        java.vm.version = ...
        java.vm.name = ...
        user.dir = ...
        ...
    -----------------------------------------
    systemEnvironment (class org.springframework.core.env.SystemEnvironmentPropertySource)
        Path = ...
        HOMEPATH = \Users\...
        JAVA_HOME = ...
        USERDOMAIN = ...
        PROCESSOR_ARCHITECTURE = AMD64
        ...
    -----------------------------------------
    server.ports (class org.springframework.core.env.MapPropertySource)
        local.server.port = 18080
    -----------------------------------------
    applicationConfig: [classpath:/application.properties] (class org.springframework.core.env.PropertiesPropertySource)
        endpoints.shutdown.enabled = true
        server.port = 18080
    -----------------------------------------
    


Weitere Beispiele





Weitere Themen: andere TechDocs | Spring DI und AOP | Spring Batch
© 2020 Torsten Horn, Aachen