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 .