Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Nesse artigo vamos ensinar como você criar uma annotation personalizada, para validar seus objetos, utilizando a biblioteca Hibernate Validator.
O Hibernate Validator é uma poderosa biblioteca muito utilizada no mundo Java que permite a validação de objetos, de forma simples e elegante. Essa biblioteca já nos entrega diversas anotações prontas que facilitam a vida do desenvolvedor mas o seu poder vai além disso, ele nos permite criar nossas próprias annotations para atender requisitos específicos de validação.
Nosso primeiro passo será definir a nossa própria annotation, vamos criar um exemplo simples onde vamos validar a idade de um funcionário que será cadastrado na nossa plataforma, a regra de negócio será bem simples para que seja possível o cadastro o nosso funcionário tem que ter a idade legal, ou seja ser maior de 18 anos.
A implementação para isso será bem simples:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import com.artefatox.annotationsvalidation.validation.annotation.impl.LegalAgeImpl;
@Documented
@Constraint(validatedBy = LegalAgeImpl.class)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface LegalAge {
String message() default "Idade inválida";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
JavaVamos explicar cada passo dessa implementação:
Agora que temos a annotation criada temos que criar uma classe que fará a validação de fato, e que será referenciada na anotação @Constraint da nossa annotation personalizada.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.artefatox.annotationsvalidation.validation.annotation.LegalAge;
public class LegalAgeImpl implements ConstraintValidator<LegalAge, Integer> {
@Override
public void initialize(LegalAge constraintAnnotation) {
// TODO Auto-generated method stub
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(value >= 18) {
return true;
} else {
return false;
}
}
}
JavaVeja que em questão de código é bem simples, nossa classe precisa implementar a interface ConstraintValidator
que recebe dois tipos, o primeiro é a sua própria annotation e o segundo o tipo do valor que será validado, essa interface nos força a implementar o método isValid
, é dentro dele que vamos colocar a lógica de validação.
A implementação do método initialize
não é obrigatória, ele serve para casos em que você precisa fazer uma inicialização antes da validação em si, um exemplo prático para isso é casos em que você precisaria recuperar informações de um outro lugar, por exemplo o application.properties caso esteja utilizando algum framework da web.
Agora vamos fazer o uso da nossa annotation, para exemplificar construímos uma chamada rest simples com o framework Spring Boot, e no recebimento do body da requisição vamos faze a validação já na camada de controller.
import javax.validation.constraints.Size;
import com.artefatox.annotationsvalidation.validation.annotation.LegalAge;
import lombok.Data;
@Data
public class Employee {
@Size(min = 3, max = 55)
private String name;
@LegalAge
private Integer age;
}
JavaVeja que estamos anotando a propriedade age com a nossa annotation personalizada, e também estamos utilizando uma notação padrão da biblioteca para validar o nome.
import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.artefatox.annotationsvalidation.entity.Employee;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@PostMapping
public ResponseEntity<?> register(@Valid @RequestBody Employee employee) {
return ResponseEntity.status(HttpStatus.CREATED)
.body("Registration completed successfully for " + employee.getName());
}
}
JavaO Spring já possui alta compatibilidade com o hibernate validation, então veja que para validar a nosso objeto no controller precisamos apenas anotar o parâmetro com @Valid
, agora podemos executar nossa aplicação e ver o funcionamento.
Vamos fazer duas requisições e validar a saída:
Requisição 1: Com os valores válidos:
curl --request POST \
--url http://localhost:8080/employees \
--header 'Content-Type: application/json' \
--data '{
"name": "Maria",
"age": 18
}'
TerminalE nossa saída será:
Registration completed successfully for Maria
TerminalRequisição 2: Com a idade inválida:
curl --request POST \
--url http://localhost:8080/employees \
--header 'Content-Type: application/json' \
--data '{
"name": "Maria",
"age": 17
}'
TerminalE com esse request a saída será:
{"timestamp":"2023-10-25T14:49:08.206+00:00","status":400,"error":"Bad Request","trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> com.artefatox.annotationsvalidation.controller.EmployeeController.register(com.artefatox.annotationsvalidation.entity.Employee): [Field error in object 'employee' on field 'age': rejected value [17]; codes [LegalAge.employee.age,LegalAge.age,LegalAge.java.lang.Integer,LegalAge]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.age,age]; arguments []; default message [age]]; default message [Idade inválida]] \n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:555)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:623)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1794)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n","message":"Validation failed for object='employee'. Error count: 1","errors":[{"codes":["LegalAge.employee.age","LegalAge.age","LegalAge.java.lang.Integer","LegalAge"],"arguments":[{"codes":["employee.age","age"],"arguments":null,"defaultMessage":"age","code":"age"}],"defaultMessage":"Idade inválida","objectName":"employee","field":"age","rejectedValue":17,"bindingFailure":false,"code":"LegalAge"}],"path":"/employees"}
TerminalVocê também pode tentar fazer o teste enviando também o nome com menos de 3 caracteres ou mais de 55, que também retornará o erro.