Tutoriel de démarrage du printemps - Démarrer une application simple

Spring Boot Tutorial - Démarrer une application simple

1. Vue d'ensemble

Spring Boot est un complément de la plate-forme Spring particulièrement axé sur la configuration et la configuration par rapport aux conventions. Il est donc très utile pour démarrer avec un minimum d'effort et créer des applications autonomes de niveau production.

This tutorial is a starting point for Boot - un moyen de démarrer de manière simple, avec une application Web de base.

Nous allons passer en revue une configuration de base, un frontal, une manipulation rapide des données et une gestion des exceptions.

Lectures complémentaires:

Comment changer le port par défaut lors du démarrage du printemps

Découvrez comment modifier le port par défaut dans une application Spring Boot.

Read more

Introduction aux démarreurs de printemps

Un aperçu rapide des démarreurs de démarrage Spring les plus courants, ainsi que des exemples sur la manière de les utiliser dans un projet réel.

Read more

2. Installer

Commençons par utiliserSpring Initializr pour générer la base de notre projet.

Le projet généré s'appuie sur le parent de démarrage:


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

Les dépendances initiales vont être assez simples:


    org.springframework.boot
    spring-boot-starter-web


    org.springframework.boot
    spring-boot-starter-data-jpa


    com.h2database
    h2

3. Configuration d'application

Ensuite, nous allons configurer une simple classemain pour notre application:

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

Remarquez comment nous utilisons@SpringBootApplication comme classe de configuration d'application principale; dans les coulisses, cela équivaut à@Configuration,@EnableAutoConfiguration et@ComponentScan ensemble.

Enfin, nous allons définir un simple fichierapplication.properties - qui pour l'instant n'a qu'une seule propriété:

server.port=8081

server.port change le port du serveur de la valeur par défaut 8080 à 8081; il y a bien sûr beaucoup plus deSpring Boot properties available.

4. Vue MVC simple

Ajoutons maintenant une interface simple à l'aide de Thymeleaf.

Tout d'abord, nous devons ajouter la dépendancespring-boot-starter-thymeleaf à nospom.xml:


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

Cela active Thymeleaf par défaut - aucune configuration supplémentaire n'est nécessaire.

Nous pouvons maintenant le configurer dans nosapplication.properties:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.application.name=Bootstrap Spring Boot

Ensuite, nous allons définir un contrôleur simple et une page d'accueil de base - avec un message de bienvenue:

@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;

    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}

Enfin, voici noshome.html:


Home Page

Hello !

Welcome to Our App

Notez comment nous avons utilisé une propriété que nous avons définie dans nos propriétés - puis l'avons injectée afin que nous puissions l'afficher sur notre page d'accueil.

5. Sécurité

Ensuite, ajoutons la sécurité à notre application - en incluant d'abord le démarreur de sécurité:


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

Nous espérons maintenant que vous remarquerez un modèle -most Spring libraries are easily imported into our project with the use of simple Boot starters.

Une fois quespring-boot-starter-security dépend du chemin de classe de l'application - tous les points de terminaison sont sécurisés par défaut, à l'aide dehttpBasic ouformLogin en fonction de la stratégie de négociation de contenu de Spring Security.

C’est pourquoi, si nous avons le démarreur sur le chemin de classe, nous devons généralement définir notre propre configuration de sécurité personnalisée en étendant la classeWebSecurityConfigurerAdapter:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and().csrf().disable();
    }
}

Dans notre exemple, nous autorisons un accès illimité à tous les points de terminaison.

 

Bien sûr, Spring Security est un sujet étendu et difficile à couvrir en quelques lignes de configuration - je vous encourage donc vivement àgo deeper into the topic.

6. Persistance simple

Commençons par définir notre modèle de données - une simple entitéBook:

@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;
}

Et son référentiel, faisant bon usage de Spring Data ici:

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

Enfin, nous devons bien sûr configurer notre nouvelle couche de persistance:

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

Notez que nous utilisons:

  • @EnableJpaRepositories pour analyser le package spécifié à la recherche de référentiels

  • @EntityScan pour récupérer nos entités JPA

Pour simplifier les choses, nous utilisons ici une base de données en mémoire H2 - afin de ne pas avoir de dépendances externes lorsque nous exécutons le projet.

Une fois que nous avons inclus la dépendance H2,Spring Boot auto-detects it and sets up our persistence sans besoin de configuration supplémentaire, autre que les propriétés de la source de données:

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=

Bien sûr, comme la sécurité, la persistance est un sujet plus large que cet ensemble de base ici, et vous devriezcertainly explore further.

7. Web et le contrôleur

Ensuite, examinons un niveau Web - et nous commencerons par configurer un contrôleur simple - lesBookController.

Nous allons mettre en œuvre des opérations CRUD de base exposant les ressources deBook avec une 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);
    }
}

Étant donné que cet aspect de l'application est une API, nous avons utilisé ici l'annotation @RestController - qui équivaut à un@Controller avec@ResponseBody - afin que chaque méthode rassemble correctement la ressource retournée à la réponse HTTP.

Juste une remarque à souligner: nous exposons ici notre entitéBook comme notre ressource externe. C'est bien pour notre application simple ici, mais dans une application réelle, vous voudrez probablementseparate these two concepts.

8. La gestion des erreurs

Maintenant que l'application principale est prête à fonctionner, concentrons-nous sura simple centralized error handling mechanism en utilisant@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);
    }
}


Au-delà des exceptions standard que nous traitons ici, nous utilisons également une exception personnalisée:

BookNotFoundException:

public class BookNotFoundException extends RuntimeException {

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

Cela devrait vous donner une idée de ce qui est possible avec ce mécanisme global de gestion des exceptions. Si vous souhaitez voir une mise en œuvre complète, jetez un œil àthe in-depth tutorial.

Notez que Spring Boot fournit également un mappage/error par défaut. Nous pouvons personnaliser sa vue en créant un simpleerror.html:


Error Occurred

    

Error Occurred!

[status] error

message

Comme la plupart des autres aspects de Boot, nous pouvons contrôler cela avec une simple propriété:

server.error.path=/error2

9. Essai

Enfin, testons notre nouvelle API Livres.

Nous allons immédiatement utiliser@SpringBootTest pour charger le contexte de l'application:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Application.class }, webEnvironment
  = WebEnvironment.DEFINED_PORT)
public class SpringBootBootstrapLiveTest {

    private static final String API_ROOT
      = "http://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");
    }
}

Premièrement, nous pouvons essayer de trouver des livres en utilisant différentes méthodes:

@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());
}

Ensuite, nous allons tester la création d'un nouveau livre:

@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());
}

Mettre à jour un livre existant:

@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"));
}

Et supprimer un livre:

@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

C'était une introduction rapide mais complète à Spring Boot.

Nous avons bien sûr à peine effleuré la surface ici - il y a beaucoup plus à ce cadre que nous pouvons couvrir dans un seul article d'introduction.

Le code source complet de nos exemples ici est, comme toujours,over on GitHub.