Spring Boot 1.4.4

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


Diese Webseite behandelt die nicht mehr aktuelle Spring-Boot-Version 1.4.4. Falls Sie sich für eine aktuellere Spring-Boot-Version interessieren, sehen Sie sich Spring Boot an.

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.



Inhalt

  1. Doku zu Spring, Spring Boot und Spring Data
  2. Unterschiedliche Test-Annotationen bis Spring Boot 1.4.x und ab 1.5.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 per JAX-RS
  10. Spring-Boot-Webanwendung mit JavaServer Faces (JSF)
  11. Spring-Boot-Webanwendung mit MVC und Thymeleaf
  12. Spring-Boot-Webanwendung mit Spring Security
  13. Spring-Boot-Webanwendung mit HTTPS
  14. JUnit-Modultest mit Mock für Spring-Boot-Webanwendung
  15. Integrationstest mit embedded Webserver für Spring-Boot-Webanwendung
  16. Test einer Webanwendung mit MVC-Model, MVC-View und Spring Security
  17. Actuator zur Ausgabe von Health, Status, Props und Metriken
  18. Konfiguration per application.properties
  19. Properties-Priorisierung und Profile
  20. ApplicationListener und EventListener
  21. Anzeige des Spring-Environments und der Spring-Properties
  22. Modifikation der Spring-Konfiguration zur Laufzeit
  23. BeanPostProcessor
  24. BeanDefinitionNames
  25. 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 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.


Unterschiedliche Test-Annotationen bis Spring Boot 1.4.x und ab 1.5.x

Die folgende Tabelle listet ein paar Beispiele für einige Unterschiede zwischen den Spring-Boot-Versionen 1.4.x und 1.5.x auf, fokussiert auf Tests. Genaueres zu den Unterschieden siehe: Spring Boot 1.4 Release Notes und Spring Boot 1.5 Release Notes.


 bis Spring Boot 1.4.xab 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-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/maven-v4_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>1.4.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.SpringApplicationConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @SpringApplicationConfiguration( classes = ApplicationMain.class )
    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 Annotationen @RunWith, @SpringApplicationConfiguration und @Autowired, sowie zu den Klassen SpringJUnit4ClassRunner und DataSourceProperties. (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: @SpringApplicationConfiguration ist ab Spring Boot 1.5 nicht mehr verfügbar. Als Ersatz wird @SpringBootTest verwendet, siehe: ApplicationMainTest mit 1.5.x.)

    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/maven-v4_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>1.4.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 = "application/json;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 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 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/maven-v4_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>1.4.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/maven-v4_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>1.4.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.8</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-impl</artifactId>
          <version>2.2.8</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.context.embedded.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.
    (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: Ab Spring Boot 1.5 muss org.springframework.boot.context.embedded.ServletContextInitializer durch org.springframework.boot.web.servlet.ServletContextInitializer ersetzt werden, siehe: ApplicationMain.java mit 1.5.x.)

  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.context.web.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.
    (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: Ab Spring Boot 1.5 muss org.springframework.boot.context.web.SpringBootServletInitializer durch org.springframework.boot.web.support.SpringBootServletInitializer ersetzt werden, siehe: ServletInitializer.java mit 1.5.x.)

  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.context.embedded.ServletRegistrationBean;
    import org.springframework.context.annotation.*;
    import com.sun.faces.config.FacesInitializer;
    
    @Configuration
    public class ConfigureJsfServletRegistration
    {
      @Bean
      public ServletRegistrationBean facesServletRegistration()
      {
        return new JsfServletRegistrationBean();
      }
    
      public class JsfServletRegistrationBean extends ServletRegistrationBean
      {
        public JsfServletRegistrationBean()
        {
          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: ServletRegistrationBean und ServletContext, sowie zum FacesInitializer-Parent-Interface ServletContainerInitializer.
    (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: Ab Spring Boot 1.5 muss org.springframework.boot.context.embedded.ServletRegistrationBean durch org.springframework.boot.web.servlet.ServletRegistrationBean ersetzt werden, siehe: ConfigureJsfServletRegistration.java mit 1.5.x.)

  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 auf der Object-Graph-Navigation Language (OGNL) und bietet 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/maven-v4_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>1.4.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.



Spring-Boot-Webanwendung mit Spring Security

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Sehen Sie sich zu Spring Security an:
    Getting Started: Securing a Web Application
    Spring Security Reference

  2. Voraussetzung ist das vorherige Beispiel Spring-Boot-Webanwendung mit MVC und Thymeleaf.
    Kopieren Sie dieses Projekt in ein neues Projektverzeichnis:

    cd \MeinWorkspace

    xcopy SpringBootThymeleaf SpringBootThymeleafSec\ /S

    cd SpringBootThymeleafSec

    tree /F

  3. Fügen Sie im neuen Projektverzeichnis SpringBootThymeleafSec in der pom.xml im <dependencies>-Block hinzu:

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    
  4. Ändern Sie im src\main\java\springbootdemo-Verzeichnis den Inhalt der ApplicationMain.java zu:

    package springbootdemo;
    
    import java.util.List;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @SpringBootApplication
    public class ApplicationMain extends WebMvcConfigurerAdapter
    {
       public static void main( String[] args )
       {
          SpringApplication.run( ApplicationMain.class, args );
       }
    
       @Override
       public void addViewControllers( ViewControllerRegistry registry )
       {
          registry.addViewController( "/mvc-th/login" ).setViewName( "login" );
       }
    
       @Override
       public void addArgumentResolvers( List<HandlerMethodArgumentResolver> argumentResolvers )
       {
          argumentResolvers.add( new MeineUserHandlerMethodArgumentResolver() );
       }
    }
    
  5. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die JPA-Entity-Klasse für die erlaubten Benutzer: MeineUser.java

    package springbootdemo;
    
    import java.util.*;
    import javax.persistence.*;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    @Entity
    public class MeineUser implements UserDetails
    {
       public  static final String MEINE_USER_ROLLENNAME = "MEINE_USER";
       private static final long   serialVersionUID = 1L;
    
       @Id
       private String username;
       private String password;
       private String fullname;
    
       @Override
       public String getUsername() { return username; }
       @Override
       public String getPassword() { return password; }
       public String getFullname() { return fullname; }
    
       public void setUsername( String username ) { this.username = username; }
       public void setPassword( String password ) { this.password = password; }
       public void setFullname( String fullname ) { this.fullname = fullname; }
    
       @Override
       public Collection<? extends GrantedAuthority> getAuthorities() {
          return Arrays.asList(  new SimpleGrantedAuthority( "ROLE_" + MEINE_USER_ROLLENNAME ) );
       }
    
       @Override public boolean isAccountNonExpired() { return true; }
       @Override public boolean isAccountNonLocked() { return true; }
       @Override public boolean isCredentialsNonExpired() { return true; }
       @Override public boolean isEnabled() { return true; }
    }
    

    Um das Beispiel einfach zu halten, implementiert die JPA-Entity-Klasse für die Benutzer auch gleichzeitig das UserDetails-Interface, damit Sie direkt zur Authentifizierung verwendet werden kann (siehe unten).
    Statt der hier gezeigten direkten fixen Rollen-Zuweisung in der Methode getAuthorities() wird die dem Benutzer zugeordnete Rolle normalerweise einer weiteren Datenbanktabelle entnommen. Dasselbe gilt für die vier is...()-boolean-Methoden, die hier der Einfachkeit halber alle true returnieren.

  6. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die JPA-Repository-Klasse: MeineUserRepository.java

    package springbootdemo;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface MeineUserRepository extends JpaRepository<MeineUser,String>
    {
       /* ok */
    }
    
  7. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die HandlerMethodArgumentResolver-Klasse: MeineUserHandlerMethodArgumentResolver.java

    package springbootdemo;
    
    import org.springframework.core.MethodParameter;
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.support.WebDataBinderFactory;
    import org.springframework.web.context.request.NativeWebRequest;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.method.support.ModelAndViewContainer;
    
    @Component
    public class MeineUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
    {
       @Override
       public boolean supportsParameter( MethodParameter parameter )
       {
          return MeineUser.class.isAssignableFrom( parameter.getParameterType() );
       }
    
       @Override
       public Object resolveArgument( MethodParameter parameter,
                                      ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                                      WebDataBinderFactory binderFactory ) throws Exception
       {
          Authentication auth = (Authentication) webRequest.getUserPrincipal();
          return auth != null && auth.getPrincipal() instanceof MeineUser ? auth.getPrincipal() : null;
       }
    }
    

    Siehe hierzu: HandlerMethodArgumentResolver.supportsParameter() und .resolveArgument().

  8. Erzeugen Sie im src\main\java\springbootdemo-Verzeichnis die WebSecurityConfigurerAdapter-Klasse: SecurityConfig.java

    package springbootdemo;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.*;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter
    {
       @Autowired
       protected MeineUserRepository repo;
    
       /** Spezifiziert, welche HTTP-Requests eine Autorisierung mit welcher Rolle benoetigen, und den Pfad zum Login-Dialog */
       @Override
       protected void configure( HttpSecurity http ) throws Exception
       {
          http.authorizeRequests()
              .antMatchers( "/mvc-th" ).hasRole( MeineUser.MEINE_USER_ROLLENNAME )
              .antMatchers( "/mvc-th/**" ).permitAll()
              .and()
              .formLogin().loginPage( "/mvc-th/login" ).failureUrl( "/mvc-th/login?error=true" );
       }
    
       /** Spezifiziert, wie die Authentifizierung erfolgt, im Beispiel per JPA und DB */
       @Override
       protected void configure( AuthenticationManagerBuilder auth ) throws Exception
       {
          auth.userDetailsService( userDetailsService() );
       }
    
       @Bean
       @Override
       public UserDetailsService userDetailsService()
       {
          return new UserDetailsService() {
             @Override
             public UserDetails loadUserByUsername( String username ) throws UsernameNotFoundException {
                UserDetails userDetails = repo.findOne( username );
                if( userDetails != null ) {
                   return userDetails;
                }
                throw new UsernameNotFoundException( "Benutzer '" + username + "' nicht vorhanden." );
             }
          };
       }
    }
    

    Siehe hierzu: WebSecurityConfigurerAdapter.configure(HttpSecurity) und WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder).
    Insbesondere unter HttpSecurity finden Sie viele weiterführende Code-Schnipsel.

  9. Erzeugen Sie im src\main\resources\templates-Verzeichnis die Thymeleaf-Template-Datei für den Login-Dialog: login.html

    <html xmlns:th="http://www.thymeleaf.org">
      <head>
        <title>Login</title>
        <link rel="stylesheet" th:href="@{/style.css}"></link>
      </head>
      <body onload='document.f.username.focus();'>
        <div id="loginForm">
          <h3>Login mit Benutzername und Passwort</h3>
          <div class="error" th:if="${param.error}">
            Benutzername oder Passwort falsch. Bitte noch mal versuchen.
          </div>
          <form name='f' th:action="@{/mvc-th/login}" method='POST'>
            <table>
              <tr>
                <td>Benutzername:</td>
                <td><input type='text' name='username' value='' /></td>
              </tr>
              <tr>
                <td>Passwort:</td>
                <td><input type='password' name='password' /></td>
              </tr>
              <tr>
                <td colspan='2'><input type="submit" name="submit" value="Login" /></td>
              </tr>
            </table>
          </form>
        </div>
      </body>
    </html>
    
  10. Erzeugen Sie im src\main\resources-Verzeichnis ein Datenbank-Skript zum Einfügen von Benutzern (tragen Sie Ihre Account-Daten ein): data.sql

    Merge into MEINE_USER ( username, password, fullname ) values ( 'anton', 'geheim00', 'Anton Alfa' );
    Merge into MEINE_USER ( username, password, fullname ) values ( 'berta', 'geheim07', 'Berta Beta' );
    
  11. Optional können Sie im src\main\resources\static-Verzeichnis eine Bilddatei für die Fehlerseite hinzu kopieren: MissingPage.png

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

    [\MeinWorkspace\SpringBootThymeleafSec]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       |- ApplicationMain.java
     |       |       |- MeineEntity.java
     |       |       |- MeineEntityController.java
     |       |       |- MeineEntityRepository.java
     |       |       |- MeineUser.java
     |       |       |- MeineUserHandlerMethodArgumentResolver.java
     |       |       |- MeineUserRepository.java
     |       |       '- SecurityConfig.java
     |       '- [resources]
     |           |- [static]
     |           |   |- MissingPage.png
     |           |   '- style.css
     |           |- [templates]
     |           |   |- error.html
     |           |   |- login.html
     |           |   '- MeineEntityWebseite.html
     |           |- application.properties
     |           '- data.sql
     '- pom.xml
    
  13. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

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

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

  14. Sie erhalten als erstes den Login-Dialog. Tragen Sie beispielsweise anton und geheim00 ein. Sie erreichen die Zielwebseite erst nach erfolgreicher Authentifizierung mit einem gültigen Benutzer-Account. Die Zielwebseite sieht dann so aus wie im vorherigen Beispiel.



Spring-Boot-Webanwendung mit HTTPS

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Im Prinzip können die meisten Spring-Boot-Webanwendungen so wie im Folgenden beschrieben um HTTPS erweitert werden. Um das Beispiel konkret nachvollziehen zu können, wird davon ausgegangen, dass das vorherige Beispiel Spring-Boot-Webanwendung mit Spring Security verwendet wird.
    Kopieren Sie dieses Projekt in ein neues Projektverzeichnis:

    cd \MeinWorkspace

    xcopy SpringBootThymeleafSec SpringBootThymeleafSecHttps\ /S

    cd SpringBootThymeleafSecHttps

    tree /F

  2. Um für einen ersten Test ein Schlüsselpaar und ein selbst signiertes Zertifikat zu generieren, führen Sie im Kommandozeilenfenster aus (das keytool-Kommando in einer einzigen Kommandozeile):

    cd src\main\resources

    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.

  3. Fügen Sie im Ressourcen-Verzeichnis src\main\resources zur Konfigurationsdatei application.properties hinzu:

    server.port                   = 8443
    server.ssl.key-store          = classpath:MeinKeystore.jks
    server.ssl.key-store-password = geheim
    server.ssl.key-password       = geheim
    

    Erläuterungen hierzu finden Sie unter Configure SSL.

    Falls Sie die Keystore-Datei nicht im JAR, sondern extern speichern wollen, setzen Sie beispielsweise:
    server.ssl.key-store = file:///MeinWorkspace/SpringBootThymeleafSecHttps/MeinKeystore.jks

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

    [\MeinWorkspace\SpringBootThymeleafSec]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [springbootdemo]
     |       |       |- ApplicationMain.java
     |       |       |- MeineEntity.java
     |       |       |- MeineEntityController.java
     |       |       |- MeineEntityRepository.java
     |       |       |- MeineUser.java
     |       |       |- MeineUserHandlerMethodArgumentResolver.java
     |       |       |- MeineUserRepository.java
     |       |       '- SecurityConfig.java
     |       '- [resources]
     |           |- [static]
     |           |   |- MissingPage.png
     |           |   '- style.css
     |           |- [templates]
     |           |   |- error.html
     |           |   |- login.html
     |           |   '- MeineEntityWebseite.html
     |           |- application.properties
     |           |- data.sql
     |           '- MeinKeystore.jks
     '- pom.xml
    
  5. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

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

    start https://localhost:8443/mvc-th

    Beachten Sie die geänderte Portnummer und dass https statt http verwendet werden muss. Die vorherige http-URL funktioniert nicht mehr.

  6. Im Kommandozeilenfenster wird u.a. geloggt:

    ... s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8443 (https)
    ... s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8443 (https)
  7. 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.

  8. Je nach Webbrowser kann vor der URL-Adresszeile ein Schloss als Symbol für die verschlüsselte Datenübertragung angezeigt werden:



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.SpringApplicationConfiguration;
    import org.springframework.http.MediaType;
    import org.springframework.mock.web.MockServletContext;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @SpringApplicationConfiguration( classes = MockServletContext.class )
    @WebAppConfiguration
    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 Annotationen @RunWith, @SpringApplicationConfiguration und @WebAppConfiguration, sowie zu den Klassen SpringJUnit4ClassRunner, MockServletContext, MockMvc, MockMvcBuilders, MockMvcRequestBuilders und MockMvcResultMatchers.
    (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: Ab Spring Boot 1.5 muss @SpringApplicationConfiguration und @WebAppConfiguration durch @SpringBootTest(webEnvironment=WebEnvironment.MOCK) ersetzt werden, siehe: ControllerUndMainTest.java mit 1.5.x.)

  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.SpringApplicationConfiguration;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @SpringApplicationConfiguration( classes = ControllerUndMain.class )
    @WebAppConfiguration
    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 + ";charset=UTF-8" ) )
                 .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.4.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.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.IntegrationTest;
    import org.springframework.boot.test.SpringApplicationConfiguration;
    import org.springframework.boot.test.TestRestTemplate;
    import org.springframework.http.ResponseEntity;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.web.client.RestTemplate;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @SpringApplicationConfiguration( classes = ControllerUndMain.class )
    @WebAppConfiguration
    @IntegrationTest( {"server.port=0"} )
    public class ControllerUndMainIT
    {
       @Value( "${local.server.port}" ) private int port;
       private RestTemplate restTemplate;
    
       @Before
       public void setUp()
       {
          restTemplate = new TestRestTemplate();
       }
    
       @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 Annotationen @RunWith, @SpringApplicationConfiguration, @WebAppConfiguration, @IntegrationTest und @Value, sowie zu den Klassen SpringJUnit4ClassRunner, RestTemplate, TestRestTemplate, ResponseEntity.
    (Falls Sie eine neuere Spring-Boot-Version einsetzen wollen: Ab Spring Boot 1.5 müssen einige Annotationen und Klassen durch andere ersetzt werden, siehe: ControllerUndMainIT.java mit 1.5.x.)

  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.



Test einer Webanwendung mit MVC-Model, MVC-View und Spring Security

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Als zu erweiternde per Spring Security abgesicherte MVC-Webanwendung dient das obige Beispiel Spring-Boot-Webanwendung mit Spring Security.

  2. Wechseln Sie in das Projektverzeichnis dieser Webanwendung und erstellen Sie zwei Testverzeichnisse:

    cd \MeinWorkspace\SpringBootThymeleafSec

    md src\test\java\springbootdemo

    md src\test\resources

  3. Erweitern Sie die pom.xml um folgende zwei Dependencies:

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-test</artifactId>
          <scope>test</scope>
        </dependency>
    
  4. 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
    
  5. Fügen Sie im src\test\java\springbootdemo-Verzeichnis eine JUnit-Testklasse hinzu: WebMvcSecurityTest.java

    package springbootdemo;
    
    import static org.hamcrest.Matchers.*;
    import static org.junit.Assert.*;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    import java.util.List;
    import org.junit.*;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.SpringApplicationConfiguration;
    import org.springframework.http.MediaType;
    import org.springframework.mock.web.MockHttpServletResponse;
    import org.springframework.security.test.context.support.*;
    import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
    import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.*;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @RunWith( SpringJUnit4ClassRunner.class )
    @SpringApplicationConfiguration( classes = ApplicationMain.class )
    @WebAppConfiguration
    public class WebMvcSecurityTest
    {
       @Autowired
       private WebApplicationContext webContext;
    
       private MockMvc mockMvc;
    
       @Before
       public void setupMockMvc()
       {
          mockMvc = MockMvcBuilders.webAppContextSetup( webContext )
                                   .apply( SecurityMockMvcConfigurers.springSecurity() )
                                   .build();
       }
    
       @Test
       public void testOhneAuthentication() throws Exception
       {
          mockMvc.perform( get( "/mvc-th" ) )
                 .andExpect( status().is3xxRedirection() )
                 .andExpect( header().string( "Location", "http://localhost/mvc-th/login" ) );
       }
    
       @Test
       public void testMitFehlendemCsrfToken() throws Exception
       {
          MvcResult mr = mockMvc.perform( post( "/mvc-th" ).contentType( MediaType.APPLICATION_FORM_URLENCODED )
                                                           .param( "text", "Mein Test-Text" ) )
                                .andExpect( status().is4xxClientError() )
                                .andReturn();
          MockHttpServletResponse hsr = mr.getResponse();
          assertEquals( 403, hsr.getStatus() );
          // Je nach verwendeten Versionen kann die Fehlermeldung auch anders lauten,
          // z.B.: "Expected CSRF token not found. Has your session expired?"
          assertEquals( "Could not verify the provided CSRF token because your session was not found.", hsr.getErrorMessage() );
       }
    
       @Test
       @WithUserDetails( "anton" )
       @WithMockUser( roles=MeineUser.MEINE_USER_ROLLENNAME )
       public void testMitPostUndGet() throws Exception
       {
          mockMvc.perform( get( "/mvc-th" ) )
                 .andExpect( status().isOk() )
                 .andExpect( view().name( "MeineEntityWebseite" ) )
                 .andExpect( model().attributeExists( "entitiesListe" ) )
                 .andExpect( model().attribute( "entitiesListe", hasSize( 0 ) ) )
                 .andExpect( content().contentType( MediaType.TEXT_HTML + ";charset=UTF-8" ) )
                 .andExpect( content().string( startsWith( "<html>\n  <head>\n    <title>Spring-Boot-MVC-Thymeleaf-Webanwendung</title>" ) ) );
          mockMvc.perform( post( "/mvc-th" ).contentType( MediaType.APPLICATION_FORM_URLENCODED )
                                            .with( SecurityMockMvcRequestPostProcessors.csrf() )
                                            .param( "text", "Mein Test-Text" ) )
                 .andExpect( status().is3xxRedirection() )
                 .andExpect( header().string( "Location", "/mvc-th" ) );
          MvcResult mr = mockMvc.perform( get( "/mvc-th" ) )
                 .andExpect( status().isOk() )
                 .andExpect( view().name( "MeineEntityWebseite" ) )
                 .andExpect( model().attribute( "entitiesListe", hasSize( 1 ) ) )
                 .andExpect( model().attribute( "entitiesListe", hasItem( hasProperty( "id", greaterThan( Long.valueOf( 0 ) ) ) ) ) )
                 .andExpect( model().attribute( "entitiesListe", hasItem( hasProperty( "datum", notNullValue() ) ) ) )
                 .andExpect( model().attribute( "entitiesListe", hasItem( hasProperty( "text", equalTo( "Mein Test-Text" ) ) ) ) )
                 .andReturn();
          System.out.println( "---- " + mr.getModelAndView() );
          MeineEntity me = ((List<MeineEntity>) mr.getModelAndView().getModel().get( "entitiesListe" )).get( 0 );
          assertEquals( "Mein Test-Text", me.getText() );
       }
    }
    
  6. Führen Sie den JUnit-Modultest aus

    mvn test

  7. Beachten Sie, wie der Security-Context in setupMockMvc() erzeugt wird mit:

    AbstractMockMvcBuilder.apply( SecurityMockMvcConfigurers.springSecurity() )

  8. Im Beispiel ist die testMitPostUndGet()-Testmethode sowohl mit @WithUserDetails als auch mit @WithMockUser annotiert. Die zweifache Annotierung ist unnötig und dient nur zur Demonstration der Optionen. Es genügt eine der beiden Annotationen.

  9. Die testMitPostUndGet()-Testmethode enthält drei Zeilen die jeweils beginnen mit:

    .andExpect( model().attribute( "entitiesListe", hasItem( hasProperty( ...

    Dies kann normalerweise mit samePropertyValuesAs() vereinfacht werden:

    .andExpect( model().attribute( "entitiesListe", hasItem( samePropertyValuesAs( referenzMeineEntity ) ) ) )

    In diesem Beispiel ist diese Vereinfachung nicht möglich, weil der Datumswert variiert.

  10. Falls Sie eine Fehlermeldung erhalten ähnlich zu:

    Range for response status value 403 expected:<REDIRECTION> but was:<CLIENT_ERROR>

    oder:

    Expected CSRF token not found. Has your session expired?

    oder:

    Could not verify the provided CSRF token because your session was not found.

    Dann müssen Sie beim MockMvcRequestBuilders.post()-Kommando wie oben gezeigt hinzufügen:

    MockHttpServletRequestBuilder.with( SecurityMockMvcRequestPostProcessors.csrf() )

    Siehe hierzu: Testing with CSRF Protection.

  11. Falls Sie eine Fehlermeldung erhalten ähnlich zu:

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.test.context.support.WithUserDetailsSecurityContextFactory': Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.security.core.userdetails.UserDetailsService]: No qualifying bean of type [org.springframework.security.core.userdetails.UserDetailsService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException

    Dann müssen Sie dafür sorgen, dass es eine Spring-Bean für UserDetailsService gibt. Im hier gezeigten Beispiel ist hierfür die obige Methode SecurityConfig.userDetailsService() mit @Bean annotiert.



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.security.enabled=false
    
  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/beans

    curl http://localhost:8080/autoconfig

    curl http://localhost:8080/env

    curl http://localhost:8080/configprops

    curl http://localhost:8080/mappings

    "Metrics Endpoints":

    curl http://localhost:8080/health

    curl http://localhost:8080/metrics

    curl http://localhost:8080/trace

    curl http://localhost:8080/dump

    "Miscellaneous Endpoints":

    curl http://localhost:8080/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/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
    endpoints.shutdown.enabled=true
    management.security.enabled=false
    
  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/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/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.security.enabled=false
    

    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/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/maven-v4_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>1.4.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
    endpoints.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
    -----------------------------------------
    


Modifikation der Spring-Konfiguration zur Laufzeit

Das folgende Beispiel demonstriert:

In diesem Beispiel wird zur Laufzeit die in der application.properties definierte Portnummer des REST-Services durch eine andere ersetzt. Spring startet den embedded Webserver mit der geänderten Portnummer.

Das Beispiel verwendet den ConfigFileApplicationListener. Alternativ könnte das ApplicationEnvironmentPreparedEvent verwendet werden. Als weitere Alternative wird seit Spring Boot 1.3.0 der EnvironmentPostProcessor angeboten.

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert das vorherige Beispiel.

  2. Um nicht durch die lange Liste der angezeigten Umgebungsvariablen die Übersicht zu verlieren, sollten Sie im src\main\java\springbootdemo-Verzeichnis in ApplicationMain.java folgende Zeile entfernen (oder auskommentieren):

          SpringEnvironmentPrintDevUtil.printSpringEnvironment( ctx.getEnvironment(), null );
    
  3. Ersetzen Sie im src\main\java\springbootdemo-Verzeichnis den Inhalt von MeinConfigFileApplicationListener.java durch:

    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 );
    
          String propSrcName  = "applicationConfigurationProperties";
          String propKey      = "server.port";
          String propNewValue = "28080";
          SpringEnvironmentPrintDevUtil.printSpringEnvironment( env, propSrcName );
          Object alterWert = SpringEnvironmentUtil.replacePropInSpringEnvironment( env, propSrcName, propKey, propNewValue );
          System.out.println( ( alterWert == null )
                              ? "Property " + propKey + " nicht gefunden in " + propSrcName + "."
                              : "Property " + propKey + " geaendert von " + alterWert + " zu " + propNewValue + "." );
          SpringEnvironmentPrintDevUtil.printSpringEnvironment( env, propSrcName );
       }
    }
    
  4. Fügen Sie im src\main\java\springbootdemo-Verzeichnis eine Util-Klasse hinzu: SpringEnvironmentUtil.java

    package springbootdemo;
    
    import java.util.*;
    import org.springframework.boot.env.EnumerableCompositePropertySource;
    import org.springframework.core.env.*;
    
    /**
     * Hilfsmethoden zu den Spring-Environment-Variablen und Properties in {@link ConfigurableEnvironment}.<br>
     * Ein ConfigurableEnvironment kann beispielsweise erzeugt werden per:<br>
     *    {@link ConfigurableApplicationContext#getEnvironment()},<br>
     *    {@link ConfigFileApplicationListener#postProcessEnvironment()},<br>
     *    {@link ApplicationEnvironmentPreparedEvent#getEnvironment()},<br>
     *    {@link EnvironmentPostProcessor#postProcessEnvironment()}<br>
     */
    public class SpringEnvironmentUtil
    {
       /**
        * Hinzufuegen einer einzelnen Property in einer neuen PropertySource.
        * Es darf im Spring-Environment noch keine PropertySource mit dem neuen PropertySource-Namen geben.
        * Die neue PropertySource wird vorrangig hinzugefuegt, andere Properties mit gleichem Key werden ueberdeckt.
        * @param  env          ConfigurableEnvironment, s.o. {@link SpringEnvironmentUtil}
        * @param  propSrcName  Name der neuen PropertySource, es darf noch keine PropertySource mit diesem Namen geben
        * @param  propKey      Key der Property
        * @param  propValue    Wert der Property
        */
       public static void addPropToSpringEnvironment( ConfigurableEnvironment env,
                                                      String propSrcName, String propKey, Object propValue )
       {
          Map<String,Object> props = new HashMap<String,Object>();
          props.put( propKey, propValue );
          addPropsToSpringEnvironment( env, propSrcName, props );
       }
    
       /**
        * Hinzufuegen mehrerer neuer Properties in einer neuen PropertySource.
        * Es darf im Spring-Environment noch keine PropertySource mit dem neuen PropertySource-Namen geben.
        * Die neue PropertySource wird vorrangig hinzugefuegt, andere Properties mit gleichem Key werden ueberdeckt.
        * @param  env          ConfigurableEnvironment, s.o. {@link SpringEnvironmentUtil}
        * @param  propSrcName  Name der neuen PropertySource, es darf noch keine PropertySource mit diesem Namen geben
        * @param  props        Hinzuzufuegende Properties
        */
       public static void addPropsToSpringEnvironment( ConfigurableEnvironment env,
                                                       String propSrcName, Map<String,Object> props )
       {
          PropertySource<?> propertySource = new MapPropertySource( propSrcName, props );
          env.getPropertySources().addFirst( propertySource );
       }
    
       /**
        * Ersetzen des Werts einer einzelnen Property in einer vorhandenen PropertySource.
        * Funktioniert nur, wenn es die PropertySource bereits gibt und diese eine Map oder EnumerableCompositePropertySource enthaelt.
        * @param  env          ConfigurableEnvironment, s.o. {@link SpringEnvironmentUtil}
        * @param  propSrcName  Name der PropertySource, z.B. "applicationConfigurationProperties" oder
        *                                                    "applicationConfig: [classpath:/application.properties]"
        * @param  propKey      Key der Property
        * @param  propNewValue Neuer Wert der Property
        * @return vorheriger Wert der Property, falls die Property geaendert werden konnte; sonst null
        */
       @SuppressWarnings("unchecked")
       public static Object replacePropInSpringEnvironment( ConfigurableEnvironment env,
                                                            String propSrcName, String propKey, String propNewValue )
       {
          MutablePropertySources mps = env.getPropertySources();
          PropertySource<?> ps = mps.get( propSrcName );
          if( ps != null ) {
             Object srcObj = ps.getSource();
             if( srcObj instanceof List<?> ) {
                for( Object obj1 : (List<?>) srcObj ) {
                   if( obj1 instanceof EnumerableCompositePropertySource ) {
                      Collection<PropertySource<?>> coll = ((EnumerableCompositePropertySource) obj1).getSource();
                      if( coll instanceof Set ) {
                         for( Object obj2 : (Set<?>) coll ) {
                            if( obj2 instanceof PropertiesPropertySource ) {
                               srcObj = ((PropertiesPropertySource) obj2).getSource();
                            }
                         }
                      }
                   }
                }
             }
             if( srcObj instanceof Map<?,?> ) {
                try {
                   return ((Map<String,Object>) srcObj).put( propKey, propNewValue );
                } catch( ClassCastException e ) { /* ok */ }
             }
          }
          return null;
       }
    
       /**
        * Ermittlung aller Environment-Werte bzw. Properties in einer PropertySource.
        * @param  env          ConfigurableEnvironment, s.o. {@link SpringEnvironmentUtil}
        * @param  propSrcName  Name der PropertySource, z.B. "applicationConfigurationProperties" oder
        *                                                    "applicationConfig: [classpath:/application.properties]"
        */
       public static Map<String,Object> getMapFromSpringEnvironment( ConfigurableEnvironment env, String propSrcName )
       {
          Map<String,Object> mp = new TreeMap<String,Object>();
          for( Iterator<PropertySource<?>> itr = env.getPropertySources().iterator(); itr.hasNext(); ) {
             PropertySource<?> ps = itr.next();
             if( propSrcName != null && ps.getName() != null && !ps.getName().contains( propSrcName ) ) {
                continue;
             }
             if( ps instanceof EnumerablePropertySource ) {
                EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) ps;
                String[] pns = eps.getPropertyNames();
                for( String pn : pns ) {
                   mp.put( pn, eps.getProperty( pn ) );
                }
             }
          }
          return mp;
       }
    }
    

    Sehen Sie sich die Javadoc zum ConfigurableEnvironment an.

  5. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

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

    Diese Aufrufe funktionieren nicht mehr:

    curl http://localhost:18080/rest

    start http://localhost:18080/rest

    Stattdessen muss jetzt die geänderte Portnummer 28080 verwendet werden:

    curl http://localhost:28080/rest

    start http://localhost:28080/rest

  6. Die Ausgabe in der Webserver-Konsole protokoliert die Modifikation der Portnummer:

    ---- ConfigFileApplicationListener: MeinConfigFileApplicationListener
    -----------------------------------------
    applicationConfigurationProperties ...
        endpoints.shutdown.enabled = true
        server.port = 18080
    -----------------------------------------
    Property server.port geaendert von 18080 zu 28080.
    -----------------------------------------
    applicationConfigurationProperties ...
        endpoints.shutdown.enabled = true
        server.port = 28080
    -----------------------------------------
    
  7. Alternativ zum gezeigten Ersetzen eines Werts einer Property in einer vorhandenen PropertySource mit der replacePropInSpringEnvironment()-Methode können Sie stattdessen eine neue PropertySource mit dem gewünschten Key/Value-Paar hinzufügen, wie es die addPropToSpringEnvironment()-Methode zeigt. Da die neue PropertySource vorrangig hinzugefügt wird, kommen andere Properties mit dem gleichem Key nicht mehr zur Anwendung, obwohl sie weiterhin vorhanden sind.

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

    package springbootdemo;
    
    import java.util.Map;
    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 );
    
          String propSrcName = "meineLaufzeitProp";
          String propKey     = "server.port";
          String propValue   = "28080";
          SpringEnvironmentUtil.addPropToSpringEnvironment( env, propSrcName, propKey, propValue );
          Map<String,Object> mp1 = SpringEnvironmentUtil.getMapFromSpringEnvironment( env, "applicationConfigurationProperties" );
          Map<String,Object> mp2 = SpringEnvironmentUtil.getMapFromSpringEnvironment( env, propSrcName );
          SpringEnvironmentPrintDevUtil.printMap( "applicationConfigurationProperties:", mp1, "    ", " = " );
          SpringEnvironmentPrintDevUtil.printMap( propSrcName + ":", mp2, "    ", " = " );
          System.out.println( "Resultierender Wert: " + propKey + " = " + env.getProperty( propKey ) );
          System.out.println( "----------------------------------------" );
       }
    }
    
  8. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

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

    curl http://localhost:28080/rest

    start http://localhost:28080/rest

  9. Die Ausgabe in der Webserver-Konsole protokoliert, dass es jetzt zwei Properties mit dem Key server.port gibt, und dass sich die neue Portnummer durchsetzt:

    ---- ConfigFileApplicationListener: MeinConfigFileApplicationListener
    applicationConfigurationProperties:
        endpoints.shutdown.enabled = true
        server.port = 18080
    meineLaufzeitProp:
        server.port = 28080
    Resultierender Wert: server.port = 28080
    ----------------------------------------
    


BeanPostProcessor

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Dieses Beispiel erweitert das vorherige Beispiel.

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

    package springbootdemo;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    @Component
    public class SpringBeanPostProcessorUtil implements BeanPostProcessor
    {
       private int i;
    
       @Override
       public Object postProcessBeforeInitialization( Object bean, String beanName ) throws BeansException
       {
          System.out.println( "---- (" + ++i + ") BeforeInitialization " + beanName + " (" + bean.getClass().getSimpleName() + ")" );
          return bean;
       }
    
       @Override
       public Object postProcessAfterInitialization( Object bean, String beanName ) throws BeansException
       {
          System.out.println( "---- (" + ++i + ") AfterInitialization  " + beanName );
          return bean;
       }
    }
    
  3. Führen Sie im Kommandozeilenfenster aus:

    mvn clean package

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

    curl http://localhost:28080/rest

    start http://localhost:28080/rest

  4. Die Ausgabe in der Webserver-Konsole protokoliert die Initialisierungen der Beans und enthält viele Zeilen ähnlich zu:

    ...
    ---- (1) BeforeInitialization ...EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat ...
    ---- (2) AfterInitialization  ...EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat
    ---- (3) BeforeInitialization tomcatEmbeddedServletContainerFactory ...
    ...
    


BeanDefinitionNames

Das folgende Beispiel demonstriert:

Führen Sie folgende Schritte aus:

  1. Für dieses Beispiel kann ein beliebiges Spring-Boot-Beispiel verwendet werden.

  2. Ermitteln Sie die Spring-Applikations-Main-Klasse, also die Klasse, in der eine der SpringApplication.run(...,...)- oder die application.run(...)-Methode aufgerufen wird.
    In den vorherigen Beispielen war dies entweder die ControllerUndMain- oder die ApplicationMain-Klasse.

  3. Falls noch nicht geschehen, ergänzen Sie diese Klasse um die benötigten Imports, beispielsweise:

    import java.util.Arrays;
    import org.springframework.context.*;

  4. Falls noch nicht geschehen, speichern Sie beim run()-Aufruf den Rückgabewert, beispielsweise so:

          ApplicationContext ctx = SpringApplication.run( ControllerUndMain.class, args );

    oder so:

          ConfigurableApplicationContext ctx = application.run( args );

  5. Fügen Sie in derselben Klasse folgende Zeilen hinzu:

          System.out.println( "-------- BeanDefinitionNames:" );
          String[] beanNames = ctx.getBeanDefinitionNames();
          Arrays.sort( beanNames );
          for( String beanName : beanNames ) {
             System.out.println( beanName );
          }
          System.out.println( "--------" );

  6. Bauen Sie das Projekt:

    mvn clean package

    Führen Sie das Programm aus, beispielsweise so (passen Sie den Projektnamen, die Portnummer und die URL an):

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

    curl http://localhost:8080/web

    bzw. so:

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

    curl http://localhost:28080/rest

  7. Die Ausgabe in der Webserver-Konsole listet die BeanDefinitionNames:

    ...
    -------- BeanDefinitionNames:
    applicationMain
    basicErrorController
    beanNameHandlerMapping
    beanNameViewResolver
    ...
    


Weitere Beispiele





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