Vodič za proljetno pokretanje - Bootstrap jednostavna aplikacija

1. Pregled

Spring Boot je zamišljeni dodatak Spring-platformi usredotočen na prekomjerno konfiguriranje - vrlo koristan za započinjanje s minimalnim naporom i stvaranje samostalnih aplikacija proizvodnog razreda.

Ovaj je vodič polazište za Boot - način da započnete na jednostavan način, s osnovnom web aplikacijom.

Preći ćemo na neke osnovne konfiguracije, front-end, brzu manipulaciju podacima i rukovanje iznimkama.

2. Postavljanje

Prvo, upotrijebimo Spring Initializr za generiranje osnove za naš projekt.

Generirani projekt oslanja se na roditelja Boot:

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

Početne ovisnosti bit će vrlo jednostavne:

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

3. Konfiguracija aplikacije

Dalje, konfigurirat ćemo jednostavan glavni klasa za našu prijavu:

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

Primijetite kako se koristimo @SpringBootApplication kao naša primarna klasa konfiguracije aplikacije; iza kulisa, to je ekvivalentno @Konfiguracija, @EnableAutoConfiguration, i @ComponentScan zajedno.

Na kraju ćemo definirati jednostavan primjena.svojstva datoteka - koja za sada ima samo jedno svojstvo:

poslužitelj.port = 8081 

poslužitelj.port mijenja poslužiteljski port sa zadanih 8080 na 8081; na raspolaganju su naravno još mnogo svojstava Spring Boot.

4. Jednostavan MVC prikaz

Dodajmo sada jednostavni prednji kraj koristeći Thymeleaf.

Prvo, moramo dodati opruga-čizma-starter-majčina dušica ovisnost o našoj pom.xml:

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

To omogućuje Thymeleaf prema zadanim postavkama - nije potrebna dodatna konfiguracija.

Sada ga možemo konfigurirati u našem primjena.svojstva:

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

Zatim ćemo definirati jednostavni kontroler i osnovnu početnu stranicu - s dobrodošlicom:

@Controller javna klasa SimpleController {@Value ("$ {spring.application.name}") String appName; @GetMapping ("/") javni niz homePage (model modela) {model.addAttribute ("appName", appName); Povratak kući"; }} 

Napokon, evo i našeg home.html:

 Početna stranica 

Dobrodošli u našu aplikaciju

Primijetite kako smo koristili svojstvo koje smo definirali u svojim svojstvima - a zatim ga ubrizgali kako bismo ga mogli prikazati na našoj početnoj stranici.

5. Sigurnost

Dalje, dodajmo sigurnost našoj aplikaciji - tako što ćemo prvo uključiti sigurnosni pokretač:

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

Do sada, nadam se da ste primijetili obrazac - većina se knjižnica Spring lako uvozi u naš projekt uz pomoć jednostavnih pokretača za pokretanje.

Jednom spring-boot-starter-sigurnost ovisnost o putu klase aplikacije - sve su krajnje točke prema zadanim postavkama zaštićene korištenjem bilo kojeg httpBasic ili formLogin na temelju strategije pregovaranja o sadržaju tvrtke Spring Security.

Zbog toga, ako imamo starter na putu predavanja, obično bismo trebali definirati vlastitu prilagođenu sigurnosnu konfiguraciju proširivanjem WebSecurityConfigurerAdapter razred:

@Configuration @EnableWebSecurity javna klasa SecurityConfig proširuje WebSecurityConfigurerAdapter {@Override zaštićena void konfiguracija (HttpSecurity http) baca izuzetak {http.authorizeRequests () .anyRequest () .permitAll () .and (). Csrf (). Csrf (). Csrf (). Csrf (). }}

U našem primjeru dopuštamo neograničen pristup svim krajnjim točkama.

Naravno, Spring Security opsežna je tema koja se ne može lako obraditi u nekoliko redaka konfiguracije - pa vas definitivno potičem da dublje uđete u temu.

6. Jednostavna postojanost

Krenimo od definiranja našeg modela podataka - jednostavnog Knjiga entitet:

@ Entity public class Book {@Id @GeneratedValue (strategy = GenerationType.AUTO) private long id; @ Stupac (nullable = false, unique = true) naslov privatnog niza; @Column (nullable = false) autor privatnog niza; }

I njegovo spremište, koje ovdje dobro koristi Spring Data:

javno sučelje BookRepository proširuje CrudRepository {Lista findByTitle (naslov niza); }

Konačno, moramo naravno konfigurirati naš novi sloj postojanosti:

@EnableJpaRepositories ("com.baeldung.persistence.repo") @EntityScan ("com.baeldung.persistence.model") @SpringBootApplication aplikacija javne klase {...}

Imajte na umu da koristimo:

  • @EnableJpaRepositories za skeniranje navedenog paketa za spremišta
  • @EntityScan pokupiti naše JPA entitete

Da sve bude jednostavnije, ovdje koristimo H2 bazu podataka u memoriji - tako da nemamo nikakve vanjske ovisnosti kada pokrećemo projekt.

Jednom kad uključimo ovisnost o H2, Spring Boot ga automatski prepoznaje i postavlja našu postojanost bez potrebe za dodatnom konfiguracijom, osim svojstava izvora podataka:

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 = 

Naravno, kao i sigurnost, ustrajnost je šira tema od ovog osnovnog skupa ovdje, i ona bi je svakako trebala dodatno istražiti.

7. Web i kontroler

Dalje, pogledajmo web razinu - i to ćemo započeti postavljanjem jednostavnog kontrolera - BookController.

Primijenit ćemo osnovne CRUD operacije izlaganja Knjiga resursi s jednostavnom provjerom valjanosti:

@RestController @RequestMapping ("/ api / books") javna klasa BookController {@Autowired privatni BookRepository bookRepository; @GetMapping public Iterable findAll () {return bookRepository.findAll (); } @GetMapping ("/ title / {bookTitle}") javni popis findByTitle (@PathVariable String bookTitle) {return bookRepository.findByTitle (bookTitle); } @GetMapping ("/ {id}") javna knjiga findOne (@PathVariable Long id) {return bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); } @PostMapping @ResponseStatus (HttpStatus.CREATED) javna izrada knjige (@RequestBody Book book) {return bookRepository.save (knjiga); } @DeleteMapping ("/ {id}") javno prazno brisanje (@PathVariable Long id) {bookRepository.findById (id) .orElseThrow (BookNotFoundException :: novo); bookRepository.deleteById (id); } @PutMapping ("/ {id}") javna knjiga ažuriranja knjige (@RequestBody Book book, @PathVariable Long id) {if (book.getId ()! = Id) {throw new BookIdMismatchException (); } bookRepository.findById (id) .orElseThrow (BookNotFoundException :: novo); vratiti bookRepository.save (knjiga); }} 

S obzirom da je ovaj aspekt aplikacije API, koristili smo @RestController bilješka ovdje - što odgovara a @Controller zajedno s @ResponseBody - tako da svaka metoda maršira vraćeni resurs pravo na HTTP odgovor.

Vrijedno je istaknuti samo jednu napomenu - izlažemo svoje Knjiga entitet kao naš vanjski resurs ovdje. To je u redu za našu jednostavnu aplikaciju ovdje, ali u stvarnoj aplikaciji vjerojatno ćete htjeti razdvojiti ova dva pojma.

8. Rukovanje pogreškama

Sad kad je osnovna aplikacija spremna za rad, usredotočimo se na jednostavan centralizirani mehanizam za rješavanje pogrešaka koristeći @ControllerAdvice:

@ControllerAdvice javna klasa RestExceptionHandler proširuje ResponseEntityExceptionHandler {@ExceptionHandler ({BookNotFoundException.class}) zaštićeni ResponseEntity handleNotFound (Exception ex, WebRequest request) {return handleExceptionInternal (exad, "Book, Http, Http, Http, ; } @ExceptionHandler ({BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class}) javni ResponseEntity handleBadRequest (Iznimka ex, WebRequest zahtjev) {return handleExceptionInternal (ex, ex.gettusHAD, HST ); }} 

Osim standardnih iznimki koje ovdje rješavamo, koristimo i prilagođenu iznimku:

BookNotFoundException:

javna klasa BookNotFoundException proširuje RuntimeException {public BookNotFoundException (poruka niza, uzrok koji se može baciti) {super (poruka, uzrok); } // ...} 

To bi vam trebalo dati ideju o tome što je moguće s ovim globalnim mehanizmom za rukovanje iznimkama. Ako želite vidjeti potpunu implementaciju, pogledajte detaljni vodič.

Imajte na umu da Spring Boot također nudi i / pogreška mapiranje prema zadanim postavkama. Njegov pogled možemo prilagoditi izradom jednostavnog pogreška.html:

 Došlo je do pogreške [status] pogreška 

poruka

Kao i većina ostalih aspekata u Bootu, i to možemo kontrolirati jednostavnim svojstvom:

server.error.path = / error2

9. Ispitivanje

Na kraju, testirajmo naš novi API za knjige.

Možemo se koristiti @SpringBootTest za učitavanje konteksta aplikacije i provjeru da nema pogrešaka pri pokretanju aplikacije:

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

Dalje, dodajmo JUnit test koji provjerava pozive API-ju koji smo napisali, koristeći RestAssured:

javna klasa SpringBootBootstrapLiveTest {private static final String API_ROOT = "// localhost: 8081 / api / books"; privatna knjiga createRandomBook () {Knjiga knjiga = nova knjiga (); book.setTitle (randomAlphabetic (10)); book.setAuthor (randomAlphabetic (15)); knjiga povratka; } private String createBookAsUri (knjiga knjiga) {Response response = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (book) .post (API_ROOT); vrati API_ROOT + "/" + response.jsonPath (). get ("id"); }} 

Prvo, možemo pokušati pronaći knjige koristeći različite metode:

@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 (knjiga); Odgovor odgovora = 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 (); Lokacija niza = createBookAsUri (knjiga); Odgovor odgovora = RestAssured.get (lokacija); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertEquals (book.getTitle (), response.jsonPath () .get ("naslov")); } @Test public void whenGetNotExistBookById_thenNotFound () {Response response = RestAssured.get (API_ROOT + "/" + randomNumeric (4)); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

Zatim ćemo testirati stvaranje nove knjige:

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

Ažurirajte postojeću knjigu:

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

I izbrišite knjigu:

@Test public void whenDeleteCreatedBook_thenOk () {Book book = createRandomBook (); Lokacija niza = createBookAsUri (knjiga); Odgovor odgovora = RestAssured.delete (lokacija); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); odgovor = RestAssured.get (mjesto); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

10. Zaključak

Ovo je bio brz, ali sveobuhvatan uvod u Spring Boot.

Ovdje smo, naravno, jedva ogrebali površinu - u ovom je okviru puno više što možemo pokriti u jednom uvodnom članku.

Upravo zato na web mjestu nemamo samo jedan članak o Bootu.

Puni izvorni kod naših primjera ovdje je, kao i uvijek, gotov na GitHubu.