Validando elementos de contêiner com Bean Validation 2.0

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.