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

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

1. обзор

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

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

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

 

И, если вы ищете первыйget started with Spring Data REST - вот хороший способ взяться за дело:

[.iframe-fluid] ##

2. ИспользуяValidators

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

2.1. мотивация

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

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

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

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

{
  "name" : "Test",
  "email" : "[email protected]",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

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

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

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"
    }
  }
}

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

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 генерирует различные события, которые перечислены здесь:

  • BeforeCreateEvent

  • AfterCreateEvent

  • BeforeSaveEvent

  • AfterSaveEvent

  • BeforeLinkSaveEvent

  • AfterLinkSaveEvent

  • BeforeDeleteEvent

  • AfterDeleteEvent

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

2.3. ОпределениеValidator

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

Supports проверяет, поддерживает ли валидатор предоставленные запросы, а метод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 - это специальный класс, предназначенный для хранения всех ошибок, представленных в методеvalidate. Позже в этой статье мы покажем, как можно использовать предоставленные сообщения, содержащиеся в объектеErrors. Чтобы добавить новое сообщение об ошибке, мы должны вызватьerrors.rejectValue(nameOfField, errorMessage).

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

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

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

  • Добавьте аннотациюComponent с именем «beforeCreateWebsiteUserValidator». Spring Boot распознает префиксbeforeCreate, который определяет событие, которое мы хотим поймать, а также распознает классWebsiteUser по имениComponent.

    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • СоздайтеBean в контексте приложения с аннотацией@Bean:

    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • Ручная регистрация:

    @SpringBootApplication
    public class SpringDataRestApplication
      extends RepositoryRestConfigurerAdapter {
        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. Ошибка обнаружения событий

На данный момент abug exists in Spring Data REST - влияет на обнаружение событий.

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

Простое решение этой проблемы - вставить все события в класс Spring Data RESTValidatingRepositoryEventListener:

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map validators;

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

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

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

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

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

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

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

ВException Handling in Spring article мы показали, как обрабатывать исключения, генерируемые фреймворком, так что это определенно хорошее чтение на данный момент.

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

Это наш классRestResponseEntityExceptionHandler:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity 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(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}


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

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

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

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

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