Restrições de método com a validação de bean 2.0
1. Visão geral
Neste artigo, discutiremos como definir e validar restrições de método usando o Bean Validation 2.0 (JSR-380).
Emthe previous article, discutimos JSR-380 com suas anotações embutidas e como implementar a validação de propriedade.
Aqui, vamos nos concentrar nos diferentes tipos de restrições de método, como:
-
restrições de parâmetro único
-
parâmetro cruzado
-
restrições de retorno
Além disso, veremos como validar as restrições manualmente e automaticamente usando o Spring Validator.
Para os exemplos a seguir, precisamos exatamente das mesmas dependências deJava Bean Validation Basics.
2. Declaração de restrições de método
Para começar,we’ll first discuss how to declare constraints on method parameters and return values of methods.
Como mencionado antes, podemos usar anotações dejavax.validation.constraints, mas também podemos especificar restrições personalizadas (e. g. para restrições personalizadas ou restrições de parâmetro cruzado).
2.1. Restrições de parâmetro único
Definir restrições em parâmetros únicos é simples. We simply have to add annotations to each parameter as required:
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer) {
// ...
}
Da mesma forma, podemos usar a mesma abordagem para construtores:
public class Customer {
public Customer(@Size(min = 5, max = 200) @NotNull String firstName,
@Size(min = 5, max = 200) @NotNull String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// properties, getters, and setters
}
2.2. Usando Restrições de Parâmetros Cruzados
Em alguns casos, pode ser necessário validar vários valores de uma só vez, por exemplo, duas quantidades numéricas sendo uma maior que a outra.
Para esses cenários, podemos definir restrições personalizadas entre parâmetros, que podem depender de dois ou mais parâmetros.
Cross-parameter constraints can be considered as the method validation equivalent to class-level constraints. Poderíamos usar os dois para implementar a validação com base em várias propriedades.
Vamos pensar em um exemplo simples: uma variação do métodocreateReservation() da seção anterior leva dois parâmetros do tipoLocalDate: uma data de início e uma data de término.
Conseqüentemente, queremos ter certeza de quebegin está no futuro eend está depois debegin. Ao contrário do exemplo anterior, não podemos definir isso usando restrições de parâmetro único.
Em vez disso, precisamos de uma restrição de parâmetro cruzado.
Em contraste com as restrições de parâmetro único,cross-parameter constraints are declared on the method or constructor:
@ConsistentDateParameters
public void createReservation(LocalDate begin,
LocalDate end, Customer customer) {
// ...
}
2.3. Criação de restrições de parâmetro cruzado
Para implementar a restrição@ConsistentDateParameters, precisamos de duas etapas.
Primeiro, precisamosdefine the constraint annotation:
@Constraint(validatedBy = ConsistentDateParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {
String message() default
"End date must be after begin date and both must be in the future";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
Aqui, essas três propriedades são obrigatórias para anotações de restrição:
-
message – retorna a chave padrão para a criação de mensagens de erro, o que nos permite usar a interpolação de mensagens
-
groups - nos permite especificar grupos de validação para nossas restrições
-
payload - pode ser usado por clientes da API Bean Validation para atribuir objetos de carga útil personalizados a uma restrição
Para obter detalhes sobre como definir uma restrição personalizada, dê uma olhada emthe official documentation.
Depois disso, podemos definir a classe validadora:
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParameterValidator
implements ConstraintValidator {
@Override
public boolean isValid(
Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null || value[1] == null) {
return true;
}
if (!(value[0] instanceof LocalDate)
|| !(value[1] instanceof LocalDate)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
return ((LocalDate) value[0]).isAfter(LocalDate.now())
&& ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
}
}
Como podemos ver, o métodoisValid() contém a lógica de validação real. Primeiro, nos certificamos de obter dois parâmetros do tipoLocalDate.. Depois disso, verificamos se ambos estão no futuro e seend está apósbegin.
Além disso, é importante notar que a anotação@SupportedValidationTarget(ValidationTarget.PARAMETERS) na classeConsistentDateParameterValidator é necessária. A razão para isso é porque@ConsistentDateParameter é definido no nível do método, mas as restrições devem ser aplicadas aos parâmetros do método (e não ao valor de retorno do método, como discutiremos na próxima seção).
Nota: a especificação Bean Validation recomenda considerar os valoresnull como válidos. Senull não for um valor válido, a anotação@NotNull deve ser usada.
2.4. Restrições de valor de retorno
Às vezes, precisamos validar um objeto conforme ele é retornado por um método. Para isso, podemos usar restrições de valor de retorno.
O exemplo a seguir usa restrições internas:
public class ReservationManagement {
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
return null;
}
}
ParagetAllCustomers(), as seguintes restrições se aplicam:
-
Primeiro, a lista retornada não deve sernulle deve ter pelo menos uma entrada
-
Além disso, a lista não deve conter entradasnull
2.5. Restrições personalizadas de valor de retorno
Em alguns casos, também podemos precisar validar objetos complexos:
public class ReservationManagement {
@ValidReservation
public Reservation getReservationsById(int id) {
return null;
}
}
Neste exemplo, um objetoReservation retornado deve satisfazer as restrições definidas por@ValidReservation, que definiremos a seguir.
Novamente,we first have to define the constraint annotation:
@Constraint(validatedBy = ValidReservationValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ValidReservation {
String message() default "End date must be after begin date "
+ "and both must be in the future, room number must be bigger than 0";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
Depois disso, definimos a classe validadora:
public class ValidReservationValidator
implements ConstraintValidator {
@Override
public boolean isValid(
Reservation reservation, ConstraintValidatorContext context) {
if (reservation == null) {
return true;
}
if (!(reservation instanceof Reservation)) {
throw new IllegalArgumentException("Illegal method signature, "
+ "expected parameter of type Reservation.");
}
if (reservation.getBegin() == null
|| reservation.getEnd() == null
|| reservation.getCustomer() == null) {
return false;
}
return (reservation.getBegin().isAfter(LocalDate.now())
&& reservation.getBegin().isBefore(reservation.getEnd())
&& reservation.getRoom() > 0);
}
}
2.6. Valor de retorno em construtores
Como definimosMETHODeCONSTRUCTOR comotarget dentro de nossa interfaceValidReservation antes,we can also annotate the constructor of Reservation to validate constructed instances:
public class Reservation {
@ValidReservation
public Reservation(
LocalDate begin,
LocalDate end,
Customer customer,
int room) {
this.begin = begin;
this.end = end;
this.customer = customer;
this.room = room;
}
// properties, getters, and setters
}
2.7. Validação em Cascata
Por fim, a API de Validação de Bean nos permite validar não apenas objetos únicos, mas também gráficos de objetos, usando a chamada validação em cascata.
Hence, we can use @Valid for a cascaded validation, if we want to validate complex objects. Isso funciona para parâmetros de método e também para valores de retorno.
Vamos supor que temos uma classeCustomer com algumas restrições de propriedade:
public class Customer {
@Size(min = 5, max = 200)
private String firstName;
@Size(min = 5, max = 200)
private String lastName;
// constructor, getters and setters
}
Uma classeReservation pode ter uma propriedadeCustomer, bem como outras propriedades com restrições:
public class Reservation {
@Valid
private Customer customer;
@Positive
private int room;
// further properties, constructor, getters and setters
}
Se agora fizermos referência aReservation como um parâmetro de método,we can force the recursive validation of all properties:
public void createNewCustomer(@Valid Reservation reservation) {
// ...
}
Como podemos ver, usamos@Valid em dois lugares:
-
No parâmetroreservation: dispara a validação do objetoReservation, quandocreateNewCustomer() é chamado
-
Como temos um gráfico de objeto aninhado aqui, também temos que adicionar um@Valid no atributocustomer: assim, ele aciona a validação desta propriedade aninhada
Isso também funciona para métodos que retornam um objeto do tipoReservation:
@Valid
public Reservation getReservationById(int id) {
return null;
}
3. Validando as restrições do método
Após a declaração de restrições na seção anterior, agora podemos prosseguir para validar essas restrições. Para isso, temos várias abordagens.
3.1. Validação Automática com Spring
Spring Validation fornece uma integração com o Hibernate Validator.
Nota: A validação do Spring é baseada no AOP e usa o Spring AOP como a implementação padrão. Portanto, a validação funciona apenas para métodos, mas não para construtores.
Se agora queremos que o Spring valide nossas restrições automaticamente, precisamos fazer duas coisas:
Firstly, we have to annotate the beans, which shall be validated, with @Validated:
@Validated
public class ReservationManagement {
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer){
// ...
}
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers(){
return null;
}
}
Em segundo lugar, temos que fornecer um beanMethodValidationPostProcessor:
@Configuration
@ComponentScan({ "org.example.javaxval.methodvalidation.model" })
public class MethodValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
O contêiner agora irá lançar umjavax.validation.ConstraintViolationException, se uma restrição for violada.
Se estivermos usando Spring Boot, o contêiner registrará um beanMethodValidationPostProcessor para nós, desde quehibernate-validator esteja no classpath.
3.2. Validação automática com CDI (JSR-365)
A partir da versão 1.1, a Validação de Bean trabalha com CDI (Contextos e Injeção de Dependências para Java EE).
Se nosso aplicativo for executado em um contêiner Java EE, o contêiner validará as restrições do método automaticamente no momento da chamada.
3.3. Validação Programática
Paramanual method validation in a standalone Java application, podemos usar a interfacejavax.validation.executable.ExecutableValidator.
Podemos recuperar uma instância usando o seguinte código:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
ExecutableValidator oferece quatro métodos:
-
validateParameters() evalidateReturnValue() para validação do método
-
validateConstructorParameters() evalidateConstructorReturnValue() para validação do construtor
A validação dos parâmetros do nosso primeiro métodocreateReservation() ficaria assim:
ReservationManagement object = new ReservationManagement();
Method method = ReservationManagement.class
.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
Object[] parameterValues = { LocalDate.now(), 0, null };
Set> violations
= executableValidator.validateParameters(object, method, parameterValues);
Nota: A documentação oficial desencoraja chamar esta interface diretamente do código do aplicativo, mas usá-la por meio de uma tecnologia de interceptação de método, como AOP ou proxies.
Caso você esteja interessado em como usar a interfaceExecutableValidator, você pode dar uma olhada emofficial documentation.
4. Conclusão
Neste tutorial, vimos rapidamente como usar restrições de método com o Hibernate Validator e também discutimos alguns novos recursos do JSR-380.
Primeiro, discutimos como declarar diferentes tipos de restrições:
-
Restrições de parâmetro único
-
Parâmetro cruzado
-
Restrições de valor de retorno
Também vimos como validar as restrições manual e automaticamente usando o Spring Validator.
Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.