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.
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.x | ab 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 |
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:
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.
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
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.
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.
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.
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.
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.
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
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
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]
Sehen Sie sich die verwendeten Libs an:
mvn dependency:tree
In dem weiter unten gezeigten Beispiel Spring-Boot-Webanwendung mit MVC und Thymeleaf wird eine sehr ähnliche JPA-Anwendung zu einer Webanwendung erweitert.
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.
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.
DB | pom.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 ..., ... |
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
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
Führen Sie den JUnit-Modultest aus:
mvn clean test
Ein Spring-Boot-/Spring-Data-Beispiel zur NoSQL-Datenbank MongoDB finden Sie unter: MongoDB mit Spring.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
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.
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
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.
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.
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\SpringBootWeb] |- [src] | '- [main] | '- [java] | '- [springbootdemo] | '- ControllerUndMain.java '- pom.xml
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:
Sie erhalten:
Hallo Spring-Boot-Web-Welt!
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.)
Beenden Sie die Anwendung mit Strg+C.
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) ...
Sehen Sie sich die verwendeten Libs an:
mvn dependency:tree
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.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Das Beispiel erweitert das vorherige Beispiel Spring-Boot-Webanwendung mit Tomcat.
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>
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
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)
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) ...
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Für dieses Beispiel dient ein beliebiges der beiden vorherigen Beispiele Spring-Boot-Webanwendung mit Tomcat oder Spring-Boot-Webanwendung mit Jetty als Ausgangspunkt.
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.
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
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:
Sie erhalten:
Hallo Spring-Boot-REST-Welt!
bzw.
Hallo MeinName!
Die vorherige Funktionalität steht weiterhin zusätzlich zur Verfügung:
curl http://localhost:8080/web
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:
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.
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.
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
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:
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:
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
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>
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 ); } }
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 ); } }
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.
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
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:
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
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
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.
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.
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.)
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.)
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.)
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.
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.
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
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootJsf-1.0-SNAPSHOT.jar
Die Webseite zeigt an:
Spring-Boot-JSF-Demo Hallo JSF mit Spring Boot
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:
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
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>
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 ); } }
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.
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.
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.
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.
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 }
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..
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>
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
Führen Sie im Kommandozeilenfenster aus:
mvn package
java -jar target/SpringBootThymeleaf-1.0-SNAPSHOT.jar
Tragen Sie auf der Webseite mehrmals Text ein und speichern Sie.
Die Webseite zeigt beispielsweise an:
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.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Sehen Sie sich zu
Spring Security an:
Getting Started: Securing a Web Application
Spring Security Reference
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
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>
Ä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() ); } }
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.
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 */ }
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().
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.
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>
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' );
Optional können Sie im src\main\resources\static-Verzeichnis eine Bilddatei für die Fehlerseite hinzu kopieren: MissingPage.png
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
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootThymeleaf-1.0-SNAPSHOT.jar
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.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
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
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.
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
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
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.
Im Kommandozeilenfenster wird u.a. geloggt:
... s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8443 (https)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.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.
Je nach Webbrowser kann vor der URL-Adresszeile ein Schloss als Symbol für die verschlüsselte Datenübertragung angezeigt werden:
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
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.
Erzeugen Sie den Test-Verzeichnisbaum:
cd \MeinWorkspace\SpringBootWeb
md src\test\java\springbootdemo
tree /F
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>
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.)
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.
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
Führen Sie die JUnit-Modultests aus:
mvn clean test
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Dieses Beispiel erweitert das vorherige Beispiel JUnit-Modultest mit Mock für Spring-Boot-Webanwendung.
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>
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.)
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
Führen Sie den Integrationstest aus:
mvn clean verify
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.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Als zu erweiternde per Spring Security abgesicherte MVC-Webanwendung dient das obige Beispiel Spring-Boot-Webanwendung mit Spring Security.
Wechseln Sie in das Projektverzeichnis dieser Webanwendung und erstellen Sie zwei Testverzeichnisse:
cd \MeinWorkspace\SpringBootThymeleafSec
md src\test\java\springbootdemo
md src\test\resources
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>
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
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() ); } }
Führen Sie den JUnit-Modultest aus
mvn test
Beachten Sie, wie der Security-Context in setupMockMvc() erzeugt wird mit:
AbstractMockMvcBuilder.apply( SecurityMockMvcConfigurers.springSecurity() )
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.
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.
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.
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.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
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.).
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>
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
Führen Sie wie oben gezeigt im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar
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":
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 }
...
Eine Liste der "Endpoints" finden Sie unter Actuator Endpoints. Erläuterungen zu den Metriken finden Sie unter Actuator Metrics.
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Dieses Beispiel erweitert das vorherige Beispiel Actuator zur Ausgabe von Health, Status, Props und Metriken.
Ü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.
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
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
Bauen Sie die Anwendung neu:
mvn clean package
java -jar target/SpringBootWeb-1.0-SNAPSHOT.jar
Überprüfen Sie, dass die bisherige URL nicht mehr funktioniert:
Testen Sie die neue URL:
Lesen Sie die applicationConfig-Environment-Variablen aus:
curl http://localhost:18080/env
{ ... "applicationConfig: [classpath:/application.properties]":{"endpoints.shutdown.enabled":"true","server.port":"18080"} }
Überprüfen Sie, dass ein Shutdown per curl jetzt funktioniert:
curl -X POST localhost:18080/shutdown
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
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Dieses Beispiel erweitert eines der beiden vorherigen Beispiele Actuator zur Ausgabe von Health, Status, Props und Metriken oder Konfiguration per application.properties.
Erzeugen Sie Verzeichnisse für Properties-Dateien:
cd \MeinWorkspace\SpringBootWeb
md config
md src\main\resources
Ä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 ); } }
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
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
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
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
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
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
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} }
Ü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.
Beachten Sie Folgendes:
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:
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
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.
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.
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.
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.
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.
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.
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.
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
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar
curl http://localhost:18080/rest
Der REST-Aufruf returniert:
Hallo Spring-Boot-Event-App! (port=18080)
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 ...
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Dieses Beispiel erweitert das vorherige Beispiel.
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.
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.
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
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar
curl http://localhost:18080/rest
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 -----------------------------------------
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:
Dieses Beispiel erweitert das vorherige Beispiel.
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 );
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 ); } }
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.
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
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 -----------------------------------------
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( "----------------------------------------" ); } }
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar
curl http://localhost:28080/rest
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 ----------------------------------------
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Dieses Beispiel erweitert das vorherige Beispiel.
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; } }
Führen Sie im Kommandozeilenfenster aus:
mvn clean package
java -jar target/SpringBootEvent-1.0-SNAPSHOT.jar
curl http://localhost:28080/rest
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 ... ...
Das folgende Beispiel demonstriert:
Führen Sie folgende Schritte aus:
Für dieses Beispiel kann ein beliebiges Spring-Boot-Beispiel verwendet werden.
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.
Falls noch nicht geschehen, ergänzen Sie diese Klasse um die benötigten Imports, beispielsweise:
import java.util.Arrays;
import org.springframework.context.*;
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 );
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( "--------" );
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
Die Ausgabe in der Webserver-Konsole listet die BeanDefinitionNames:
... -------- BeanDefinitionNames: applicationMain basicErrorController beanNameHandlerMapping beanNameViewResolver ...