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

Руководство по валидаторам Spring Data REST

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

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

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

ANDROMEDA

1. Обзор

В этой статье представлено базовое введение в Spring Data REST Validators. Если вам нужно сначала ознакомиться с основами Spring Data REST, обязательно посетите эту статью , чтобы освежить в памяти основы.

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

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

И, если вы хотите сначала начать работу с Spring Data REST — вот хороший способ взяться за дело:

2. Использование валидаторов

Начиная с Spring 3, фреймворк имеет интерфейс Validator , который можно использовать для проверки объектов.

2.1. Мотивация

В предыдущей статье мы определили нашу сущность с двумя свойствами — name и email .

Итак, чтобы создать новый ресурс, нам просто нужно запустить:

curl -i -X POST -H "Content-Type:application/json" -d 
'{ "name" : "Test", "email" : "test@test.com" }'
http://localhost:8080/users

Этот запрос POST сохранит предоставленный объект JSON в нашей базе данных, и операция вернет:

{
"name" : "Test",
"email" : "test@test.com",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}

Ожидался положительный результат, поскольку мы предоставили достоверные данные. Но что произойдет, если мы удалим имя свойства или просто установим значение в пустую строку ?

Чтобы протестировать первый сценарий, мы запустим модифицированную команду, где мы установим пустую строку в качестве значения для имени свойства :

curl -i -X POST -H "Content-Type:application/json" -d 
'{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

С помощью этой команды мы получим следующий ответ:

{
"name" : "",
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}

Для второго сценария мы удалим имя свойства из запроса:

curl -i -X POST -H "Content-Type:application/json" -d 
'{ "email" : "Baggins" }' http://localhost:8080/users

Для этой команды мы получим такой ответ:

{
"name" : null,
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/2"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/2"
}
}
}

Как мы видим, оба запроса были в порядке, и мы можем подтвердить это кодом состояния 201 и ссылкой API на наш объект .

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

2.2. События Spring Data REST

Во время каждого вызова Spring Data REST API экспортер Spring Data REST генерирует различные события, которые перечислены здесь:

  • Перед созданием события
  • Афтеркреатеевент
  • Перед сохранением события
  • AfterSaveEvent
  • Перед LinkSaveEvent
  • Событие AfterLinkSave
  • Перед Удалить Событие
  • AfterDeleteEvent

Поскольку все события обрабатываются одинаково, мы покажем только, как обрабатывать событие beforeCreateEvent , которое генерируется перед сохранением нового объекта в базе данных.

2.3. Определение валидатора

Чтобы создать собственный валидатор, нам нужно реализовать интерфейс org.springframework.validation.Validator с методами support и validate .

Поддерживает проверки, поддерживает ли валидатор предоставленные запросы, в то время как метод проверки проверяет предоставленные данные в запросах .

Давайте определим класс WebsiteUserValidator :

public class WebsiteUserValidator implements Validator {

@Override
public boolean supports(Class<?> clazz) {
return WebsiteUser.class.equals(clazz);
}

@Override
public void validate(Object obj, Errors errors) {
WebsiteUser user = (WebsiteUser) obj;
if (checkInputString(user.getName())) {
errors.rejectValue("name", "name.empty");
}

if (checkInputString(user.getEmail())) {
errors.rejectValue("email", "email.empty");
}
}

private boolean checkInputString(String input) {
return (input == null || input.trim().length() == 0);
}
}

Объект Errors — это специальный класс, предназначенный для хранения всех ошибок, указанных в методе проверки . Позже в этой статье мы покажем, как вы можете использовать предоставленные сообщения, содержащиеся в объекте Errors .

Чтобы добавить новое сообщение об ошибке, мы должны вызвать errors.rejectValue(nameOfField, errorMessage) .

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

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

Это можно сделать тремя способами:

  • Добавьте аннотацию компонента с именем « beforeCreateWebsiteUserValidator ». Spring Boot распознает префикс beforeCreate , который определяет событие, которое мы хотим перехватить, а также распознает класс WebsiteUser из имени компонента .
@Component("beforeCreateWebsiteUserValidator")
public class WebsiteUserValidator implements Validator {
...
}
  • Создайте Bean в контексте приложения с аннотацией @Bean :
@Bean
public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
return new WebsiteUserValidator();
}
  • Ручная регистрация:
@SpringBootApplication
public class SpringDataRestApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(SpringDataRestApplication.class, args);
}

@Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new WebsiteUserValidator());
}
}
  • В этом случае вам не нужны никакие аннотации в классе WebsiteUserValidator .

2.4. Ошибка обнаружения событий

На данный момент в Spring Data REST существует ошибка, которая влияет на обнаружение событий.

Если мы вызовем POST-запрос, который генерирует событие beforeCreate , наше приложение не будет вызывать валидатор, потому что событие не будет обнаружено из-за этой ошибки.

Простой обходной путь для этой проблемы — вставить все события в класс Spring Data REST ValidatingRepositoryEventListener :

@Configuration
public class ValidatorEventRegister implements InitializingBean {

@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;

@Autowired
private Map<String, Validator> validators;

@Override
public void afterPropertiesSet() throws Exception {
List<String> events = Arrays.asList("beforeCreate");
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
events.stream()
.filter(p -> entry.getKey().startsWith(p))
.findFirst()
.ifPresent(
p -> validatingRepositoryEventListener
.addValidator(p, entry.getValue()));
}
}
}

3. Тестирование

В разделе 2.1. мы показали, что без валидатора мы можем добавлять объекты без свойства name в нашу базу данных, что нежелательно, поскольку мы не проверяем целостность данных.

Если мы хотим добавить тот же объект без свойства имени , но с предоставленным валидатором, мы получим эту ошибку:

curl -i -X POST -H "Content-Type:application/json" -d 
'{ "email" : "test@test.com" }' http://localhost:8080/users
{  
"timestamp":1472510818701,
"status":406,
"error":"Not Acceptable",
"exception":"org.springframework.data.rest.core.
RepositoryConstraintViolationException",
"message":"Validation failed",
"path":"/users"
}

Как мы видим, были обнаружены недостающие данные из запроса и объект не был сохранен в базу данных. Наш запрос был возвращен с HTTP-кодом 500 и сообщением о внутренней ошибке.

Сообщение об ошибке ничего не говорит о проблеме в нашем запросе. Если мы хотим сделать его более информативным, нам придется изменить объект ответа.

В статье «Обработка исключений в Spring» мы показали, как обрабатывать исключения, сгенерированные фреймворком, так что на данный момент это определенно полезно для чтения.

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

Это наш класс RestResponseEntityExceptionHandle r:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {

@ExceptionHandler({ RepositoryConstraintViolationException.class })
public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
RepositoryConstraintViolationException nevEx =
(RepositoryConstraintViolationException) ex;

String errors = nevEx.getErrors().getAllErrors().stream()
.map(p -> p.toString()).collect(Collectors.joining("\n"));

return new ResponseEntity<Object>(errors, new HttpHeaders(),
HttpStatus.PARTIAL_CONTENT);
}
}

С помощью этого пользовательского обработчика наш возвращаемый объект будет иметь информацию обо всех обнаруженных ошибках.

4. Вывод

В этой статье мы показали, что валидаторы необходимы для каждого REST API Spring Data, который обеспечивает дополнительный уровень безопасности для вставки данных.

Мы также показали, как просто создать новый валидатор с аннотациями.

Как всегда, код этого приложения можно найти в проекте GitHub .