Convalida in Spring Boot

1. Panoramica

Quando si tratta di convalidare l'input dell'utente, Spring Boot fornisce un forte supporto per questa attività comune, ma critica, fin da subito.

Sebbene Spring Boot supporti una perfetta integrazione con i validatori personalizzati, lo standard de facto per eseguire la convalida è Hibernate Validator , l'implementazione di riferimento del framework Bean Validation.

In questo tutorial vedremo come convalidare gli oggetti di dominio in Spring Boot .

2. Le dipendenze di Maven

In questo caso, impareremo come convalidare gli oggetti di dominio in Spring Boot creando un controller REST di base.

Il controller prenderà prima un oggetto di dominio, quindi lo convaliderà con Hibernate Validator e infine lo renderà persistente in un database H2 in memoria.

Le dipendenze del progetto sono abbastanza standard:

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

Come mostrato sopra, abbiamo incluso spring-boot-starter-web nel nostro file pom.xml perché ne avremo bisogno per creare il controller REST. Inoltre, assicuriamoci di controllare le ultime versioni di spring-boot-starter-jpa e il database H2 su Maven Central.

A partire da Boot 2.3, dobbiamo anche aggiungere esplicitamente la dipendenza spring-boot-starter-validation :

 org.springframework.boot spring-boot-starter-validation 

3. Una semplice classe di dominio

Con le dipendenze del nostro progetto già in atto, dobbiamo quindi definire una classe di entità JPA di esempio, il cui ruolo sarà esclusivamente la modellazione degli utenti.

Diamo un'occhiata a questa classe:

@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @NotBlank(message = "Name is mandatory") private String name; @NotBlank(message = "Email is mandatory") private String email; // standard constructors / setters / getters / toString }

L'implementazione della nostra classe di entità User è davvero piuttosto anemica, ma mostra in poche parole come utilizzare i vincoli di Bean Validation per vincolare i campi nome e email .

Per semplicità, abbiamo vincolato i campi di destinazione utilizzando solo il vincolo @NotBlank . Inoltre, abbiamo specificato i messaggi di errore con l' attributo message .

Pertanto, quando Spring Boot convalida l'istanza della classe, i campi vincolati non devono essere nulli e la loro lunghezza ridotta deve essere maggiore di zero .

Inoltre, Bean Validation fornisce molti altri utili vincoli oltre a @NotBlank. Questo ci consente di applicare e combinare diverse regole di convalida alle classi vincolate. Per ulteriori informazioni, leggere i documenti ufficiali di convalida dei bean.

Poiché utilizzeremo Spring Data JPA per salvare gli utenti nel database H2 in memoria, dobbiamo anche definire una semplice interfaccia di repository per avere funzionalità CRUD di base sugli oggetti utente :

@Repository public interface UserRepository extends CrudRepository {}

4. Implementazione di un controller REST

Naturalmente, dobbiamo implementare un livello che ci consenta di ottenere i valori assegnati ai campi vincolati del nostro oggetto Utente .

Pertanto, possiamo convalidarli ed eseguire alcune ulteriori attività, a seconda dei risultati della convalida.

Spring Boot rende questo processo apparentemente complesso davvero semplice attraverso l'implementazione di un controller REST.

Diamo un'occhiata all'implementazione del controller REST:

@RestController public class UserController { @PostMapping("/users") ResponseEntity addUser(@Valid @RequestBody User user) { // persisting the user return ResponseEntity.ok("User is valid"); } // standard constructors / other methods } 

In un contesto Spring REST, l'implementazione del metodo addUser () è abbastanza standard.

Ovviamente, la parte più rilevante è l'uso dell'annotazione @Valid .

Quando Spring Boot trova un argomento annotato con @Valid , avvia automaticamente l'implementazione JSR 380 predefinita - Hibernate Validator - e convalida l'argomento.

Quando l'argomento target non riesce a superare la convalida, Spring Boot genera un'eccezione MethodArgumentNotValidException .

5. L' annotazione @ExceptionHandler

Sebbene sia davvero utile che Spring Boot convalidi l' oggetto User passato automaticamente al metodo addUser () , l'aspetto mancante di questo processo è il modo in cui elaboriamo i risultati della convalida.

L' annotazione @ExceptionHandler ci consente di gestire tipi specifici di eccezioni attraverso un unico metodo.

Pertanto, possiamo usarlo per elaborare gli errori di convalida:

@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public Map handleValidationExceptions( MethodArgumentNotValidException ex) { Map errors = new HashMap(); ex.getBindingResult().getAllErrors().forEach((error) -> { String fieldName = ((FieldError) error).getField(); String errorMessage = error.getDefaultMessage(); errors.put(fieldName, errorMessage); }); return errors; }

Abbiamo specificato l' eccezione MethodArgumentNotValidException come eccezione da gestire. Di conseguenza, Spring Boot chiamerà questo metodo quando l' oggetto Utente specificato non è valido .

Il metodo memorizza il nome e il messaggio di errore successivo alla convalida di ogni campo non valido in una mappa. Successivamente invia la mappa al client come rappresentazione JSON per un'ulteriore elaborazione.

In poche parole, il controller REST ci consente di elaborare facilmente le richieste a diversi endpoint, convalidare gli oggetti utente e inviare le risposte in formato JSON.

Il design è abbastanza flessibile da gestire le risposte del controller attraverso diversi livelli web, che vanno dai motori di modelli come Thymeleaf, a un framework JavaScript completo come Angular.

6. Test del controller REST

Possiamo facilmente testare la funzionalità del nostro controller REST con un test di integrazione.

Iniziamo a deridere / eseguire il cablaggio automatico dell'implementazione dell'interfaccia UserRepository , insieme all'istanza UserController e a un oggetto MockMvc :

@RunWith(SpringRunner.class) @WebMvcTest @AutoConfigureMockMvc public class UserControllerIntegrationTest { @MockBean private UserRepository userRepository; @Autowired UserController userController; @Autowired private MockMvc mockMvc; //... } 

Poiché stiamo testando solo il livello Web, utilizziamo l' annotazione @WebMvcTest . Ci consente di testare facilmente richieste e risposte utilizzando il set di metodi statici implementati dalle classi MockMvcRequestBuilders e MockMvcResultMatchers .

Ora testiamo il metodo addUser () con un oggetto User valido e uno non valido passati nel corpo della richiesta:

@Test public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception { MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8")); String user = "{\"name\": \"bob\", \"email\" : \"[email protected]\"}"; mockMvc.perform(MockMvcRequestBuilders.post("/users") .content(user) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content() .contentType(textPlainUtf8)); } @Test public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception { String user = "{\"name\": \"\", \"email\" : \"[email protected]\"}"; mockMvc.perform(MockMvcRequestBuilders.post("/users") .content(user) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isBadRequest()) .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory"))) .andExpect(MockMvcResultMatchers.content() .contentType(MediaType.APPLICATION_JSON_UTF8)); } } 

Inoltre, possiamo testare l'API del controller REST utilizzando un'applicazione gratuita di test del ciclo di vita dell'API , come Postman.

7. Esecuzione dell'applicazione di esempio

Infine, possiamo eseguire il nostro progetto di esempio con un metodo main () standard :

@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public CommandLineRunner run(UserRepository userRepository) throws Exception { return (String[] args) -> { User user1 = new User("Bob", "[email protected]"); User user2 = new User("Jenny", "[email protected]"); userRepository.save(user1); userRepository.save(user2); userRepository.findAll().forEach(System.out::println); }; } } 

Come previsto, dovremmo vedere un paio di oggetti User stampati nella console.

Una richiesta POST all'endpoint // localhost: 8080 / users con un oggetto User valido restituirà la stringa "User is valid".

Allo stesso modo, una richiesta POST con un oggetto Utente senza nome e valori di posta elettronica restituirà la seguente risposta:

{ "name":"Name is mandatory", "email":"Email is mandatory" }

8. Conclusione

In questo articolo, abbiamo appreso le basi per eseguire la convalida in Spring Boot .

Come al solito, tutti gli esempi mostrati in questo articolo sono disponibili su GitHub.