Spring Boot-zelfstudie - Bootstrap een eenvoudige applicatie

1. Overzicht

Spring Boot is een eigenzinnige, congres-over-configuratie gerichte toevoeging aan het Spring-platform - zeer handig om met minimale inspanning aan de slag te gaan en stand-alone applicaties van productiekwaliteit te creëren.

Deze tutorial is een startpunt voor Boot - een manier om op een eenvoudige manier aan de slag te gaan, met een eenvoudige webapplicatie.

We zullen enkele kernconfiguraties, een front-end, snelle gegevensmanipulatie en het afhandelen van uitzonderingen bespreken.

2. Installatie

Laten we eerst Spring Initializr gebruiken om de basis voor ons project te genereren.

Het gegenereerde project is afhankelijk van de Boot-ouder:

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

De aanvankelijke afhankelijkheden zullen vrij eenvoudig zijn:

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

3. Applicatieconfiguratie

Vervolgens configureren we een eenvoudig hoofd klasse voor onze applicatie:

@SpringBootApplication openbare klasse Toepassing {openbare statische leegte hoofd (String [] args) {SpringApplication.run (Application.class, args); }} 

Merk op hoe we het gebruiken @SpringBootApplication als onze primaire toepassingsconfiguratieklasse; achter de schermen, dat is gelijk aan @Configuratie, @EnableAutoConfiguration, en @BuienRadarNL samen.

Ten slotte zullen we een eenvoudig definiëren application.properties bestand - dat voorlopig maar één eigenschap heeft:

server.port = 8081 

Server poort verandert de serverpoort van de standaard 8080 in 8081; er zijn natuurlijk nog veel meer Spring Boot-eigenschappen beschikbaar.

4. Eenvoudige MVC-weergave

Laten we nu een eenvoudig front-end toevoegen met Thymeleaf.

Eerst moeten we de spring-boot-starter-thymeleaf afhankelijkheid van onze pom.xml:

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

Dat maakt Thymeleaf standaard mogelijk - er is geen extra configuratie nodig.

We kunnen het nu configureren in ons application.properties:

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

Vervolgens definiëren we een eenvoudige controller en een eenvoudige startpagina - met een welkomstbericht:

@Controller openbare klasse SimpleController {@Value ("$ {spring.application.name}") String appName; @GetMapping ("/") public String homePage (modelmodel) {model.addAttribute ("appName", appName); terug naar huis"; }} 

Eindelijk, hier is onze home.html:

 Startpagina 

Welkom bij onze app

Merk op hoe we een eigenschap hebben gebruikt die we in onze eigendommen hebben gedefinieerd - en die vervolgens hebben geïnjecteerd zodat we deze op onze startpagina kunnen weergeven.

5. Beveiliging

Laten we vervolgens beveiliging aan onze applicatie toevoegen - door eerst de beveiligingsstarter toe te voegen:

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

Inmiddels merk je hopelijk een patroon op - De meeste Spring-bibliotheken kunnen eenvoudig in ons project worden geïmporteerd met behulp van eenvoudige opstartprogramma's.

Zodra het spring-boot-starter-security afhankelijkheid van het klassenpad van de applicatie - alle eindpunten worden standaard beveiligd met behulp van een van beide httpBasic of formLogin gebaseerd op de onderhandelingsstrategie van Spring Security.

Daarom moeten we, als we de starter op het classpath hebben, gewoonlijk onze eigen aangepaste beveiligingsconfiguratie definiëren door de extensie WebSecurityConfigurerAdapter klasse:

@Configuration @EnableWebSecurity openbare klasse SecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) gooit uitzondering {http.authorizeRequests () .anyRequest () .permitAll () .en (). Csrf (). Uitschakelen (); }}

In ons voorbeeld staan ​​we onbeperkte toegang toe tot alle eindpunten.

Spring Security is natuurlijk een uitgebreid onderwerp dat niet gemakkelijk in een paar configuratieregels kan worden behandeld - dus ik moedig je zeker aan om dieper op het onderwerp in te gaan.

6. Eenvoudige volharding

Laten we beginnen met het definiëren van ons datamodel - een eenvoudig Boek entiteit:

@Entity public class Book {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé lang ID; @Column (nullable = false, unique = true) private String-titel; @Column (nullable = false) private String-auteur; }

En de repository, die hier goed gebruik maakt van Spring Data:

openbare interface BookRepository breidt CrudRepository {List findByTitle (String-titel) uit; }

Ten slotte moeten we natuurlijk onze nieuwe persistentielaag configureren:

@EnableJpaRepositories ("com.baeldung.persistence.repo") @EntityScan ("com.baeldung.persistence.model") @SpringBootApplication openbare klasse Applicatie {...}

Merk op dat we gebruiken:

  • @EnableJpaRepositories om het opgegeven pakket te scannen op repositories
  • @EntityScan om onze PPV-entiteiten op te halen

Om het simpel te houden, gebruiken we hier een H2 in-memory database - zodat we geen externe afhankelijkheden hebben wanneer we het project uitvoeren.

Zodra we H2-afhankelijkheid opnemen, Spring Boot detecteert het automatisch en stelt onze volharding in zonder dat er extra configuratie nodig is, behalve de eigenschappen van de gegevensbron:

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 = 

Natuurlijk is volharding, net als beveiliging, een breder onderwerp dan deze basisset hier, en een die je zeker verder moet onderzoeken.

7. Web en de controller

Laten we vervolgens eens kijken naar een weblaag - en we beginnen daarmee door een eenvoudige controller in te stellen - de BookController.

We zullen basis CRUD-bewerkingen implementeren die blootstellen Boek bronnen met enkele eenvoudige validatie:

@RestController @RequestMapping ("/ api / books") openbare klasse BookController {@Autowired privé BookRepository bookRepository; @GetMapping openbare herhaalbare findAll () {return bookRepository.findAll (); } @GetMapping ("/ title / {bookTitle}") openbare lijst findByTitle (@PathVariable String bookTitle) {return bookRepository.findByTitle (bookTitle); } @GetMapping ("/ {id}") openbaar boek findOne (@PathVariable Lange id) {return bookRepository.findById (id) .orElseThrow (BookNotFoundException :: nieuw); } @PostMapping @ResponseStatus (HttpStatus.CREATED) openbaar boek aanmaken (@RequestBody Book-boek) {return bookRepository.save (boek); } @DeleteMapping ("/ {id}") openbare ongeldige verwijdering (@PathVariable Lange id) {bookRepository.findById (id) .orElseThrow (BookNotFoundException :: nieuw); bookRepository.deleteById (id); } @PutMapping ("/ {id}") openbaar Boek updateBook (@RequestBody Boek boek, @PathVariable Lange id) {if (book.getId ()! = Id) {gooi nieuwe BookIdMismatchException (); } bookRepository.findById (id) .orElseThrow (BookNotFoundException :: nieuw); return bookRepository.save (boek); }} 

Aangezien dit aspect van de applicatie een API is, hebben we gebruik gemaakt van de @RestController annotatie hier - wat overeenkomt met een @Controller samen met @ResponseBody - zodat elke methode de geretourneerde bron rechtstreeks naar de HTTP-reactie leidt.

Slechts één opmerking die het vermelden waard is - we stellen onze bloot Boek entiteit als onze externe bron hier. Dat is prima voor onze eenvoudige applicatie hier, maar in een echte applicatie wil je deze twee concepten waarschijnlijk scheiden.

8. Foutafhandeling

Nu de kernapplicatie klaar is voor gebruik, gaan we ons concentreren op een eenvoudig gecentraliseerd foutafhandelingsmechanisme gebruik makend van @ControllerAdvice:

@ControllerAdvice openbare klasse RestExceptionHandler breidt ResponseEntityExceptionHandler {@ExceptionHandler ({BookNotFoundException.class}) beschermd ResponseEntity handleNotFound (uitzondering ex, WebRequest-verzoek) {return handleExceptionInternal (ex, "Boek niet gevonden", nieuwe HttpHeadFOON, verzoek), ; } @ExceptionHandler ({BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class}) openbare ResponseEntity handleBadRequest (Uitzondering ex, WebRequest-verzoek) {return handleExceptionInternal (ex, ex.getLocalizedStMessage (), nieuw Httpead-verzoek, ); }} 

Naast de standaarduitzonderingen die we hier behandelen, gebruiken we ook een aangepaste uitzondering:

BookNotFoundException:

public class BookNotFoundException breidt RuntimeException uit {public BookNotFoundException (String bericht, Throwable oorzaak) {super (bericht, oorzaak); } // ...} 

Dit zou u een idee moeten geven van wat er mogelijk is met dit globale afhandelingsmechanisme voor uitzonderingen. Als je een volledige implementatie wilt zien, bekijk dan de uitgebreide tutorial.

Merk op dat Spring Boot ook een /fout mapping standaard. We kunnen de weergave aanpassen door een eenvoudig error.html:

 Fout opgetreden [status] fout 

bericht

Net als de meeste andere aspecten in Boot, kunnen we dat beheren met een eenvoudige eigenschap:

server.error.path = / error2

9. Testen

Laten we tot slot onze nieuwe Books-API testen.

We kunnen er gebruik van maken @BuienRadarNL om de toepassingscontext te laden en te controleren of er geen fouten zijn bij het uitvoeren van de app:

@RunWith (SpringRunner.class) @SpringBootTest openbare klasse SpringContextTest {@Test openbare ongeldige contextLoads () {}}

Laten we vervolgens een JUnit-test toevoegen die de aanroepen naar de API die we hebben geschreven verifieert met behulp van RestAssured:

openbare klasse SpringBootBootstrapLiveTest {private static final String API_ROOT = "// localhost: 8081 / api / books"; privéboek createRandomBook () {Boek boek = nieuw boek (); book.setTitle (randomAlphabetic (10)); book.setAuthor (randomAlphabetic (15)); retourboek; } private String createBookAsUri (Boek boek) {Response response = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (boek) .post (API_ROOT); retourneer API_ROOT + "/" + response.jsonPath (). get ("id"); }} 

Ten eerste kunnen we proberen boeken te vinden met behulp van verschillende methoden:

@Test openbare ongeldigheid whenGetAllBooks_thenOK () {Reactie reactie = RestAssured.get (API_ROOT); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); } @Test openbare leegte whenGetBooksByTitle_thenOK () {Boek boek = createRandomBook (); createBookAsUri (boek); Antwoordantwoord = RestAssured.get (API_ROOT + "/ title /" + book.getTitle ()); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertTrue (response.as (List.class) .size ()> 0); } @Test openbare leegte whenGetCreatedBookById_thenOK () {Boek boek = createRandomBook (); String locatie = createBookAsUri (boek); Antwoordantwoord = RestAssured.get (locatie); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertEquals (book.getTitle (), response.jsonPath () .get ("title")); } @Test openbare leegte whenGetNotExistBookById_thenNotFound () {Reactie reactie = RestAssured.get (API_ROOT + "/" + randomNumeric (4)); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

Vervolgens testen we het maken van een nieuw boek:

@Test openbare leegte whenCreateNewBook_thenCreated () {Boek boek = createRandomBook (); Antwoordantwoord = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (boek) .post (API_ROOT); assertEquals (HttpStatus.CREATED.value (), response.getStatusCode ()); } @Test openbare leegte whenInvalidBook_thenError () {Boek boek = createRandomBook (); book.setAuthor (null); Antwoordantwoord = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (boek) .post (API_ROOT); assertEquals (HttpStatus.BAD_REQUEST.value (), response.getStatusCode ()); } 

Update een bestaand boek:

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

En verwijder een boek:

@Test openbare leegte whenDeleteCreatedBook_thenOk () {Boek boek = createRandomBook (); String locatie = createBookAsUri (boek); Antwoordantwoord = RestAssured.delete (locatie); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); response = RestAssured.get (locatie); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

10. Conclusie

Dit was een snelle maar uitgebreide introductie tot Spring Boot.

We hebben hier natuurlijk nauwelijks de oppervlakte bekrast - er is veel meer in dit raamwerk dat we in een enkel intro-artikel kunnen behandelen.

Dat is precies waarom we niet zomaar een artikel over Boot op de site hebben staan.

De volledige broncode van onze voorbeelden hier is, zoals altijd, op GitHub.