Перейти к основному содержимому

Проверка в Spring Boot

· 6 мин. чтения

1. Обзор

Когда дело доходит до проверки пользовательского ввода, Spring Boot обеспечивает мощную поддержку этой распространенной, но важной задачи прямо из коробки.

Хотя Spring Boot поддерживает бесшовную интеграцию с пользовательскими валидаторами, стандартом де-факто для выполнения валидации является Hibernate Validator , эталонная реализация инфраструктуры Bean Validation .

В этом руководстве мы рассмотрим, как проверять объекты домена в Spring Boot .

2. Зависимости Maven

В этом случае мы узнаем, как проверять объекты домена в Spring Boot , создавая базовый контроллер REST.

Контроллер сначала возьмет объект домена, затем проверит его с помощью Hibernate Validator и, наконец, сохранит его в базе данных H2 в памяти.

Зависимости проекта довольно стандартны:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>runtime</scope>
</dependency>

Как показано выше, мы включили spring-boot-starter-web в наш файл pom.xml , потому что он понадобится нам для создания контроллера REST. Кроме того, давайте обязательно проверим последние версии spring-boot-starter-jpa и базу данных H2 на Maven Central.

Начиная с Boot 2.3, нам также необходимо явно добавить зависимость spring-boot-starter-validation :

<dependency> 
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

3. Простой доменный класс

Поскольку зависимости нашего проекта уже установлены, нам нужно определить пример класса сущности JPA, роль которого будет заключаться исключительно в моделировании пользователей.

Давайте посмотрим на этот класс:

@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

}

Реализация нашего класса сущностей User действительно довольно анемична, но в двух словах она показывает, как использовать ограничения Bean Validation для ограничения полей имени и электронной почты .

Для простоты мы наложили ограничения на целевые поля, используя только ограничение @NotBlank . Также мы указали сообщения об ошибках с помощью атрибута message .

Поэтому, когда Spring Boot проверяет экземпляр класса, ограниченные поля не должны иметь значение null, а их усеченная длина должна быть больше нуля .

Кроме того, Bean Validation предоставляет множество других удобных ограничений помимо @NotBlank. Это позволяет нам применять и комбинировать различные правила проверки для ограниченных классов. Для получения дополнительной информации, пожалуйста, прочитайте официальную документацию по проверке компонентов .

Поскольку мы будем использовать Spring Data JPA для сохранения пользователей в базе данных H2 в памяти, нам также необходимо определить простой интерфейс репозитория для использования базовых функций CRUD для объектов User :

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

4. Реализация REST-контроллера

Конечно, нам нужно реализовать слой, который позволит нам получать значения, присвоенные ограниченным полям нашего объекта User .

Поэтому мы можем проверить их и выполнить несколько дополнительных задач в зависимости от результатов проверки.

Spring Boot делает этот, казалось бы, сложный процесс действительно простым благодаря реализации контроллера REST.

Давайте посмотрим на реализацию контроллера REST:

@RestController
public class UserController {

@PostMapping("/users")
ResponseEntity<String> addUser(@Valid @RequestBody User user) {
// persisting the user
return ResponseEntity.ok("User is valid");
}

// standard constructors / other methods

}

В контексте Spring REST реализация метода addUser() довольно стандартна.

Конечно, наиболее важной частью является использование аннотации @Valid .

Когда Spring Boot находит аргумент с аннотацией @Valid , он автоматически загружает реализацию JSR 380 по умолчанию — Hibernate Validator — и проверяет аргумент.

Когда целевой аргумент не проходит проверку, Spring Boot выдает исключение MethodArgumentNotValidException .

5. Аннотация @ExceptionHandler

Хотя Spring Boot действительно удобно проверять объект User , автоматически передаваемый методу addUser() , недостающим аспектом этого процесса является то, как мы обрабатываем результаты проверки.

Аннотация @ExceptionHandler позволяет нам обрабатывать определенные типы исключений с помощью одного единственного метода.

Поэтому мы можем использовать его для обработки ошибок проверки:

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

В качестве обрабатываемого исключения мы указали исключение MethodArgumentNotValidException . Следовательно, Spring Boot будет вызывать этот метод, когда указанный объект User недействителен . ``

Метод сохраняет имя и сообщение об ошибке после проверки каждого недопустимого поля в карте. Затем он отправляет карту обратно клиенту в виде представления JSON для дальнейшей обработки.

Проще говоря, контроллер REST позволяет нам легко обрабатывать запросы к различным конечным точкам, проверять объекты User и отправлять ответы в формате JSON.

Дизайн достаточно гибок, чтобы обрабатывать ответы контроллера через несколько веб-уровней, начиная от механизмов шаблонов, таких как Thymeleaf , и заканчивая полнофункциональным фреймворком JavaScript, таким как Angular .

6. Тестирование REST-контроллера

Мы можем легко проверить функциональность нашего контроллера REST с помощью интеграционного теста .

Давайте начнем имитировать/автомонтировать реализацию интерфейса UserRepository вместе с экземпляром UserController и объектом MockMvc :

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

@MockBean
private UserRepository userRepository;

@Autowired
UserController userController;

@Autowired
private MockMvc mockMvc;

//...

}

Поскольку мы тестируем только веб-уровень, мы используем аннотацию @WebMvcTest . Это позволяет нам легко тестировать запросы и ответы, используя набор статических методов, реализованных классами MockMvcRequestBuilders и MockMvcResultMatchers .

Теперь давайте протестируем метод addUser() с допустимым и недопустимым объектом User , переданным в теле запроса:

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
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\" : \"bob@domain.com\"}";
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));
}
}

Кроме того, мы можем протестировать API контроллера REST с помощью бесплатного приложения для тестирования жизненного цикла API , такого как Postman .

7. Запуск примера приложения

Наконец, мы можем запустить наш пример проекта со стандартным методом main() :

@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", "bob@domain.com");
User user2 = new User("Jenny", "jenny@domain.com");
userRepository.save(user1);
userRepository.save(user2);
userRepository.findAll().forEach(System.out::println);
};
}
}

Как и ожидалось, мы должны увидеть в консоли пару объектов User .

Запрос POST к конечной точке http://localhost:8080/users с действительным объектом пользователя вернет строку «Пользователь действителен».

Аналогично, запрос POST с объектом User без значений имени и адреса электронной почты вернет следующий ответ:

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

8. Заключение

В этой статье мы изучили основы выполнения валидации в Spring Boot .

Как обычно, все примеры, показанные в этой статье, доступны на GitHub .