Validação Declarativa
O Spring MVC integra o Bean Validation (JSR-303) desde a versão 3, permitindo validar objetos de comando diretamente por anotações. Abaixo está o passo a passo para ativar e usar esse recurso.
Dependências Necessárias
Usaremos o Hibernate Validator como implementação de referência. Adicione os seguintes JARs ao classpath da aplicação:
validation-api-1.0.0.GA.jar— API da especificação JSR-303.hibernate-validator-4.3.0.Final.jar— Implementação do Hibernate Validator.jboss-logging-3.1.0.CR2.jar— Dependência de logging do Hibernate Validator.
Registro do Validador no Spring
Crie um bean do tipo LocalValidatorFactoryBean e enforme ao <mvc:annotation-driven> que ele deve ser usado:
<bean id="validador" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="fonteMensagens"/>
</bean>
<bean id="fonteMensagens" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:mensagens"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="120"/>
</bean>
<mvc:annotation-driven validator="validador"/>
O arquivo mensagens.properties, colocado no src, pode conter entradas como:
nome.obrigatorio=O nome do cliente é obrigatório
email.invalido=O endereço de e-mail informado não é válido
idade.minima=A idade mínima permitida é 18 anos
Modelo com Restrições
As restrições são declaradas diretamente nos atributos do modelo:
public class ClienteModel {
@NotBlank(message = "{nome.obrigatorio}")
@Size(min = 3, max = 60, message = "{nome.tamanho}")
private String nome;
@Email(message = "{email.invalido}")
private String email;
@Min(value = 18, message = "{idade.minima}")
private int idade;
// getters e setters omitidos
}
Controller que Dispara a Validação
Use @Valid antes do objeto de comando. Logo em seguida, adicione um parâmetro BindingResult (ou Errors) para acessar os erros:
@Controller
public class CadastroController {
@RequestMapping("/clientes/cadastrar")
public String cadastrar(
@Valid @ModelAttribute("cliente") ClienteModel cliente,
BindingResult resultado) {
if (resultado.hasErrors()) {
return "cadastro/erro";
}
return "cadastro/sucesso";
}
}
Exibição dos Erros na Visão
Na página JSP, utilize a tag <form:errors> para listar as violações:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form commandName="cliente">
<form:errors path="*" cssStyle="color: red"/>
</form:form>
Para testar, faça uma requisição sem informar o nome:
http://localhost:8080/app/clientes/cadastrar?nome=&email=invalido&idade=10
Principais Anotações de Validação
O Hibernate Validator oferece um conjunto rico de restrições. A tabela abaixo resume as mais usadas:
- @AssertFalse: o valor booleano deve ser
false. - @AssertTrue: o valor booleano deve ser
true. - @NotNull: o valor não pode ser
null. - @Null: o valor deve ser
null. - @Min(valor): o número deve ser maior ou igual ao limite.
- @Max(valor): o número deve ser menor ou igual ao limite.
- @DecimalMin(valor): equivalente a
@Min, mas aceitaBigDecimaleString. - @DecimalMax(valor): equivalente a
@Max, mas aceitaBigDecimaleString. - @Digits(inteiro, fracao): limita a quantidade de dígitos inteiros e decimais.
- @Size(min, max): tamanho da coleção, do array ou da
Stringdeve estar dentro do intervalo. - @Past: a data deve ser anterior ao momento atual.
- @Future: a data deve ser posterior ao momento atual.
- @NotBlank: a
Stringnão pode sernull, vazia ou composta apenas de espaços. - @Length(min, max): o comprimento da
Stringdeve estar dentro do intervalo. - @NotEmpty: a coleção, array ou
Stringnão pode sernullnem vazia. - @Range(min, max): o valor numérico deve estar dentro do intervalo.
- @Email: a
Stringdeve ter formato de e-mail. - @Pattern(regexp): a
Stringdeve corresponder à expressão regular. - @Valid: força a validação recursiva de um objeto associado.
Mensagens de Erro
As mensagens podem ser definidas de duas formas: diretamente no atributo message da anotação ou externamente, via MessageSource.
Mensagem em Hard Code
@NotBlank(message = "O nome é obrigatório")
private String nome;
Essa abordagem dificulta a manutenção e a internacionalização. Prefira externalizar as mensagens.
Mensagem Externalizada
Como visto anteriormente, basta usar a sintaxe {chave} e manter as traduções em arquivos .properties:
@NotBlank(message = "{nome.obrigatorio}")
private String nome;
Vários Objetos Validados no Mesmo Método
Quando um método de controle precisa validar mais de um objeto, cada objeto deve ser seguido pelo seu próprio BindingResult:
@RequestMapping("/pedidos/finalizar")
public String finalizar(
@Valid @ModelAttribute("cliente") ClienteModel cliente,
BindingResult clienteErros,
@Valid @ModelAttribute("pedido") PedidoModel pedido,
BindingResult pedidoErros) {
if (clienteErros.hasErrors()) {
return "pedidos/erro-cliente";
}
if (pedidoErros.hasErrors()) {
return "pedidos/erro-pedido";
}
return "pedidos/sucesso";
}
Na visão, cada formulário exibe seus próprios erros:
<form:form commandName="cliente">
<form:errors path="*" cssStyle="color: red"/>
</form:form>
<form:form commandName="pedido">
<form:errors path="*" cssStyle="color: red"/>
</form:form>
Tratamento de Exceções
O Spring MVC oferece três mecanismos principais para tratar exceções: implementar HandlerExceptionResolver, usar SimpleMappingExceptionResolver ou anotar métodos com @ExceptionHandler.
Implementando HandlerExceptionResolver
Crie um bean que decide qual visão exibir para cada tipo de exceção:
public class TratadorExcecoesGlobal implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof IllegalArgumentException) {
System.out.println("Argumento inválido detectado");
return new ModelAndView("erro/argumento-invalido");
}
return new ModelAndView("erro/geral");
}
}
Registre-o no XML:
<bean id="tratadorExcecoes" class="com.exemplo.spring.TratadorExcecoesGlobal"/>
Se o método retornar null, o Spring continua consultando os demais resolvedores registrados até que algum retorne uma visão.
SimpleMappingExceptionResolver
Uma alternativa declarativa é mapear exceções para visões diretamante no XML:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="erro/geral"/>
<property name="exceptionAttribute" value="excecao"/>
<property name="exceptionMappings">
<props>
<prop key="java.lang.IllegalArgumentException">erro/argumento-invalido</prop>
<prop key="java.lang.NumberFormatException">erro/formato-numerico</prop>
</props>
</property>
</bean>
O objeto da exceção ficará disponível no request com o nome excecao.
@ExceptionHandler
Para um tratamento mais específico dentro de um controller, use o método anotado:
@ExceptionHandler({ IllegalArgumentException.class, NumberFormatException.class })
public String tratarExcecao(Exception ex, Model model) {
model.addAttribute("excecao", ex);
if (ex instanceof IllegalArgumentException) {
return "erro/argumento-invalido";
}
return "erro/geral";
}
Ordem de Execução
Quando uma exceção ocorre, os mecanismos são consultados na seguinte ordem:
- O Spring procura primeiro por um
@ExceptionHandlerno próprio controller. - Em seguida, busca beans que implementam
HandlerExceptionResolver. - Caso haja múltiplos resolvedores, eles são executados na ordem de configuração.
- O processamento termina asim que um resolvedor retornar uma visão não nula.