Validando elementos de contêiner com Bean Validation 2.0
1. Visão geral
A versão 2.0 da especificaçãoJava Bean Validation adiciona vários novos recursos, entre os quais a possibilidade de validar elementos de containers.
Essa nova funcionalidade aproveita as anotações de tipo introduzidas no Java 8. Portanto, requer o Java versão 8 ou superior para funcionar.
As anotações de validação podem ser adicionadas a contêineres como coleções, objetosOptional e outros contêineres internos e personalizados.
Para uma introdução aJava Bean Validation e como configurar as dependênciasMaven de que precisamos, verifique nossoprevious article here.
Nas seções a seguir, vamos nos concentrar na validação de elementos de cada tipo de contêiner.
2. Elementos de coleção
Podemos adicionar anotações de validação a elementos de coleções do tipojava.util.Iterable,java.util.Listejava.util.Map.
Vejamos um exemplo de validação dos elementos de uma lista:
public class Customer {
List<@NotBlank(message="Address must not be blank") String> addresses;
// standard getters, setters
}
No exemplo acima, definimos uma propriedadeaddresses para uma classeCustomer, que contém elementos que não podem estar vaziosStrings.
Observe quethe @NotBlank validation applies to the String elements, and not the entire collection. Se a coleção estiver vazia, nenhuma validação será aplicada.
Vamos verificar que se tentarmos adicionar umString vazio à listaaddresses, a estrutura de validação retornará umConstraintViolation:
@Test
public void whenEmptyAddress_thenValidationFails() {
Customer customer = new Customer();
customer.setName("John");
customer.setAddresses(Collections.singletonList(" "));
Set> violations =
validator.validate(customer);
assertEquals(1, violations.size());
assertEquals("Address must not be blank",
violations.iterator().next().getMessage());
}
A seguir, vamos ver como podemos validar os elementos de uma coleção do tipoMap:
public class CustomerMap {
private Map<@Email String, @NotNull Customer> customers;
// standard getters, setters
}
Observe quewe can add validation annotations for both the key and the value of a Map element.
Vamos verificar se adicionar uma entrada com um e-mail inválido resultará em um erro de validação:
@Test
public void whenInvalidEmail_thenValidationFails() {
CustomerMap map = new CustomerMap();
map.setCustomers(Collections.singletonMap("john", new Customer()));
Set> violations
= validator.validate(map);
assertEquals(1, violations.size());
assertEquals(
"Must be a valid email",
violations.iterator().next().getMessage());
}
3. ValoresOptional
As restrições de validação também podem ser aplicadas a um valorOptional:
private Integer age;
public Optional<@Min(18) Integer> getAge() {
return Optional.ofNullable(age);
}
Vamos criar umCustomer com uma idade muito baixa - e verificar se isso resulta em um erro de validação:
@Test
public void whenAgeTooLow_thenValidationFails() {
Customer customer = new Customer();
customer.setName("John");
customer.setAge(15);
Set> violations
= validator.validate(customer);
assertEquals(1, violations.size());
}
Por outro lado, seage for nulo, o valorOptional não é validado:
@Test
public void whenAgeNull_thenValidationSucceeds() {
Customer customer = new Customer();
customer.setName("John");
Set> violations
= validator.validate(customer);
assertEquals(0, violations.size());
}
4. Elementos de contêiner não genéricos
Além de adicionar anotações para argumentos de tipo, também podemos aplicar validação a containers não genéricos, desde que haja um extrator de valor para o tipo com a anotação@UnwrapByDefault.
Extratores de valor são as classes que extraem os valores dos contêineres para validação.
The reference implementation contains value extractors for OptionalInt, OptionalLong and OptionalDouble:
@Min(1)
private OptionalInt numberOfOrders;
Nesse caso, a anotação@Min se aplica ao valorInteger empacotado, e não ao contêiner.
5. Elementos de contêiner personalizados
Além dos extratores de valor internos, também podemos definir nossos próprios e registrá-los com um tipo de contêiner.
Dessa forma, podemos adicionar anotações de validação aos elementos de nossos contêineres personalizados.
Vamos adicionar uma nova classeProfile que contém uma propriedadecompanyName:
public class Profile {
private String companyName;
// standard getters, setters
}
Em seguida, queremos adicionar uma propriedadeProfile na classeCustomer com uma anotação@NotBlank - que verifica secompanyName não é umString vazio:
@NotBlank
private Profile profile;
Para que isso funcione, precisamos de um extrator de valor que determina a validação a ser aplicada à propriedadecompanyName e não ao objeto de perfil diretamente.
Vamos adicionar uma classeProfileValueExtractor que implementa a interfaceValueExtractor e substitui o métodoextractValue():
@UnwrapByDefault
public class ProfileValueExtractor
implements ValueExtractor<@ExtractedValue(type = String.class) Profile> {
@Override
public void extractValues(Profile originalValue,
ValueExtractor.ValueReceiver receiver) {
receiver.value(null, originalValue.getCompanyName());
}
}
Esta classe também precisa especificar o tipo do valor extraído usando a anotação@ExtractedValue.
Além disso, adicionamosthe @UnwrapByDefault annotation that specifies the validation should be applied to the unwrapped value and not the container.
Finalmente, precisamos registrar a classe adicionando um arquivo chamadojavax.validation.valueextraction.ValueExtractor ao diretórioMETA-INF/services, que contém o nome completo de nossa classeProfileValueExtractor:
org.example.javaxval.container.validation.valueextractors.ProfileValueExtractor
Agora, quando validamos um objetoCustomer com uma propriedadeprofile com umcompanyName vazio, veremos um erro de validação:
@Test
public void whenProfileCompanyNameBlank_thenValidationFails() {
Customer customer = new Customer();
customer.setName("John");
Profile profile = new Profile();
profile.setCompanyName(" ");
customer.setProfile(profile);
Set> violations
= validator.validate(customer);
assertEquals(1, violations.size());
}
Observe que se você estiver usandohibernate-validator-annotation-processor, adicionar uma anotação de validação a uma classe de contêiner personalizada, quando estiver marcada como@UnwrapByDefault, resultará em um erro de compilação na versão 6.0.2.
Este é umknown issuee provavelmente será resolvido em uma versão futura.
6. Conclusão
Neste artigo, mostramos como podemos validar vários tipos de elementos de contêiner usandoJava Bean Validation 2.0.
Você pode encontrar o código-fonte completo dos exemplosover on GitHub.