view scope no JSF 2.0

Eu uso JSF há um bom tempo e sempre fui um defensor de que JSF é muito bom, mas hoje em dia ainda não da pra usar ele sozinho, por isso mesmo uso JSF + Facelets + Seam. Acredito que com o que temos hoje usar JSF sozinho não é lá das coisas mais produtivas do mundo, e isso em alguns casos faz com que pessoas critiquem o JSF porque não querem adicionar mais dependências ao projeto e deixam Facelets e Seam de fora.

Pessoalmente eu acho isso quase o mesmo nível de querer fazer um projeto sem Hibernate pra não ter mais dependências no projeto. Tá certo que hoje o caso é diferente pois temos a JPA, mas se não a tivéssemos ainda, você faria um projeto de verdade só com JDBC só pra não ter essa dependência com Hibernate? Pois bem, muita gente faz isso com JSF. Mas assim como a JPA veio para deixar as pessoas mais tranquilas e menos apavoradas de ter um framework a mais na aplicação, o JSF 2.0 também vai dar uma forcinha nesse sentido. Com ele poderemos ter uma aplicação funcionando super bem sem adicionar outros frameworks para isso. E isso devido duas coisas: a inclusão do Facelets (já apresentada aqui), e o novo escopo view.

Erros de validação

O JSF faz um papel muito bem feito abstraindo a camada web. Conseguimos desenvolver uma aplicação com JSF sem colocar a mão em HttpServletRequest e outros Http* da vida. O serviço seria muito bem feito de ponta a ponta se não fosse por um detalhe: a falta de um escopo que abstraísse também o http como o restante faz. Até o JSF 1.2 parece que essa parte foi esquecida, e enquanto o Faces tratava os escopos já conhecidos de toda aplicação web, como request, session e application, deixava uma porta aberta para o surgimento de componentes como t:saveState do Tomahawk, e depois as famosas conversations, no Seam.

Mas para que conversation e saveState servem? Um dos maiores problemas do JSF é a quantidade de “erro de validação” que acabam “estourando” na cara do desenvolvedor. Claro que sabendo como o JSF trabalha nós conseguimos desenvolver sem problemas. Eu sempre comento que a regra número 1 é sempre sobrescrever o método equals. Seguir boas práticas nunca é demais. E com isso boa parte dos erros de validação simplesmente somem. Um dia eu paro pra explicar melhor isso. Mas existem erros que acontecem como os famosos combos em cascata e commandButtons/commandLinks que não executam poque estão dentro de dataTable’s que só são preenchidas depois de alguma ação.

Imagine que temos uma página de cadastro de endereço, onde existem dois combos, o primeiro para estado e o segundo para cidades. Mas inicialmente o combo de cidades vem vazio, somente depois que selecionamos o primeiro, uma ação é executada para buscar as cidades. Selecionamos então “MS”, acontece uma nova requisição, as cidades são mostradas, selecionamos “Campo Grande” e mandamos salvar. Nisso vem o famoso erro de validação no combo das cidades. Isso acontece porque pela natureza stateless do managed bean de escopo request que estamos usando, o JSF esquece que já foi feita a requisição que busca as cidades, e para ele quando clicamos em salvar é como se fosse a primeira requisição. Então o JSF pensa: “como esse cara tá submetendo uma cidade sendo que eu nem mostrei os valores desse combo ainda? Essa cara tá me sacaneando, vou tesourar essa requisição dele”. Mas como fugir disso então? Coloca tudo no escopo de sessão? A resposta para esse problema é a utilização de escopos mais refinados, não disponíveis (ainda) no JSF padrão.

Contextos mais “espertos”

Na minha opinião, uma das melhores coisas do Seam é a conversação. Um conversação é um “escopo” maior que um request e menor que a sessão do usuário, e que tem um início e fim definidos por nós. Quando iniciamos uma conversação o contexto “vira” statefull até que seja finalizada, quando então “vira” stateless novamente. Claro que o Seam é muito mais que isso, mas para o assunto do post isso é o mais relevante. Fora Conversation, o Seam tem um escopo Page, cujo funcionamento é basicamente o mesmo do saveState. Dessa forma, o contexto é statefull enquanto submetemos para a mesma página. Uma conversação é algo mais refinado, que pode envolver várias telas, mas para a grande maioria dos casos essas várias requisições que precisamos manter o estado são feitas para mesma tela, como nos exemplos dos combos de estado e cidade e da dataTable resultando de filtro com uma action dentro.

View Scope

Exatamente nesse caso que o escopo view vem suprir essa carência do JSF padrão. Um managed bean com esse escopo permanece na memória enquanto submetemos para a mesma tela. Não quero dizer que você nunca mais vai precisar de outras coisas, ou que o Seam nada mais é do que fazer de outra forma o que um saveState já fazia antes, mas sim que agora para a maioria das aplicações já poderemos usar apenas JSF 2 sem ter que usar outras ferramentas para nos auxiliar. Claro que um conjunto de componentes ricos sempre será bem vindo, e que certamente iremos continuar querendo usar algumas coisas mais específicas que o Seam/WebBeans nos oferecem. Mas o grande ganho é que para o básico não precisamos de mais nada, coisa que infelizmente hoje, com JSF 1.2, ainda não acontece.

Como já escrevi bastante, vamos a um exemplo. O nosso managed bean de escopo view ficaria assim:

@ManagedBean(name="enderecoBean")
@ViewScoped
public class EnderecoBean {
 
    @PostContruct
    public void construct() {
        //chamado só quando o managed bean é colocado no escopo view,
        //e não a cada requisição como acontecia com o escopo request
    }
 
    @PreDestroy
    public void destroy() {
        //chamado quando outra view for chamada através do UIViewRoot.setViewId(String viewId)
    }
 
}

Com esse escopo o JSF cria o managed bean na primeira requisição onde ele for preciso (se for lazy), e só o remove da memória quando for chamado o método UIViewRoot.setViewId(String viewId) para indicar que iremos trabalhar com outra página.

24 thoughts on “view scope no JSF 2.0

  1. Ótimo post Gilliard, muito obrigado por trazer as novidades do JSF 2 para nós!

  2. Muito legal o post, gostei da linguagem do server pensando
    rs

  3. Excelente post Gilliard,

    A falta de escopos de conversação na especificação Jsf sempre foi um problema, a utilização de mecanismos para soluciona-lo sempre existiram, mas sempre nos obrigou a adicionar um novo framework/componente ao projeto para resolver um problema que não deveria existir na especificação.

    A falta dele causa vários problemas, entre os mais comuns como você mesmo citou tem o problema das combos aninhadas (ou não), dos commandLinks de uma datatable entre vários outros, contudo são solucionáveis mesmo com o escopo de request e um razoável conhecimento do ciclo de vida do Jsf.

    A idéia do Jsf é encapsular toda a complexidade do http através de componentes e eventos, nos dando a impressão de um ambiente stateful, mas isso não é nada fácil, e piora mais ainda quando tentamos integrar Jsf a um framework de persistência ORM, aí sim as coisas pioram.

    O Seam não só resolve o problema de conversação entre managed beans, mas sim a conversação de escopo de persistência também, fazendo assim a integração do Jsf e JPA mais simples e prática! Isso é formidável!

    O problema de validação é um dos problemas mais comuns que vejo entre os novos desenvolvedores que usam Jsf, eu já bloguei sobre isso faz algum tempo,
    http://www.rponte.com.br/2008/02/01/selectonemenu-converter-erro-de-validacao/

    Enfim, gostei do post e principalmente de ter a certeza que JSF2.0 trará no mínimo um escopo “page” que será útil em praticamente quase todas as aplicações, principalmente se as páginas forem construídas de maneira não tradicional.

    Abraços e parabéns pelo post.

  4. Obrigado a todos,
    e valeu pelo link Rafael, com ele eu nem preciso mais escrever aquela parte que mencionei no post que escreveria depois, pois era pra descrever exatamente o caso que você escreveu no seu post, muito bom!

    A parte de conseguir resolver tudo com JSF entendendo o ciclo de vida é complicado. Infelizmente cheguei a conclusão que poucas pessoas que desenvolvem com JSF realmente entendem o seu ciclo de vida, o que acaba causando vários problemas. Eu acabei atropelando um pouquinho essa parte para não alongar demais o post pois pretendia escrever melhor depois, mas graças ao teu post não vou precisar mais :D

    Mas a grande vantagem do JSF 2.0, na minha opinião, é que ele já virá mais completo de série, possibilitando que seja possível fazer uma aplicação razoavelmente bem feita com JSF sem conhecer tanto assim do cilco de vida. Mas sobrescrever equals e hashCode continua sendo necessário. Como eu falei no post, nunca é demais seguir boas práticas, pois elas acabam nos livrando de problemas que nem sabíamos que iríamos passar sem elas ;)

  5. E ai Gilliard pesquisando aqui no Google sobre JSF 2.0 axei seu blog [:D], gostei… valeu.

  6. Pingback: Entendendo propósito JSR 299 : JCDI/Web Beans « rafa.rocha

  7. Obrigado pelo blog Gilliard.
    Eu tô com dificuldades no ViewScoped. Em qual momento exatamente ele remove o bean? tem como Exemplificar?
    Pois mesmo depois de tudo feito e saído da página, se eu acessá-la diretamente pela url o formulário volta todo preenchido.

  8. Pingback: Gilliard Cordeiro » JavaServer Faces 2.0 – Early Draft Review 2

  9. Gilliard, estou tendo o mesmo problema que o amigo hugo. Configurei um bean com @ViewScoped e dois metodos com @PostConstruct e @PreDestroy, acessando o bean sempre do mesmo xhtml, e retornando para o mesmo, o JSF continua tratando o bean como request e recebo a seguinte informação no console:
    INICIANDO BEAN: com.controle.TesteBean
    19/01/2010 08:56:15 javax.faces.component.UIViewRoot$ViewMap put
    WARNING: Definindo valor de atributo não serializável em ViewMap

    Implementei serializable no bean para testar, mas ainda continua tratando como request. Ao mudar para @SessionScoped, funciona como deve, dentro do escopo de sessão.

    Any thoughts? :)

  10. Marcelo, como faz muito tempo que você postou essa dúvida, queria saber se ainda está com esse problema. Se estiver vou dar uma testada aqui e te falo, se não, qual era o problema?

  11. Também não estou conseguindo usar o escopo view. Me dá uma mensagem de que deve ser serializável, mas se torno serializável, não funciona mesmo assim.

  12. @Vanessa Schissato
    Qual versão do JSF você está usando? O que exatamente acontece? Como você está fazendo para testar? Essa mensagem que ele dá não seria somente um warn? Se for uma exception, você chegou a ver na stack se não tem outro motivo?
    São muitas perguntas, eu sei, mas como não estou vendo você testar tenho que perguntar.

    Atualmente estou utilizando o mojarra 2.0.2.FCS com primefaces 2.0.2 e o escopo funciona normalmente. O que pude perceber de problema é com a paginação ajax do componente dataTable do primefaces, que chama o método anotado com @PreDestroy… mas mesmo assim não terde os valores do managed bean.

  13. Gostaria de saber como passar um parâmetro de uma página para outra página.
    A 1º página ira chamar um método de uma classe @ViewScoped!

  14. Olá Gilliard

    Parabéns pelo post! Eu estava procurando detalhes sobre este escopo novo do JSF 2 que foi a “salvação da lavoura” para o meu caso que é justamente o citado (Estado/Cidade).

    Me parti de rir na parte a seguir:
    “Então o JSF pensa: “como esse cara tá submetendo uma cidade sendo que eu nem mostrei os valores desse combo ainda? Essa cara tá me sacaneando, vou tesourar essa requisição dele”.”

    Eu postei no blog do Rafael Ponte um texto referente à dúvidas no caso de combos aninhadas e converter. Me bati um monte e no fim não usei converter. Eu utilizo o id e nome como exemplo :

    itens.add(new SelectItem(estado.getIdEstado(), estado.getEstado()));
    

    Qual o empecilho de continuar o desenvolvimento da minha aplicação sem utilizar converter ? Qual a real necessidade se, no meu caso, eu só preciso dos ids no retorno ?

    Abraço,
    Rodrigo

  15. Oi Rodrigo, obrigado.
    Não tem problema nenhum não usar converter. Eu prefiro pois fica mais “limpo”. No mundo OO não existem ids, e sim objetos, então por isso eu prefiro usar os conversores. Mas fica tranquilo, se isso não “mela” teu sistema, e a equipe prefere assim, então beleza. Mas eu acho que fica mais complicado (tem que criar a lista de SelectItem) e menos OO (mas como falei, se não incomoda, então não seja purista demais e continue como está).
    Valeu.

  16. Olá Gilliard,

    Valeu pela resposta. Fiquei em dúvida agora se o uso de converter vai ser interessante para os proximos passos que eu seguir na parte de CRUD usuários, por exemplo.

    Explicando melhor minha dúvida, tenho lá a tela de pesquisa onde eu clico em um usuário para editar as informações. Neste caso eu utilizaria no método a recuperação das informações via BO > DAO de forma normal no método, por exemplo, “recuperaUsuario()” sem o uso de converter.

    Eu postei lá no blog do Rafael Ponte uma dúvida bem nessa linha de raciocínio onde eu tenho uma situação diferente para o esquema de Estado/Cidade. Tenho lá o meu bean RegiaoBean onde dentro há a lógica para o load de estados e também das cidades de acordo com o estado. Ou seja, este mesmo bean é referenciado nas 2 comboboxs. Neste caso eu questionei ele como utilizaria converter. Nos exemplos que vejo na internet, temos lá um UserBean com atributo “User user” e atributo “List users”. Aí neste caso o converter é tranquilo de se fazer mas para o meu caso que tenho no mesmo managedBean os atributos Estado e Cidade, não consegui visualizar como implementar um converter. Acredito que converter deve ser visto para uma situação bem específica em que realmente eu preciso recuperar as informações para uma determinada ação, e esta situação não deve ser confundida com a recuperação normal do objeto por meio de um método exemplo “buscarUsuario()”. Menciono isso pois no converter lá, chamar direto Dao ou Bo me parece um tanto estranho e além do que “quebra” as rotinas de CRUD que no meu ver devem ser chamadas no managedBean próprio que neste caso seria UsuarioBean.

    Abraço,
    Rodrigo

    Abraço,
    Rodrigo

    Abraço,
    Rodrigo

  17. @Rodrigo

    Ops, desculpe os textos repetidos, costumo escrever em bloco de notas antes de postar direto aqui.

    Por favor, Gilliard e quem mais for ler a parte que escrevi (“quebra as rotinas CRUD”), não me crucifiquem, rs. Vou retificar o que escrevi. Vejo a grande sacada no uso de converter pois ele está ligado diretamente ao componente. Ou seja, no caso da combobox, não preciso disparar um método via listener/action. O converter já me ajuda nisso. No meu caso eu não precisei utilizar mas quem precisar, é um baita recurso!!

    Bom, espero ter entendido bem esse lance de converter.

    Abraço,
    Rodrigo

  18. @Rodrigo

    Não teria como você injetar esse teu bean que recupera as informações no converter e assim ele te entregar os objetos prontos seguindo a lógica específica da tua aplicação?

    Para fazer isso, coloque o seam-faces na tua app (2 jars sem configuração nenhuma).

  19. Agora o ViewScope é meio complicado em um caso específico, não é?

    Se eu abrir uma modal pra manipular outros dados, o que ocorre com a página que ficou atrás?

    Pois com a anotação @KeepAlive do RichFaces 3.3, isso não acontecia, porque na verdade não há navegação aí.. agora com o @ViewScoped do JSF 2, isso fica como mais um problema a se resolver.

    Claro que pode ser resolvido com escopos customizados, mas quem quiser migrar uma aplicação de JSF 1.2 com RichFaces 3.3 pra JSF2 vai ter uns pepinos pra pensar hehehee

  20. @Rodrigo
    Na verdade quando você abre um modal, não faz navegação. Logo o @ViewScoped vai funcionar igual nesse caso.
    Agora em outros casos que o @KeepAlive resolveria o @ViewScope não vai funcionar igual, e acho que no post dá pra ter uma noção boa do que muda, mas no exemplo que você deu fica igual.
    No JSF 2.2 a coisa deve melhorar um pouco. Quando as mudanças estiverem implementadas eu atualizo o post.
    Valeu.

  21. Como eu adiciono um objeto no @ViewScoped pelo ManagedBean?
    Fazendo um paralelo com o Apache Trinidad, que adiciona o PageFlowScope ao JSF, eu adicionaria assim:

    import org.apache.myfaces.trinidad.context.RequestContext;
    RequestContext rc = RequestContext.getCurrentInstance();
    rc.getPageFlowScope().put(name, value);

  22. Oi @Gregory,

    Acho que a forma “padrão” (JSF 2.0 e 2.1) de fazer isso é com o Flash Scope:

    Flash flash = FacesContext.getCurrentInstance().getExternalContext().getFlash();
    flash.putNow(name, value)

    Agora igual ao trinidad ou o spring que tem “flow”, só no JSF 2.2.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>