24 de dezembro de 2011

Tutorial Spring MVC

Faz muito tempo que não posto um tutorial completo com um projeto (código exemplo) sobre um framework, atualmente estou desenvolvendo aplicação com Spring MVC e isso acabou me motivando a escrever um tutorial sobre ele.

Aconselho antes a ler sobre Patterns como DAO, Repository, MVC, TO, PO, VO, DTO.

Passo 1:

Executem o Eclipse -> File -> New -> Project -> Dynamic Web Project

Passo 2:

Crie essa estrutura básica de pacotes.



Passo 3:

Criei dentro do pacote domain um bean
public class Produto {
     public Produto(Long id, String nome, String descricao, double preco, String cor, Long quantidade) {
     this.id = id;
     this.nome = nome;
     this.descricao = descricao;
     this.preco = preco;
     this.cor = cor;
     this.quantidade = quantidade;
}
private Long id;
private String nome;
private String descricao;
private double preco;
private String cor;
private Long quantidade;

//getters and setters

}



Passo 4:

Dentro do pacote dao criei a ProdutoDao e a ProdutoDaoI:

Interface:

public interface ProdutoDaoI {

    public void salva(Produto produto);
   
    public List pegaTodos();
   
    public void remove(Produto produto);
   
    public Produto pegaPorId(Long id);
}

Implementação:


Dentro da implementação do meu DAO notem a anotação @Repository essa anotação diz que ali será um repositório de dados ou seja seu DAO (camada de persistência),  com essa anotação automaticamente as exceptions são traduzidas e você não terá preocupações com lock otimista do JPA por exemplo.

@Repository
public class ProdutoDao implements ProdutoDaoI {

    private final static List produtos = new ArrayList();
   
    static {
        populaProdutos();
    }
   
    public void salva(Produto produto) {
        produto.setId(produtos.size() +1l);
        produtos.add(produto);
    }

    public List pegaTodos() {
        return Collections.unmodifiableList(produtos);
    }

    public void remove(Produto produto) {
        Iterator it = produtos.iterator();
        while(it.hasNext()) {
            Produto existente = it.next();
            if(existente.getId().equals(produto.getId())) {
                it.remove();
                break;
            }
        }
    }

    private static void populaProdutos() {
        produtos.add(new Produto(1l, "iPhone", "Celular da apple", 299.90, "prata", 10L));
        produtos.add(new Produto(2l, "DVD Yu Yu Hakusho", "Anime sobre Yusuke Urameshi um detetive sobrenatural.", 1999.99, "prata", 20L));
        produtos.add(new Produto(3l, "Caelum OnLine", "Cursos online da Caelum", 249.00, "verde", 60L));
        produtos.add(new Produto(4l, "Fred Rovella Show", "cd de músicas italianas", 29.90, "azul", 100L));
    }

    public Produto pegaPorId(Long id) {
        for(Produto produto : produtos) {
            if(produto.getId().equals(id)) return produto;
        }
        return null;
    }

}

Passo 5:
 
Nesse Passo serão utilizadas 2 anotações do Spring a @Service e a @Autowired (também citei a @Qualifier para quem vá utilizá-la futuramente).@Autowired: Serve para injeção de beans, como meus beans são simples posso usar tranquilamente essa anotação, mas se meu projeto começar a crescer muito será necessário usar a anotação @Qualifier para indicar qual bean quero injetar e evitar erros de injeção.
@Service: Serve para anotar a camada de serviço.

Dentro do pacote service criei as classe ProdutoService e a interface ProdutoServiceI, é ali que ficarão suas regras de negócio, ela receberá os parametros da camada view, chamará a dao e retornará a resposta para a view.

Interface:
public interface ProdutoServiceI {

    public void salva(Produto produto);

    public List pegaTodos();

    public void remove(Produto produto);

    public Produto pegaPorId(Long id);
}

Implementação:
@Service
public class ProdutoService implements ProdutoServiceI{

    @Autowired
    private ProdutoDao    produtoDao;

    public void salva(Produto produto) {
        produtoDao.salva(produto);
    }

    public List pegaTodos() {
        return produtoDao.pegaTodos();
    }

    public void remove(Produto produto) {
        produtoDao.remove(produto);
    }

    public Produto pegaPorId(Long id) {
        return produtoDao.pegaPorId(id);
    }
}


Passo 6:


As classes da camada de pacotes view, criei mais 2 pacotes form e controller, no pacote Form eu criei uma classe que terá os dados das páginas JSP e no pacote Controller as classes que controlam as chamadas do JSP para a service (Leia Controller como Managed Bean do JSF ou as Actions do Struts).


Form:

Aqui podemos ver as anotações do Hibernate Validator.
@NotEmpty: Anotação para validar se o valor é vazio.

@NotNull: Anotação para validar se o valor é nulo.
Message: Você pode customizar as mensagens que por padrão são em inglês.

public class ProdutoForm {

    private Long id;
    @NotEmpty(message = "Valor não pode ser vazio")
    private String nome;
    @NotEmpty(message = "Valor não pode ser vazio")
    private String descricao;
    private double preco;
    @NotEmpty(message = "Valor não pode ser vazio")
    private String cor;
    @NotNull(message = "Valor não pode ser nulo")
    private Long quantidade;

    //getters and setters

}


Controller:
@Controller
@RequestMapping("/produto/**")
public class ProdutoController {

    @Autowired
    private ProdutoService    produtoService;

    private ProdutoForm        produtoForm;

    private Produto         produto;
   
    @RequestMapping("/produto/formulario")
    public ModelAndView formulario() {
        return new ModelAndView("formulario").addObject("produtoForm", new ProdutoForm());
    }
   
    @RequestMapping("/produto/adiciona")
    public ModelAndView adiciona(@Valid ProdutoForm produtoForm, BindingResult result) {
        if (result.hasErrors()) {
            return new ModelAndView("formulario").addAllObjects(result.getModel());
        }
        populaBean(produtoForm);
        produtoService.salva(produto);
        return new ModelAndView("lista").addObject("produtos", produtoService.pegaTodos());
    }

    @RequestMapping("/produto/lista")
    public ModelAndView lista() {
        return new ModelAndView("lista").addObject("produtos", produtoService.pegaTodos());
    }
   
    @RequestMapping(value = "/produto/remove", method = RequestMethod.GET)
    public ModelAndView remove(@Valid @RequestParam(value = "produto.id") long id) {
        produto = new Produto();
        produto.setId(id);
        produtoService.remove(produto);
        return new ModelAndView("lista").addObject("produtos", produtoService.pegaTodos());
    }
   
    @RequestMapping("/produto/consulta")
    public ModelAndView consulta() {
        return new ModelAndView("consulta").addObject("produtoForm", new ProdutoForm());
    }
   
    @RequestMapping("/produto/pesquisa")
    public ModelAndView pesquisa(@Valid ProdutoForm produtoForm, BindingResult result) {
        produto = produtoService.pegaPorId(produtoForm.getId());
        produtoForm = populaForm(produto, produtoForm);
        return new ModelAndView("exibeProduto").addObject("produtoForm", produtoForm);
    }
   
    private void populaBean(ProdutoForm produtoForm) {
        //implementação do método de/para
    }
   
    private ProdutoForm populaForm(Produto produto, ProdutoForm produtoForm) {
       //implementação do método de/para
    }

   //getters and setters
   
}

É aqui que acontece a atuação do Spring MVC, temos a anotação @Controller que diz que essa classe será a página que receberá os valores do formulário e depois irá passar um objeto ou redirecionar para outras páginas.


@Controller: Anotação que diz que aquela classe terá como função  transformar o dados do formulário em dados do modelo, ou seja, gerenciamento entre as camadas View e Model.

@RequestMapping: Essa anotação é onde definimos o caminho do HTTP que irá ser utilizado na nossa aplicação, sendo mapeado na classe, todas as chamadas que contém "/produto/*" serão analisadas pelo Controller.

@RequestParam(value = "produto.id"): Podemos receber tanto o Form completo como apenas o parametro que desejamos com a anotação @RequestParam como parametro do método no Controller.

Podemos também enviar e receber mais de um objeto do(para o) formulário através do ModelAndView, nos frameworks mais antigos enviávamos apenas um objeto no retorno, com o ModelAndView podemos enviar mais.
Exemplo:
return new ModelAndView("exibeProduto").addObject("produtoForm", produtoForm);
Na linha acima estamos dizendo, ModelAndView coloque na página exibeProduto o objeto produtoForm.
ou ModelAndView eu quero que na página lista você coloque a lista de produtos.

return new ModelAndView("lista").addObject("produtos", produtoService.pegaTodos());

OBS: repare que o que coloco dentro do ModelAndView("lista") é o JSP lista.jsp.
OBS 2: Spring 3 trabalha por convenção, então baseado em suas anotações, JSPs e métodos , o que está dentro do @RequestMapping é o que criará na uri o caminho que chamará sua página.
Exemplo:
@RequestMapping("/produto/consulta") chamará o JSP consulta e na url ficará: /produto/consulta

Para validar o formulário utilizamos o BindingResult, ele verifica as validações e podemos retornar a página em seu estado no momento que pegamos o erro (ao invés de salvar todos objetos em param ou hiddens, mantendo seu estado.

Para validar nos parametros do método do controller que queremos fazer a validação colocamos no objeto a anotação @Valid, por exemplo: @Valid @RequestParam(value = "produto.id") long id, verifica se o id é válido ou @Valid ProdutoForm produtoForm no caso de um formulário.
Dentro do método adicionamos:


        if (result.hasErrors()) {
            return new ModelAndView("formulario").addAllObjects(result.getModel());
        }

No retorno estamos dizendo: Hei, se houver erro ModelAndView, volta pra página e devolve os objetos que estavam populados com as respectivas mensagens de erro.

Passo 7:
Para configurar o Spring precisamos colocar alguns XMLs (acalme-se não será um caminhão de XML como era antigamente)

Dentro de WebContent -> WEB-INF -> spring econtraremos o servlet-spring.xml e o app-spring.xml

servlet-spring.xml
[?xml version="1.0" encoding="UTF-8"?]
[beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd"]

     
    [bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"]
      [property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/]
      [property name="prefix" value="/WEB-INF/jsp/"/]
      [property name="suffix" value=".jsp"/]
    [/bean]

[/beans]
ViewResolver: aqui ele irá caçar na pasta "WEB-INF/jsp/" os nossos arquivos com sulfixo ".jsp"

app-spring.xml
[?xml version="1.0" encoding="UTF-8"?]
[beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd]
       
    [mvc:annotation-driven /]
    [context:annotation-config /]
   
   [ mvc:resources location="/css/" mapping="/resources/"/]
   [ mvc:resources location="/images/" mapping="/resources/"/]
   [ mvc:resources location="/js/" mapping="/resources/"/]
       
    [context:component-scan base-package="br.com.possege.loja" /]
  
   
    [import resource="spring-servlet.xml"/]

    [bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /]
   
    [mvc:resources mapping="/resources/**" location="/resources/" /]

    [bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource"]
        [property name="basename" value="classpath:application-message" /]
        [property name="defaultEncoding" value="UTF-8" /]
        [property name="fallbackToSystemLocale" value="false" /]
    [/bean]
    
    [bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"]
        property name="paramName" value="lang" /]
    [/bean]
    [bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"]
        [property name="defaultLocale" value="pt_BR" /]
    [/bean]
 [/beans]

mvc:annotation-driven: Permite enviar as requisições das classes anotadas com @Controller.
context:annotation-config: Procura todas as classes anotadas com @PersistenceContext, @Autowired, entre outros, fazendo automaticamente a injeção de dependência.
context:component-scan: Procura todas as classes anotadas no pacote definido, assim não precisamos mapeá-las em XML, quando o scan é feito as classes são passadas por um filtro e é criada a definição em um bean para cada uma delas, quem determina a definição do bean é a anotação.
mvc:resources: Serve para o acesso GET dos arquivos estáticos, como CSS, JS, etc.
import resource: Importa o outro XML de configuração do Spring.

Passo 8:

Vamos configurar o web.xml
[?xml version="1.0" encoding="UTF-8"?]
[web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"]
[display-name]projetoSpring[/display-name]

    [servlet]
        [servlet-name]spring[/servlet-name]
        [servlet-class]org.springframework.web.servlet.DispatcherServlet[/servlet-class]
        [init-param]
            [param-name]contextConfigLocation[/param-name]
            [param-value]/WEB-INF/spring/app-config.xml[/param-value]
        [/init-param]
        [load-on-startup]1[/load-on-startup]
    [/servlet]

    [!-- Spring MVC Dispatcher Servlet --]
    [servlet-mapping]
        [servlet-name]spring[/servlet-name]
        [url-pattern]/[/url-pattern]
    [/servlet-mapping]
   
    [!-- Permitir comandos HTTP RESTfull (GET, POST, PUT, DELETE) --]
    [filter]
        [filter-name]hiddenHttpMethodFilter[/filter-name]
        [filter-class]org.springframework.web.filter.HiddenHttpMethodFilter[/filter-class]
    [/filter]

    [filter-mapping]
        [filter-name]hiddenHttpMethodFilter[/filter-name]
        [servlet-name]spring[/servlet-name]
    [/filter-mapping]

    [session-config]
        [session-timeout]10[/session-timeout]
    [/session-config]

    [!--SiteMesh --]
    [filter]
        [filter-name]sitemesh[/filter-name]
        [filter-class]com.opensymphony.sitemesh.webapp.SiteMeshFilter[/filter-class]
    [/filter]

    [filter-mapping]
        [filter-name]sitemesh[/filter-name]
        [url-pattern]/*[/url-pattern]
    [/filter-mapping]
   
    [!-- Encoding --]
    [filter]
        [filter-name]encodingFilter[/filter-name]
        [filter-class]org.springframework.web.filter.CharacterEncodingFilter[/filter-class]
        [init-param]
            [param-name]encoding[/param-name]
            [param-value]UTF-8[/param-value]
        [/init-param]
        [init-param]
            [param-name]forceEncoding[/param-name]
            [param-value]true[/param-value]
        [/init-param]
    [/filter]
    [filter-mapping]
        [filter-name]encodingFilter[/filter-name]
        [url-pattern]*[/url-pattern]
    [/filter-mapping]
   
    [error-page]
        [error-code]405[/error-code]
        [location]/erro[/location]
    [/error-page]
    [error-page]
        [error-code]500[/error-code]
        [location]/erro[/location]
    [/error-page]
    [error-page]
        [error-code]404[/error-code]
        [location]/erro[/location]
    [/error-page]
    [error-page]
        [error-code]400[/error-code]
        [location]/erro[/location]
    [/error-page]
[/web-app]

 Aqui configuramos o Spring, uma pog para permitir comandos HTTP Restfull, os filtros, a configuração do SiteMesh, Encoding e páginas de erro.

O redirecionamento de um 404, 500, etc, particularmente achei bizarro a solução que encontrei foi criar um ErroController e apontar para um JSP chamado erro.
Até cheguei abrir uma discussão no GUJ mas não obtive resposta.

ErroController:
@Controller
@RequestMapping("/")
public class ErroController {

    @RequestMapping("/**")
    public ModelAndView erro() {
        return new ModelAndView("erro");
    }
}

JSP:
[%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%]
[html]
    [head]
        [title][spring:message code="produto.erro.titulo" /][/title]
    [/head]
    [body]
        [p]
            [spring:message code="produto.erro.msg_default_erroGenerico" /]
        [/p]
    [/body]
 [/html]

Passo 9:

Configurando o SiteMesh, já adianto que não fiz CSS para esse exemplo, mas o SiteMesh é um ótimo template e já o deixei configurado.

Para configurar o SiteMesh eu criei na pasta WEB-INF o decorators.xml e uma sub pasta chamada decorators com o principal.jsp dentro.

decorators.xml
[xml]
[?xml version="1.0" encoding="UTF-8"?]
[decorators defaultdir="/decorators/principal.jsp"]

    [decorator name="principal"]
        [pattern]/*[/pattern]
    [/decorator]
   
[/decorators]
[/xml]

principal.jsp
[!DOCTYPE HTML]
[%@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator"%]
[%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%]
[%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%]
[html]
    [head]
        [title][decorator:title default="Possege"/][/title]
        [meta charset="utf-8" /]
        [meta http-equiv="pragma" content="no-cache" /]
        [meta http-equiv="expires" content="-1" /]
        [meta http-equiv="cache-control" content="no-cache" /]
       
        [meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" /]       
        [meta name="format-detection" content="telephone=no" /]

        [meta name="apple-mobile-web-app-capable" content="yes" /]
        [meta name="apple-mobile-web-app-status-bar-style" content="black" /]       
       
       
        [decorator:head /]
    [/head]
    [body]
        [div]
            [div]
                [decorator:body/]
            [/div]
        [/div]
        [script src="http://www.google.com/jsapi"][/script]
       [script type="text/javascript" src="[c:url value="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" /]"][/script]
  [/body]
[/html]

Passo 10:

Criei uma pasta na raiz do projeto chamada resources que leva meu application-message_pt_BR.properties para internacionalização.
produto.lista.titulo=Lista de Produtos

produto.novo.titulo=Novo Produto
produto.novo.nome=Nome
produto.novo.descricao=Descri\u00E7\u00E3o
produto.novo.preco=Pre\u00E7o
produto.novo.cor=Cor
produto.novo.quantidade=Quantidade

produto.consulta.titulo=Consulta Produto
produto.consulta.codigo=C\u00F3digo
produto.consulta.lista=Listar Todos Produtos
produto.consulta.consulta=Consultar

produto.exibe.titulo=Produto Encontrado
produto.exibe.codigo=C\u00F3digo:
produto.exibe.nome=Nome:
produto.exibe.descricao=Descri\u00E7\u00E3o:
produto.exibe.preco=Pre\u00E7o:
produto.exibe.cor=Cor:
produto.exibe.quantidade=Quantidade:

produto.erro.titulo=Erro
produto.erro.msg_default_erroGenerico=Ocorreu um erro.

Passo 11:

Por fim criei todos os JSPs, não vou colocá-los aqui no blog mas vou colocar algumas tags que são essênciais e úteis.

As tag libs que mais utilizei:
[%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%]
[%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%]
[%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%]
[%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%]

Tags úteis:
[spring:message code="produto.exibe.preco" /]
[c:url var="pesquisa" value="/produto/pesquisa" /]
[form:form action="${pesquisa}" id="produtoForm" modelAttribute="produtoForm" method="post"]
[form:input path="nome" /] 
[form:errors path="nome"  /]
[fmt:formatNumber value="${produtoForm.preco }" type="currency"/]
[fmt:formatDate pattern="dd/MM/yyyy" value="${produtoForm.algumaData}"/]

spring:message: Coloca as mensagens, quando code ele pega do properties, quando text você digita o texto.
c:url: Submeter a página por um link, no caso estou passando o valor para o form:form
form:form: Nosso formulário, passo a ação, id e o modelAttribute que o Controller irá mandar, posso escolher enviar por get ou post.
for:input: Criar os campos texto para digitação no formulário.
form:errors: Pegar as mensagens de erro enviadas pelo BindingResult.
fmt:formatNumber: Formatar o valor no caso em moeda, há porcentagem e outros valores.
fm:formatDate: Formatar as datas no padrão que defini.

Fiz também um remove ajax utilizando JQuery dentro da aplicação:


[a href="javascript:void(0);" onclick="remove(${produto.id}); return false;"]Remove[/a]

[script type="text/javascript" src="[c:url value="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" /]"][/script]
[script type="text/javascript"]
    function remove(id){
        $.get('remove?produto.id='+id,function(){
            $('#produtoRemove-' + id).hide();
            alert('produto removido com sucesso');
            $('#produtoRemove-' + id).remove();
        });
    }
[/script]

Bom todo o fonte disponibilizei o download no GitHub.

Espero que este tutorial o ajude nos pontos que tive problemas, a ideia era mostrar uma solução com o ótimo framework MVC que os Spring nos dá.

Aconselho também estudar VRaptor é muito parecida a forma de programar e é um excelente framework MVC.

Agradecimenos especiais para Caelum e ao Christian Reichel por ter me ajudado com as dúvidas que surgiram quando comecei a programar com Spring MVC.

Comentários são bem vindos e as referências para deixar o seu template mais bonito e mais detalhes sobre Spring MVC e o VRaptor estão abaixo:


Washington Botelho - SiteMesh
Edson Gonçalves - Spring MVC
Rodolfo Chaves - Anotações do Spring
Valdemar Junior - Spring MVC com Tiles
Spring Source - Documentação Oficial
Jérôme Jaglale - Spring MVC Fast Tutorial
NetBeans - Tutorial Spring
Mkyoung - Hello World Spring
Caelum - FJ 27 Spring Framework
Caelum - FJ 28 VRaptor
Caelum - VRaptor online


Um Feliz Natal para todos. =D

5 comentários:

Unknown disse...

Really a good info regarding the Spring tutorial, I really appreciate, More information regarding the Interview questions you can visit at
http://www.tekhnologia.com/search/label/Spring%20Interview%20Questions

Paulo Roberto disse...

Muito bom o tutorial, conseguir tirar muitas duvidas, mas estou com tendo um problema com o seu exemplo, ele não entra não pagina de erro!

Bregaida disse...

Paulo, tudo bem?
Então, fez aquela jogada com "/ e /**"?
Só consegui gerar a página de erro com esse modo bizarro, como está no tópico do GUJ também.

Vinicius Martins disse...

Opa, sei que o post já tem bastaaante tempo, mas me ajudou muito, nessa nova fase, onde estou estudando o spring MVC.
A Unica duvida que não consegui foi a seguinte:
Se eu tiver um campo em produto que em certa situação não poderá ser vazio, porém em outra situação poderá. Como fazer essa validação ?

Bregaida disse...

Boa noite Vinicius, tudo bem?
Sem problema quanto ao tempo, importante que o tutorial está ajudando, vamos lá:
Você pode criar no seu Controller ou no seu Form um método de validação no qual vc faz a condição de quando você deve dar obrigatoriedade ao campo ou não, no caso dele ser obrigatório retorne um BinderResult com @Valid e passa a mensagem quando estiver com erro, retornando para o formulário preenchido já com a mensagem como aqui:
public ModelAndView adiciona(@Valid ProdutoForm produtoForm, BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView("formulario").addAllObjects(result.getModel());
}
}

Isso resolverá seu problema =D