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 .