Spring Boot Tutorial - Bootstrap en simpel applikation

1. Oversigt

Spring Boot er en meningsfuld, konvention-over-konfigurationsfokuseret tilføjelse til Spring-platformen - meget nyttigt at komme i gang med mindst mulig indsats og oprette enkeltstående applikationer i produktionsklassen.

Denne vejledning er et udgangspunkt for Boot - en måde at komme i gang på en enkel måde med en grundlæggende webapplikation.

Vi gennemgår nogle kernekonfigurationer, en front-end, hurtig databehandling og håndtering af undtagelser.

2. Opsætning

Lad os først bruge Spring Initializr til at generere basen til vores projekt.

Det genererede projekt er afhængigt af Boot-forælderen:

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

De oprindelige afhængigheder vil være ret enkle:

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

3. Konfiguration af applikation

Dernæst konfigurerer vi en simpel vigtigste klasse til vores ansøgning:

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

Læg mærke til, hvordan vi bruger @SpringBootApplication som vores primære applikationskonfigurationsklasse; bag kulisserne svarer det til @Konfiguration, @EnableAutoConfigurationog @ComponentScan sammen.

Endelig definerer vi en simpel application.properties fil - som for øjeblikket kun har en egenskab:

server.port = 8081 

server.port ændrer serverporten fra standard 8080 til 8081; der er selvfølgelig mange flere Spring Boot egenskaber til rådighed.

4. Enkel MVC-visning

Lad os nu tilføje en simpel frontend ved hjælp af Thymeleaf.

Først skal vi tilføje spring-boot-starter-thymeleaf afhængighed af vores pom.xml:

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

Det muliggør Thymeleaf som standard - ingen ekstra konfiguration er nødvendig.

Vi kan nu konfigurere det i vores application.properties:

spring.thymeleaf.cache = falsk spring.thymeleaf.enabled = sand spring.thymeleaf.prefix = classpath: / skabeloner / spring.thymeleaf.suffix = .html spring.application.name = Bootstrap Spring Boot 

Dernæst definerer vi en simpel controller og en grundlæggende startside - med en velkomstbesked:

@Controller offentlig klasse SimpleController {@Value ("$ {spring.application.name}") String appName; @GetMapping ("/") offentlig String homePage (Model model) {model.addAttribute ("appName", appName); vende hjem"; }} 

Endelig er her vores hjem.html:

 Hjemmeside 

Velkommen til vores app

Bemærk, hvordan vi brugte en ejendom, som vi definerede i vores egenskaber - og derefter injicerede den, så vi kan vise den på vores startside.

5. Sikkerhed

Lad os derefter tilføje sikkerhed til vores applikation - ved først at inkludere sikkerhedsstarteren:

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

Nu bemærker du forhåbentlig et mønster - de fleste forårsbiblioteker importeres let til vores projekt ved hjælp af enkle startstarter.

En gang spring-boot-starter-sikkerhed afhængighed af klassens sti til applikationen - alle slutpunkter er sikret som standard ved hjælp af en af ​​dem httpBasic eller formLogin baseret på Spring Securitys indholdsforhandlingsstrategi.

Derfor, hvis vi har starteren på klassestien, bør vi normalt definere vores egen brugerdefinerede sikkerhedskonfiguration ved at udvide WebSecurityConfigurerAdapter klasse:

@Configuration @EnableWebSecurity offentlig klasse SecurityConfig udvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http.authorizeRequests () .anyRequest () .permitAll () .and (). Csrf (). Deaktiver (). }}

I vores eksempel tillader vi ubegrænset adgang til alle slutpunkter.

Naturligvis er Spring Security et omfattende emne og ikke let dækket af et par konfigurationslinjer - så jeg opfordrer dig bestemt til at gå dybere ind i emnet.

6. Enkel vedholdenhed

Lad os starte med at definere vores datamodel - en simpel Bestil enhed:

@Entity public class Book {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; @Column (nullable = false, unique = true) privat streng titel; @Column (nullable = false) privat Stringforfatter; }

Og dets lager, der gør god brug af Spring Data her:

offentlig grænseflade BookRepository udvider CrudRepository {List findByTitle (String title); }

Endelig skal vi selvfølgelig konfigurere vores nye persistenslag:

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

Bemærk, at vi bruger:

  • @EnableJpaRepositories for at scanne den specificerede pakke for opbevaringssteder
  • @EntityScan at hente vores JPA-enheder

For at holde tingene enkle bruger vi en H2-hukommelsesdatabase her - så vi ikke har nogen eksterne afhængigheder, når vi kører projektet.

Når vi inkluderer H2 afhængighed, Spring Boot registrerer det automatisk og indstiller vores vedholdenhed uden behov for ekstra konfiguration bortset fra datakildeegenskaberne:

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 = 

Selvfølgelig er udholdenhed ligesom sikkerhed et bredere emne end dette grundlæggende sæt her, og du bør helt sikkert undersøge det nærmere.

7. Internettet og controlleren

Lad os derefter se på et web-niveau - og vi starter det med at oprette en simpel controller - BookController.

Vi implementerer basale CRUD-operationer, der udsættes for Bestil ressourcer med nogle enkle valideringer:

@RestController @RequestMapping ("/ api / books") offentlig klasse BookController {@Autowired private BookRepository bookRepository; @GetMapping offentlig Iterabel findAll () {returner bookRepository.findAll (); } @GetMapping ("/ title / {bookTitle}") offentlig liste 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}") offentlig ugyldig sletning (@PathVariable Long id) {bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); bookRepository.deleteById (id); } @PutMapping ("/ {id}") offentlig bogopdateringsbog (@RequestBody bogbog, @PathVariable Lang id) {hvis (book.getId ()! = Id) {smid ny BookIdMismatchException (); } bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); returner bookRepository.save (bog); }} 

Da dette aspekt af applikationen er en API, brugte vi @RestController kommentar her - hvilket svarer til en @Kontrol sammen med @ResponseBody - således at hver metode marshallerer den returnerede ressource lige til HTTP-svaret.

Bare en note, der er værd at påpege - vi udsætter vores Bestil enhed som vores eksterne ressource her. Det er fint for vores enkle anvendelse her, men i en rigtig applikation vil du sandsynligvis ønske at adskille disse to koncepter.

8. Fejlhåndtering

Nu hvor kerneapplikationen er klar til brug, lad os fokusere på en simpel centraliseret fejlhåndteringsmekanisme ved brug af @ControllerAdvice:

@ControllerAdvice offentlig klasse RestExceptionHandler udvider ResponseEntityExceptionHandler {@ExceptionHandler ({BookNotFoundException.class}) beskyttet ResponseEntity handleNotFound (Exception ex, WebRequest anmodning) {return handleExceptionInternal (ex, "Book ikke fundet", ny HttpHttpeaders) ; } @ExceptionHandler ({BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class}) public ResponseEntity handleBadRequest (Exception ex, WebRequest request) {return handleExceptionInternal (ex, ex.getLocalizedMessage, Http) Http. ); }} 

Ud over de standardundtagelser, vi håndterer her, bruger vi også en brugerdefineret undtagelse:

BookNotFoundException:

offentlig klasse BookNotFoundException udvider RuntimeException {public BookNotFoundException (String besked, Throwable årsag) {super (besked, årsag); } // ...} 

Dette skal give dig en idé om, hvad der er muligt med denne globale undtagelseshåndteringsmekanisme. Hvis du gerne vil se en fuld implementering, skal du kigge på den detaljerede vejledning.

Bemærk, at Spring Boot også giver en /fejl kortlægning som standard. Vi kan tilpasse dens visning ved at oprette en simpel error.html:

 Der er sket en fejl [status] fejl 

besked

Som de fleste andre aspekter i Boot kan vi kontrollere det med en simpel egenskab:

server.error.path = / error2

9. Testning

Lad os endelig teste vores nye Books API.

Vi kan gøre brug af @SpringBootTest for at indlæse applikationskonteksten og kontrollere, at der ikke er nogen fejl, når appen kører:

@RunWith (SpringRunner.class) @SpringBootTest offentlig klasse SpringContextTest {@Test offentlig ugyldig contextLoads () {}}

Lad os derefter tilføje en JUnit-test, der bekræfter opkaldene til den API, vi er skrevet, ved hjælp af RestAssured:

public class SpringBootBootstrapLiveTest {private static final String API_ROOT = "// localhost: 8081 / api / books"; privat bog createRandomBook () {Bog bog = ny bog (); book.setTitle (randomAlphabetic (10)); book.setAuthor (randomAlphabetic (15)); returbog; } privat streng createBookAsUri (bogbog) {Response response = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (book) .post (API_ROOT); returner API_ROOT + "/" + respons.jsonPath (). get ("id"); }} 

For det første kan vi prøve at finde bøger ved hjælp af forskellige metoder:

@Test offentlig ugyldig nårGetAllBooks_thenOK () {Response response = RestAssured.get (API_ROOT); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); } @Test offentlig ugyldig nårGetBooksByTitle_thenOK () {Book book = createRandomBook (); createBookAsUri (bog); Svarrespons = RestAssured.get (API_ROOT + "/ titel /" + bog.getTitle ()); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); assertTrue (respons.as (List.class) .størrelse ()> 0); } @Test offentlig ugyldig nårGetCreatedBookById_thenOK () {Book book = createRandomBook (); Strengplacering = createBookAsUri (bog); Svarrespons = RestAssured.get (placering); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); assertEquals (book.getTitle (), respons.jsonPath () .get ("titel")); } @Test offentlig ugyldigt nårGetNotExistBookById_thenNotFound () {Response response = RestAssured.get (API_ROOT + "/" + randomNumeric (4)); assertEquals (HttpStatus.NOT_FOUND.value (), respons.getStatusCode ()); } 

Derefter tester vi at oprette en ny bog:

@Test offentlig ugyldig nårCreateNewBook_thenCreated () {Book book = createRandomBook (); Svarrespons = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (bog) .post (API_ROOT); assertEquals (HttpStatus.CREATED.value (), respons.getStatusCode ()); } @Test offentlig ugyldig nårInvalidBook_thenError () {Book book = createRandomBook (); book.setAuthor (null); Svarrespons = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (bog) .post (API_ROOT); assertEquals (HttpStatus.BAD_REQUEST.value (), respons.getStatusCode ()); } 

Opdater en eksisterende bog:

@Test offentlig ugyldig nårUpdateCreatedBook_thenUpdated () {Book book = createRandomBook (); Strengplacering = createBookAsUri (bog); book.setId (Long.parseLong (location.split ("api / books /") [1])); book.setAuthor ("newAuthor"); Svarrespons = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (bog) .put (placering); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); respons = RestAssured.get (placering); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); assertEquals ("newAuthor", response.jsonPath () .get ("author")); } 

Og slet en bog:

@Test offentlig ugyldig nårDeleteCreatedBook_thenOk () {Book book = createRandomBook (); Strengplacering = createBookAsUri (bog); Svarrespons = RestAssured.delete (placering); assertEquals (HttpStatus.OK.value (), respons.getStatusCode ()); respons = RestAssured.get (placering); assertEquals (HttpStatus.NOT_FOUND.value (), respons.getStatusCode ()); } 

10. Konklusion

Dette var en hurtig, men omfattende introduktion til Spring Boot.

Vi ridsede naturligvis næppe overfladen her - der er meget mere i denne ramme, som vi kan dække i en enkelt intro-artikel.

Det er netop derfor, vi ikke bare har en enkelt artikel om Boot på webstedet.

Den fulde kildekode for vores eksempler her er som altid over på GitHub.