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

Проверка в Spring Boot

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

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

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 .