Spring Boot Tutorial - Booten Sie eine einfache Anwendung

1. Übersicht

Spring Boot ist eine eigenständige, auf Konventionen und Konfigurationen ausgerichtete Erweiterung der Spring-Plattform - äußerst nützlich, um mit minimalem Aufwand loszulegen und eigenständige Anwendungen für die Produktion zu erstellen.

Dieses Tutorial ist ein Ausgangspunkt für Boot - eine Möglichkeit, auf einfache Weise mit einer einfachen Webanwendung zu beginnen.

Wir werden einige Kernkonfigurationen, ein Front-End, eine schnelle Datenmanipulation und die Ausnahmebehandlung behandeln.

2. Setup

Verwenden wir zunächst Spring Initializr, um die Basis für unser Projekt zu generieren.

Das generierte Projekt basiert auf dem übergeordneten Boot:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE  

Die anfänglichen Abhängigkeiten werden recht einfach sein:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-data-jpa   com.h2database h2 

3. Anwendungskonfiguration

Als nächstes konfigurieren wir eine einfache Hauptklasse für unsere Anwendung:

@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

Beachten Sie, wie wir @SpringBootApplication als primäre Anwendungskonfigurationsklasse verwenden. hinter den Kulissen entspricht dies @Configuration , @EnableAutoConfiguration und @ComponentScan zusammen.

Schließlich definieren wir eine einfache application.properties- Datei, die derzeit nur eine Eigenschaft hat:

server.port=8081 

server.port ändert den Serverport von Standard 8080 auf 8081; Es sind natürlich noch viele weitere Spring Boot-Eigenschaften verfügbar.

4. Einfache MVC-Ansicht

Fügen wir nun mit Thymeleaf ein einfaches Frontend hinzu.

Zuerst müssen wir die Abhängigkeit von Spring-Boot-Starter-Thymeleaf zu unserer pom.xml hinzufügen :

 org.springframework.boot spring-boot-starter-thymeleaf  

Dies aktiviert Thymeleaf standardmäßig - es ist keine zusätzliche Konfiguration erforderlich.

Wir können es jetzt in unserer application.properties konfigurieren :

spring.thymeleaf.cache=false spring.thymeleaf.enabled=true spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.application.name=Bootstrap Spring Boot 

Als nächstes definieren wir einen einfachen Controller und eine grundlegende Homepage - mit einer Willkommensnachricht:

@Controller public class SimpleController { @Value("${spring.application.name}") String appName; @GetMapping("/") public String homePage(Model model) { model.addAttribute("appName", appName); return "home"; } } 

Zum Schluss hier unsere home.html :

 Home Page  

Welcome to Our App

Beachten Sie, wie wir eine Eigenschaft verwendet haben, die wir in unseren Eigenschaften definiert haben - und diese dann eingefügt haben, damit wir sie auf unserer Homepage anzeigen können.

5. Sicherheit

Als nächstes fügen wir unserer Anwendung Sicherheit hinzu - indem wir zuerst den Sicherheitsstarter einschließen:

 org.springframework.boot spring-boot-starter-security  

Inzwischen bemerken Sie hoffentlich ein Muster - die meisten Spring-Bibliotheken können mithilfe einfacher Boot-Starter problemlos in unser Projekt importiert werden .

Sobald die Spring-Boot-Starter-Sicherheitsabhängigkeit vom Klassenpfad der Anwendung abhängt, werden alle Endpunkte standardmäßig mit httpBasic oder formLogin gesichert, basierend auf der Content- Negotiation -Strategie von Spring Security.

Wenn wir den Starter im Klassenpfad haben, sollten wir daher normalerweise unsere eigene benutzerdefinierte Sicherheitskonfiguration definieren, indem wir die WebSecurityConfigurerAdapter- Klasse erweitern:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .permitAll() .and().csrf().disable(); } }

In unserem Beispiel erlauben wir den uneingeschränkten Zugriff auf alle Endpunkte.

Natürlich ist Spring Security ein umfangreiches Thema, das in einigen Konfigurationszeilen nicht einfach behandelt werden kann. Ich empfehle Ihnen daher auf jeden Fall, sich eingehender mit dem Thema zu befassen.

6. Einfache Beharrlichkeit

Beginnen wir mit unserem Datenmodell definieren - eine einfache Buch Einheit:

@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Column(nullable = false, unique = true) private String title; @Column(nullable = false) private String author; }

Und sein Repository, das Spring Data hier gut nutzt:

public interface BookRepository extends CrudRepository { List findByTitle(String title); }

Schließlich müssen wir natürlich unsere neue Persistenzschicht konfigurieren:

@EnableJpaRepositories("com.baeldung.persistence.repo") @EntityScan("com.baeldung.persistence.model") @SpringBootApplication public class Application { ... }

Beachten Sie, dass wir verwenden:

  • @EnableJpaRepositories , um das angegebene Paket nach Repositorys zu durchsuchen
  • @EntityScan , um unsere JPA-Entitäten abzuholen

Um die Sache einfach zu halten, verwenden wir hier eine H2-In-Memory-Datenbank, damit wir beim Ausführen des Projekts keine externen Abhängigkeiten haben.

Sobald wir die H2-Abhängigkeit einbezogen haben, erkennt Spring Boot sie automatisch und richtet unsere Persistenz ein, ohne dass eine zusätzliche Konfiguration erforderlich ist, außer den Datenquelleneigenschaften:

spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password= 

Of course, like security, persistence is a broader topic than this basic set here, and one you should certainly explore further.

7. Web and the Controller

Next, let's have a look at a web tier – and we'll start that by setting up a simple controller – the BookController.

We'll implement basic CRUD operations exposing Book resources with some simple validation:

@RestController @RequestMapping("/api/books") public class BookController { @Autowired private BookRepository bookRepository; @GetMapping public Iterable findAll() { return bookRepository.findAll(); } @GetMapping("/title/{bookTitle}") public List findByTitle(@PathVariable String bookTitle) { return bookRepository.findByTitle(bookTitle); } @GetMapping("/{id}") public Book findOne(@PathVariable Long id) { return bookRepository.findById(id) .orElseThrow(BookNotFoundException::new); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Book create(@RequestBody Book book) { return bookRepository.save(book); } @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { bookRepository.findById(id) .orElseThrow(BookNotFoundException::new); bookRepository.deleteById(id); } @PutMapping("/{id}") public Book updateBook(@RequestBody Book book, @PathVariable Long id) { if (book.getId() != id) { throw new BookIdMismatchException(); } bookRepository.findById(id) .orElseThrow(BookNotFoundException::new); return bookRepository.save(book); } } 

Given this aspect of the application is an API, we made use of the @RestController annotation here – which equivalent to a @Controller along with @ResponseBody – so that each method marshalls the returned resource right to the HTTP response.

Just one note worth pointing out – we're exposing our Book entity as our external resource here. That's fine for our simple application here, but in a real-world application, you will likely want to separate these two concepts.

8. Error Handling

Now that the core application is ready to go, let's focus on a simple centralized error handling mechanism using @ControllerAdvice:

@ControllerAdvice public class RestExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ BookNotFoundException.class }) protected ResponseEntity handleNotFound( Exception ex, WebRequest request) { return handleExceptionInternal(ex, "Book not found", new HttpHeaders(), HttpStatus.NOT_FOUND, request); } @ExceptionHandler({ BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class }) public ResponseEntity handleBadRequest( Exception ex, WebRequest request) { return handleExceptionInternal(ex, ex.getLocalizedMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST, request); } } 

Beyond the standard exceptions we're handling here, we're also using a custom exception:

BookNotFoundException:

public class BookNotFoundException extends RuntimeException { public BookNotFoundException(String message, Throwable cause) { super(message, cause); } // ... } 

This should give you an idea of what's possible with this global exception handling mechanism. If you'd like to see a full implementation, have a look at the in-depth tutorial.

Note that Spring Boot also provides an /error mapping by default. We can customize its view by creating a simple error.html:

 Error Occurred  [status] error 

message

Like most other aspects in Boot, we can control that with a simple property:

server.error.path=/error2

9. Testing

Finally, let's test our new Books API.

We can make use of @SpringBootTest to load the application context and verify there are no errors when running the app:

@RunWith(SpringRunner.class) @SpringBootTest public class SpringContextTest { @Test public void contextLoads() { } }

Next, let's add a JUnit test that verifies the calls to the API we're written, using RestAssured:

public class SpringBootBootstrapLiveTest { private static final String API_ROOT = "//localhost:8081/api/books"; private Book createRandomBook() { Book book = new Book(); book.setTitle(randomAlphabetic(10)); book.setAuthor(randomAlphabetic(15)); return book; } private String createBookAsUri(Book book) { Response response = RestAssured.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(book) .post(API_ROOT); return API_ROOT + "/" + response.jsonPath().get("id"); } } 

First, we can try to find books using variant methods:

@Test public void whenGetAllBooks_thenOK() { Response response = RestAssured.get(API_ROOT); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); } @Test public void whenGetBooksByTitle_thenOK() { Book book = createRandomBook(); createBookAsUri(book); Response response = RestAssured.get( API_ROOT + "/title/" + book.getTitle()); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); assertTrue(response.as(List.class) .size() > 0); } @Test public void whenGetCreatedBookById_thenOK() { Book book = createRandomBook(); String location = createBookAsUri(book); Response response = RestAssured.get(location); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); assertEquals(book.getTitle(), response.jsonPath() .get("title")); } @Test public void whenGetNotExistBookById_thenNotFound() { Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4)); assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode()); } 

Next, we'll test creating a new book:

@Test public void whenCreateNewBook_thenCreated() { Book book = createRandomBook(); Response response = RestAssured.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(book) .post(API_ROOT); assertEquals(HttpStatus.CREATED.value(), response.getStatusCode()); } @Test public void whenInvalidBook_thenError() { Book book = createRandomBook(); book.setAuthor(null); Response response = RestAssured.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(book) .post(API_ROOT); assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode()); } 

Update an existing book:

@Test public void whenUpdateCreatedBook_thenUpdated() { Book book = createRandomBook(); String location = createBookAsUri(book); book.setId(Long.parseLong(location.split("api/books/")[1])); book.setAuthor("newAuthor"); Response response = RestAssured.given() .contentType(MediaType.APPLICATION_JSON_VALUE) .body(book) .put(location); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); response = RestAssured.get(location); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); assertEquals("newAuthor", response.jsonPath() .get("author")); } 

And delete a book:

@Test public void whenDeleteCreatedBook_thenOk() { Book book = createRandomBook(); String location = createBookAsUri(book); Response response = RestAssured.delete(location); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); response = RestAssured.get(location); assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode()); } 

10. Conclusion

This was a quick but comprehensive intro to Spring Boot.

Wir haben hier natürlich kaum an der Oberfläche gekratzt - es gibt noch viel mehr in diesem Framework, das wir in einem einzigen Intro-Artikel behandeln können.

Genau deshalb haben wir nicht nur einen einzigen Artikel über Boot auf der Website.

Der vollständige Quellcode unserer Beispiele hier ist wie immer auf GitHub verfügbar.