Atualizando programaticamente componentes JSF em requisições AJAX

É muito comum utilizarmos o componente p:outputPanel do Primefaces com o atributo autoUpdate=true quando queremos atualizar componentes automaticamente sempre que uma requisição AJAX é disparada por algum componente. Isso é mais comum ainda quando temos um template Facelets na qual as mensagens de sucesso e erro estão no topo da página.


    

O interessante é que podemos simular este mesmo comportamento de forma programática e sem a necessidade de um conjunto de componentes. Basta a nova API do JSF 2 responsável pela atualização parcial de páginas e requisições AJAX: PartialViewContext. Podemos ver como API funciona no código abaixo:

FacesContext.getCurrentInstance().getPartialViewContext()
            .getRenderIds().add(":mensagens");

Na verdade, a API do JSF 2 só nos permite atualizar o componente h:messages programaticamente. Para que o componente seja sempre atualizado a cada evento AJAX, nós podemos usar um PhaseListener, como discutido no novo post do blog da TriadWorks: Renderizando automaticamente componentes JSF em requisições AJAX.

Além da API PartialViewContext, a solução usa basicamente conceitos de PhaseListener e do ciclo de vida! Estes conceitos são estudados e praticados no curso de JSF 2 com Spring da TriadWorks.

Vale a pena a leitura!

@ViewScoped, o ovo e a galinha

Uma das funcionalidades mais esperadas do JSF2 sem dúvida foi o escopo de visão (view scope), mais conhecido como @ViewScoped. Com esta simples anotação se tornou possível manter managed beans num escopo maior do que requisição e menor do que sessão. O que resolveu a maioria dos problemas relacionados a ajax, uso inadequado do escopo de sessão e idas e vindas desnecessárias ao banco de dados.

Apesar de todas as coisas boas trazidas pelo @ViewScoped, ele trouxe consigo alguns problemas e limitações que a maioria dos desenvolvedores não estavam acostumados, como problemas de serialização, escopo restrito apenas a mesma página, integração precária com CDI e Spring, incompatibilidade com JSTL e a necessidade de limpar a todo instante os dados do managed bean.

Entre estes problemas, o que gerou bastante frustração para muitos foi o uso do @ViewScoped juntamente com component bindings ou tag handlers (Issue #1492), tanto é que o problema acabou recebendo um apelido interessante, “chicken/egg issue“, ou em tradução livre, o problema do ovo e da galinha. Esse bug ficou mais conhecido durante os primeiros anos de vida do JSF2, mas hoje, após a sua correção, ele não é tão recorrente assim.

O problema do ovo e da galinha

O bug causa a instanciação dos managed beans em @ViewScoped a cada requisição, seja ela ajax ou não. A princípio isso não parece muito sério, mas vai de contra a natureza do escopo, que deveria ser instanciado uma única vez. Isso pode acarretar problemas como processamento desnecessário de métodos @PostConstruct, erros em conversores e validadores ou problemas mais difíceis de detectar, como a criação da árvore de componentes em um estado inválido.

O erro se manifesta ao “linkar” (fazer binding) um bean em @ViewScoped com tag handlers ou component bindings (os atributos id e binding dos componentes) e ter o Partial State Saving habilitado. Entenda tag handlers como qualquer tag que não seja um componente (UIComponent), elas são facilmente reconhecidas por não terem o atributo rendered, como por exemplo JSTL.

Um caso bem comum onde o problema se manifesta é quando você tenta utilizar a tag handler ui:include (não, ela não é um componente!) com @ViewScoped:

<ui:include src="#{menuViewScopedBean.menuName}.xhtml" />

Ou até em casos aparentemente inofensivos, como iterar uma coleção com JSTL, como abaixo:

<c:forEach var="car" items="#{carViewScopedBean.allCars}">
	<h:outputText value="#{car}" />
</c:forEach>

Não esqueça que component bindings (os atributos id e binding são avaliados em build time) também disparam o problema:

<h:form binding="#{viewScopedBean.uiForm}">
	…
</h:form>

Segue a lista das tag handlers que usamos ao trabalhar com JSF. Logo você deveria evita-las ao usar @ViewScoped:

Entendendo o que acontece

Tag handlers e component bindings são avaliados durante a fase de (re)construção da árvore de componentes (build time), enquanto os componentes do JSF são avaliados somente na fase de renderização (render time).

O problema é que beans @ViewScoped são armazenados na árvore de componentes, mas a árvore só estará disponível após a fase de construção, e a tag handler precisa do bean para construir a árvore (por isso o apelido do bug, “chicken/egg issue“).

Como tag handlers são executadas antes da árvore estar disponível e, existe um binding da tag com o bean, o JSF é obrigado a instanciar um novo bean (com as propriedades não inicializadas) para satisfazer a EL (binding) da tag handler, quando na verdade o que ele deveria ter feito era referenciar o bean que já estava armazenado na árvore. Basicamente você acaba com duas instâncias do managed bean por requisição. Onde o primeiro é usado na reconstrução da árvore de componentes e o segundo durante o ciclo de vida do JSF.

No fim de tudo, após a árvore ter sido reconstruída e o bean ter sido colocado de volta no escopo de visão (view scope), sobrescrevendo o bean que foi criado, o ciclo de vida continua normalmente, porém os danos causados pelo bug já aconteceram e não tem como voltar atrás.

Soluções

Existem algumas soluções para o problema, algumas simples e outras nem tanto. A notícia boa é que para quem já utiliza JSF 2.2 o problema já foi resolvido e não precisa se preocupar com ele!

Mas como bem sabemos, nem todos os projetos podem se dar ao luxo de se manterem atualizados a cada nova release, seja por qual for o motivo. Sendo, segue as principais soluções:

1. Atualize a versão do JSF

Como disse, o bug foi mais crítico nos primeiros anos do JSF 2.0 e 2.1, mas hoje em dia ele só atormenta quem não se manteve atualizado com as releases periódicas do Mojarra e/ou Myfaces.

O bug foi resolvido juntamente com a release do JSF 2.2, logo, quem já trabalha com a versão mais recente do faces não terá do que reclamar. Para quem ainda trabalha com JSF 2.1 existe a correção desde a versão 2.1.18 do Mojarra, qualquer versão antes desta é totalmente bugada e apresentará todos os problemas relatados.

Para quem não pode atualizar a versão do faces, as próximas soluções podem ajudar.

2. Desligue o Partial State Saving

Nas primeiras semanas em que o bug apareceu, a solução mais simples, porém a mais radical para aqueles que não poderiam abrir mão de suas tag handlers era desligar o Partial State Saving (PSS) globalmente, já que o problema estava intimamente ligado à ele. Para isso, basta configurar seu web.xml com:

<context-param>
	<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
	<param-value>false</param-value>
</context-param>

Desligar o PSS é abrir mão de uma das features mais importantes do JSF2, pois ela é responsável por manter a árvore de componentes parcialmente em memória, com isso ela diminui consideravelmente o consumo de banda de rede, memória e cpu do servidor. Ao desligar o PSS a aplicação volta a trabalhar como no JSF 1.2, ou seja, armazenando toda a árvore de componentes em memória. E isto pode ser um problema se sua aplicação mantém o estado da árvore no lado cliente.

3. Desligue o Partial State Saving por página

Uma solução melhor do que desligar o PSS globalmente, é desliga-lo somente para as páginas (views) problemáticas, as páginas que utilizam tag handlers ou component bindings. Dessa forma sua aplicação, certamente a maior parte dela, continua tirando proveito das otimizações do JSF 2. Para isso, basta configurar seu web.xml com:

<context-param>
	<param-name>javax.faces.FULL_STATE_SAVING_VIEW_IDS</param-name>
	<param-value>/home.xhtml,/pages/booking.xhtml</param-value>
</context-param>

O parâmetro aceita uma lista de views (páginas) separadas por vírgula na qual terão o PSS desligado.

4. Substitua tag handlers por componentes

Se por acaso você não quiser abrir mão do PSS então a única solução que lhe resta é substituir todas as tag handlers que tem binding com beans @ViewScoped por componentes normais do JSF.

Para isso, segue uma listas com alternativas para cada tag handler:

  • <c:choose>: use o atributo rendered no lugar dele
  • <c:forEach>: substitua pelo componente <ui:repeat>
  • <c:if>: use o atributo rendered no lugar dele
  • <c:set>: substitua por <ui:param>, <f:viewParam>, @ManagedProperty ou @PostConstruct
  • <f:actionListener>: use o atributo actionListener do componente em vez dele
  • <f:convertXxx> como <f:convertNumber>: use o atributo converter do componente ou use o <f:converter> em vez dele
  • <f:facet>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <f:validateXxx> como <f:validateLongRange>: use o atributo validator ou use o <f:validator> em vez dele
  • <f:valueChangeListener>: use o atributo valueChangeListener no lugar dele
  • <ui:decorate>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <ui:composition>: não existe alternativa para ele, simplesmente não use EL em qualquer atributo desta tag handler
  • <ui:include>: use componentes <ui:fragment rendered> para cada <ui:include> com páginas estáticas
  • qualquer tag file customizada: substitua por componentes do JSF (UIComponent)

Concluindo

Hoje em dia o problema não é dos mais críticos, mas ainda existem muitas aplicações que sofrem com ele. Se você tem um projeto com versões antigas do JSF 2, é bem provável que o problema aconteça em algumas de suas páginas, mas talvez não chegue a ser algo sério e você não tenha percebido.

Tão importante quanto resolver o problema é entendê-lo, o que leva muitas vezes ao aprendizado de novos conceitos e de como o JSF trabalha por debaixo dos panos. Conceitos e discussões sobre este problema e outros são tratados no curso de JSF 2 e Spring da TriadWorks.

E aí, você já conhecia o problema do ovo e da galinha? Usou alguma outra solução que não comentei?

10 razões para migrar sua aplicação para JSF2

Hoje foi publicado no blog da Caelum um post sobre as 10 razões para migrar sua aplicação para JSF2, e eu tive o prazer e a honra de colaborar com o post a convite de um grande amigo, o Raphael Lacerda.

O post enumera 10 razões que vão desde aspectos técnicos até comerciais, entre eles podemos citar  alguns como melhoria de performance, melhorias na especificação e fim do suporte à versão antiga da especificação e componentes.

Vale a pena conferir.