Guia para validadores REST de dados Spring
1. Visão geral
Este artigo aborda uma introdução básica aos Validadores REST do Spring Data. Se você precisa primeiro revisar os fundamentos do Spring Data REST, definitivamente visitethis article para revisar os fundamentos.
Simplificando, com o Spring Data REST, podemos simplesmente adicionar uma nova entrada ao banco de dados por meio da API REST, mas é claro que também precisamos garantir que os dados sejam válidos antes de realmente persistir.
Este artigo continua emexisting article e vamos reutilizar o projeto existente que configuramos lá.
E, se você está procurando primeiroget started with Spring Data REST - aqui está uma boa maneira de começar a correr:
[.iframe-fluido] ##
2. UsandoValidators
A partir do Spring 3, a estrutura apresenta a interfaceValidator - que pode ser usada para validar objetos.
2.1. Motivação
No artigo anterior, definimos nossa entidade com duas propriedades -nameeemail.
E assim, para criar um novo recurso, basta executar:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "Test", "email" : "[email protected]" }'
http://localhost:8080/users
Essa solicitação POST salvará o objeto JSON fornecido em nosso banco de dados e a operação retornará:
{
"name" : "Test",
"email" : "[email protected]",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}
Era esperado um resultado positivo, pois fornecemos dados válidos. Mas, o que acontecerá se removermos a propriedadename, ou apenas definirmos o valor para umString vazio?
Para testar o primeiro cenário, executaremos o comando modificado anterior, onde definiremos uma string vazia como um valor para a propriedadename:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users
Com esse comando, obteremos a seguinte resposta:
{
"name" : "",
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}
Para o segundo cenário, removeremos a propriedadename da solicitação:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "email" : "Baggins" }' http://localhost:8080/users
Para esse comando, obteremos esta resposta:
{
"name" : null,
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/2"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/2"
}
}
}
Como podemos ver, ambas as solicitações estavam corretas e podemos confirmar isso com o código de status 201 e o link da API para nosso objeto.
Esse comportamento não é aceitável, pois queremos evitar a inserção de dados parciais em um banco de dados.
2.2. Eventos REST Spring Data
Durante cada chamada na API Spring Data REST, o exportador Spring Data REST gera vários eventos listados aqui:
-
BeforeCreateEvent
-
AfterCreateEvent
-
BeforeSaveEvent
-
AfterSaveEvent
-
BeforeLinkSaveEvent
-
AfterLinkSaveEvent
-
BeforeDeleteEvent
-
AfterDeleteEvent
Uma vez que todos os eventos são tratados de maneira semelhante, mostraremos apenas como tratarbeforeCreateEvent que é gerado antes de um novo objeto ser salvo no banco de dados.
2.3. Definindo umValidator
Para criar nosso próprio validador, precisamos implementar a interfaceorg.springframework.validation.Validator com os métodossupportsevalidate.
Supports verifica se o validador oferece suporte a solicitações fornecidas, enquanto o métodovalidate valida os dados fornecidos nas solicitações.
Vamos definir uma classeWebsiteUserValidator:
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);
}
}
O objetoErrors é uma classe especial projetada para conter todos os erros fornecidos no métodovalidate. Posteriormente neste artigo, mostraremos como você pode usar mensagens fornecidas contidas no objetoErrors. Para adicionar uma nova mensagem de erro, temos que chamarerrors.rejectValue(nameOfField, errorMessage).
Depois de definir o validador, precisamos mapeá-lo para um evento específico que é gerado após a solicitação ser aceita.
Por exemplo, em nosso caso,beforeCreateEvent é gerado porque queremos inserir um novo objeto em nosso banco de dados. Porém, como queremos validar o objeto em uma solicitação, precisamos primeiro definir nosso validador.
Isso pode ser feito de três maneiras:
-
Adicione a anotaçãoComponent com o nome “beforeCreateWebsiteUserValidator“. O Spring Boot reconhecerá o prefixobeforeCreate que determina o evento que queremos capturar e também reconhecerá a classeWebsiteUser do nomeComponent.
@Component("beforeCreateWebsiteUserValidator") public class WebsiteUserValidator implements Validator { ... } -
CrieBean no contexto do aplicativo com a anotação@Bean:
@Bean public WebsiteUserValidator beforeCreateWebsiteUserValidator() { return new WebsiteUserValidator(); } -
Registro manual:
@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()); } }-
Para este caso, você não precisa de nenhuma anotação na classeWebsiteUserValidator.
-
2.4. Bug de descoberta de evento
No momento, abug exists in Spring Data REST - que afeta a descoberta de eventos.
Se chamarmos o pedido POST que gera o eventobeforeCreate, nosso aplicativo não chamará o validador porque o evento não será descoberto, devido a este bug.
Uma solução simples para esse problema é inserir todos os eventos na classe 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. Teste
EmSection 2.1., mostramos que, sem um validador, podemos adicionar objetos sem propriedade de nome em nosso banco de dados, o que não é o comportamento desejado porque não verificamos a integridade dos dados.
Se quisermos adicionar o mesmo objeto sem a propriedadename, mas com o validador fornecido, obteremos este erro:
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"
}
Como podemos ver, os dados ausentes da solicitação foram detectados e um objeto não foi salvo no banco de dados. Nossa solicitação foi retornada com código HTTP 500 e mensagem para um erro interno.
A mensagem de erro não diz nada sobre o problema em nossa solicitação. Se quisermos torná-lo mais informativo, teremos que modificar o objeto de resposta.
EmException Handling in Spring article, mostramos como lidar com as exceções geradas pelo framework, então essa é definitivamente uma boa leitura neste ponto.
Como nosso aplicativo gera uma exceçãoRepositoryConstraintViolationException, criaremos um manipulador para essa exceção em particular que modificará a mensagem de resposta.
Esta é a nossa classeRestResponseEntityExceptionHandler:
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {
@ExceptionHandler({ RepositoryConstraintViolationException.class })
public ResponseEntity
Com esse manipulador personalizado, nosso objeto de retorno terá informações sobre todos os erros detectados.
4. Conclusão
Neste artigo, mostramos que os validadores são essenciais para cada API REST do Spring Data, que fornece uma camada extra de segurança para inserção de dados.
Também ilustramos como é simples criar um novo validador com anotações.
Como sempre, o código para este aplicativo pode ser encontrado emGitHub project.