Neste artigo veremos como o JSF trata os estados dos componentes que são trafegados através de requisições GET e POST, como ele consegue manter o estado dos componentes.
Mas para chegarmos até este ponto mais avançado precisamos entender o que é a “Árvore de Componentes do JSF” e como ela funciona.
JSF View State
O JSF trabalha de maneira stateful, ou seja, são os próprios componentes do JSF que guardam seu estado e, por conta disto, é possível recuperar informações anteriores, como por exemplo, valores que o usuário digitou nas requisições anteriores.
Vamos primeiro montar um formulário simples em JSF e começar a analisar o conceito do estado da View usando este formulário. Use o código da Listagem 1.
Listagem 1. Formulário JSF
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head />
<h:body>
<h:form id="formularioTeste">
<h:outputLabel value="Nome" />
<h:inputText value="#{meuManagedBean.nome}" id="inputTextNome" />
<h:outputLabel value="Assunto" />
<h:inputText value="#{meuManagedBean.assunto}" id="inputTextAssunto" />
<h:outputLabel value="Texto" />
<h:inputTextarea value="#{meuManagedBean.texto}" id="inputTextAreaTexto" />
<h:commandButton actionListener="#{meuManagedBean.process()}" value="Processar" id="commandButtonProcessar" />
</h:form>
</h:body>
</html>
É importante usar o formulário para nosso teste, pois precisaremos de características específicas como o 'id' em cada componente que será enviado.
Nosso XHTML apresentado precisa conectar-se a um ManagedBean que receberá a requisição, então vejamos nosso ManagedBean na Listagem 2.
Listagem 2. ManagedBean do XHTML da Listagem 1
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean(name = "meuManagedBean")
@ViewScoped
public class MeuManagedBean{
private static final long serialVersionUID = 1L;
private String nome;
private String assunto;
private String texto;
public void process(){
System.out.println(nome);
System.out.println(assunto);
System.out.println(texto);
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getAssunto() {
return assunto;
}
public void setAssunto(String assunto) {
this.assunto = assunto;
}
public String getTexto() {
return texto;
}
public void setTexto(String texto) {
this.texto = texto;
}
}
Vamos agora começar a estudar as nossas Listagens 1 e 2 para entender como o JSF envia os componentes e consegue manter seus estados. Se você está utilizando um browser que possua uma ferramenta de depuração, como o firebug, então é hora de utilizá-la pois é de lá que extrairemos as informações necessárias para este artigo.
Em nosso caso estamos usando o Google Chrome, onde apertando a tecla F12 e indo até a aba “Network” você tem acesso a todas as requisições sendo trafegadas naquele momento. Clicando na página correspondente a sua requisição conseguimos ver o conteúdo do request e do response, que estudaremos nas seções a seguir.
Montado seu formulário e o servidor devidamente iniciado, preenchemos os dados e clicamos no botão 'Processar'. Neste momento é enviada um HTTP GET ao controlador do JSF com os seguintes dados presentes na Listagem 3.
Listagem 3. Dados sendo enviados
formularioTeste:
formularioTeste
javax.faces.ViewState:
-9148576975950225668:-2390461136904853257
formularioTeste:inputTextNome:
aoskd
formularioTeste:inputTextAssunto:
oaskd
formularioTeste:inputTextAreaTexto:
12093
formularioTeste:commandButtonProcessar:
Processar
Esse conteúdo pode ser encontrado no bloco “Form Data” da aba Headers, localizado em “Network” do navegador Google Chrome. Este conteúdo muda a cada requisição, mesmo que nenhum dado seja alterado.
Por enquanto não se preocupe com o “javax.faces.ViewState”, pois explicaremos mais à frente. O importante é perceber que todos os nossos componentes (com dados a serem enviados) estão inclusos na nossa requisição (Request).
Quando esta requisição chega ao nosso ManagedBean o JSF encarrega-se de montar a árvore de componentes, que são: UIForm, UIInput, UICommand e etc. Por exemplo, nosso “<h:form>” transforma-se em UIForm no “lado Java”, assim ele poderá manipular seus valores e atributos; assim como o “<h:inputText>” transforma-se em UIInput, e estes estão todos ligados através do UIViewRoot, como mostra a Figura 1.
Figura 1. UIViewRoot
Nosso UIViewRoot possui o UIForm (<h:form>) que, por sua vez, possui todos os outros componentes internos como inputs, botões, validadores e etc. Esta é a árvore de componentes construída pelo JSF. O UIViewRoot, como você já deve ter percebido, é a raiz de todos os outros componentes e este fica salvo no HttpSession do usuário.
Agora é o momento para você parar e questionar-se como o JSF consegue saber que na alteração do nome no ManagedBean o componente inputtext também deve alterar seu valor. A resposta para isso está na Figura 1, pois os bindings realizados entre o XHTML e o ManagedBean, juntamente com o HttpSession, garantem que estes valores possam ser associados. Cada componente guarda seu próprio valor.
Cada árvore de componentes, igual à da Figura 1, tem um ID associado e isso é necessário, pois cada vez que uma nossa requisição HTTP GET é realizada ao controlador do JSF uma nova árvore é criada com um novo ID, mas a antiga continua sendo armazenada. Este ID é armazenado naquele campo que apresentamos na Listagem 3: o javax.faces.ViewState:
javax.faces.ViewState:
-9148576975950225668:-2390461136904853257
Quando enviamos a primeira requisição HTTP GET ao Controlador do JSF, a nossa primeira árvore é criada e, como resposta, o JSF nos retorna um HTML contendo o ID dessa árvore, como mostra a Listagem 4.
Listagem 4. Retorno do JSF
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<form id="formularioTeste" name="formularioTeste" method="post" action="teste.xhtml" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="formularioTeste" value="formularioTeste" />
<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="8558332815273273256:1956746276483854780" autocomplete="off" />
<label>Nome</label><input id="formularioTeste:inputTextNome" type="text" name="formularioTeste:inputTextNome" value="asodka" /><label>Assunto</label><input id="formularioTeste:inputTextAssunto" type="text" name="formularioTeste:inputTextAssunto" value="aoskdoas" /><label>Texto</label><textarea id="formularioTeste:inputTextAreaTexto" name="formularioTeste:inputTextAreaTexto">1293819</textarea><input id="formularioTeste:commandButtonProcessar" type="submit" name="formularioTeste:commandButtonProcessar" value="Processar" />
</form></body>
</html>
O retorno (response) do JSF é o nosso formulário + o ID da nova árvore. Se clicarmos em processar novamente, mesmo que os dados não sejam alterados, o response retornará um outro ID, como mostra a Listagem 5.
Listagem 5. Novo retorno JSF
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>
<form id="formularioTeste" name="formularioTeste" method="post" action="teste.xhtml" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="formularioTeste" value="formularioTeste" />
<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="8558332815273273256:-308533199161773362" autocomplete="off" />
<label>Nome</label><input id="formularioTeste:inputTextNome" type="text" name="formularioTeste:inputTextNome" value="asodka" /><label>Assunto</label><input id="formularioTeste:inputTextAssunto" type="text" name="formularioTeste:inputTextAssunto" value="aoskdoas" /><label>Texto</label><textarea id="formularioTeste:inputTextAreaTexto" name="formularioTeste:inputTextAreaTexto">1293819</textarea><input id="formularioTeste:commandButtonProcessar" type="submit" name="formularioTeste:commandButtonProcessar" value="Processar" />
</form></body>
</html>
Poderíamos fazer isso 10 mil vezes e seriam criadas 10 mil árvores. Você já conseguiu visualizar uma falha neste algoritmo? Se um usuário enviar 10 mil vezes a mesma página na mesma sessão, então possivelmente o servidor terá um estouro de memória ou “Java Heap Space”, já que a sessão fica armazenada no servidor e consequentemente todas as árvores ficarão lá também.
Para evitar que isso ocorra, a implementação Mojarra do JSF definiu um padrão máximo de 15 árvores por sessão: isso significa que se o usuário solicitar a 16 requisição (16 árvores), o JSF irá remover a árvore menos utilizada para comportar esta nova árvore, ou seja, nenhum usuário nunca passará das 15 árvores por sessão.
Mas o problema de memória ainda continua quando trabalhamos com uma grande quantidade de usuários, pois se você tiver um sistema com muitos acessos, suponhamos três mil diários, mesmo com a limitação de 15 árvores, você poderá ainda ter 45000 árvores em memória (3000x15), o que é um tanto quanto preocupante de acordo com o hardware do seu servidor. Uma solução é diminuir a quantidade de árvores permitidas por sessão no seu arquivo web.xml: poderíamos colocar cinco por sessão, como mostra o código da Listagem 6.
Listagem 6. Limitando a quantidade de árvores por sessão - Mojarra
<context-param>
<param-name>com.sun.faces.numberOfViewsInSession</param-name>
<param-value>5</param-value>
</context-param>
A configuração mostrada funciona para a implementação Mojarra, mas se você usa MyFaces então o correto seria o código da Listagem 7.
Listagem 7. Limitando a quantidade de árvores por sessão – MyFaces>
<context-param>
<param-name>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</param-name>
<param-value>5</param-value>
<description>Funciona somente de o state for "server"
</description>
</context-param>
Com a limitação apresentada reduzimos de 45000 para 3000x5 = 15000. Uma redução considerável, mas ainda podemos melhorar. Você pode perguntar-se: porque não colocamos uma árvore e ficamos com um “super desempenho”?
Para responder a esta pergunta vamos abrir uma situação hipotética: Você configurou o limite de árvore de componentes por sessão igual a 1. Um usuário qualquer acessar o sistema e abre a tela de consulta de clientes (uma árvore instanciada - vá fazendo os cálculos), mas este usuário ao mesmo tempo quer ver as categorias de clientes que estão cadastradas, então ele abre outra aba e consulta a categoria de clientes (mais uma árvore instanciada). Neste ponto, a árvore que armazenava a consulta de clientes é removida da sessão para comportar a árvore de consulta de categoria de clientes. O usuário resolver voltar a tela de consulta de clientes e clica no botão 'Editar' de um cliente específico e um erro é retornado: ViewExpiredException. O que é isso? Nossa consulta de clientes tinha uma árvore que foi removida da sessão para comportar uma nova árvore, sendo assim, essa “tela” tem uma “View inválida”, e teremos que recarregá-la.
Percebeu o problema de armazenar poucas árvores na sessão? Impossibilitamos que o usuário possa navegar por diversas abas do sistema sem problemas. Então agora estamos literalmente entre “a cruz e a espada”, pois não podemos diminuir muito a quantidade de árvores permitidas por sessão para evitar o ViewExpiredException, e também não podemos aumentar muito para evitar um Java Heap Space no servidor. O que fazer?
Existe uma outra solução: O nosso problema ocorre por conta de necessitarmos armazenar uma quantidade grande de árvores na sessão que consequentemente está no servidor. O JSF nos dá uma outra alternativa de não salvar a árvore na sessão, assim poupamos a memória do servidor. A árvore, neste caso, é serializada no cliente e é enviada ao servidor de forma serializada, onde este, por sua vez, irá recriar toda tela bom base na informação que está serializada.
Vamos habilitar essa técnica no nosso arquivo web.xml usando o código da Listagem 8.
Listagem 8. Habilitando serialização de árvore no cliente – web.xml
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
O comportamento padrão é “server” que estudamos nas seções anteriores, onde a árvore de componentes é salva na sessão, mas mudando para “client” isso é desabilitado e a árvore é serializada no cliente. Em cada requisição agora não é mais enviado o ID da árvore, e sim um componente inputHidden contendo o valor serializado, como mostra a Listagem 9.
Listagem 9. Retorno da árvore serializada
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>
<form id="formularioTeste" name="formularioTeste" method="post" action="teste.xhtml" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="formularioTeste" value="formularioTeste" />
<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="L9ppXq2IyVDWQ+tWBoZldutisSUxT2jEkbsfSUy+iF1EFhZXtPPYd6CUSY/mSML6dJDVvdTiejm47I43OkfMS0HDWTqxeXO0XfFwt7cSNNMeydT0nQT+p0MpLvKYm7Ewthi5CHv3BbPbqXUwOgogasL8kZZwNYEZACVtMidnYEGjJTWm57ccVklp7JNqpZ9tK+/zTJMrNcDzzGUg5cDh9pNCtQHnM6TmSP8yHrYIYNtbJVVt4iWZWc8mUCJgNrY7hciZWFyEYPfyWOKVAtzxD32wkMj2iufMj1I7+7Ev+C3VVTOnU0tfR/9deAvzfwzRAkUMZ+dFN0PAnwoXsfEmJwWOmewbgGNrGwiU/Sn3/1LqkxUaudolS5A2D+PYNwocTUhgIc9VY3tNwyMu7V6TXnao0vhWw1M5HNPgE0Dr2ORy5M5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJOcK/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn27kuFEM9eQC/Ll7LCoCNnaB/ih3iF6PrKGi8KHuvxSRrb9njVjmFGEe7B7MtTP+m72gE0Dr2ORy5M5L3NcAv+C3VV
CrUnjcGjw3fLnnD4qyy58Gw12XWJwN2mq9/fPRSRSTkIQ00Dvo+GJF+CuTJPU0UU+q0qcnXxBRxEdK+2o78Z9tQx+/c/FKF75GsdHgqAtiWGRSjBUySLd2Y8A9JgNnOKzeDcpyy1xCCgGDEUt5lNzOiqoRcpHK5t8FTOgz24FYmiv4BdvwWZ5dcI70S/yND4hS+I/LW/3Pu7QNjc3wu7VwwMo1OGXD/BMio8w4LK324i0dSoMhhXEN9Ok/64YAJ2cwXvbrioaRxkGRgHQwXFpMu6kxhjXHadsYxlpnAGddj8p4u9D7qRemwRgO4Wdl4zAkQKVqxkeNsNA3yWoLB
vhWw1M5HNPgE0Dr2ORy5MRSRS5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJOcK/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn27kuFEM9eQC/Ll7ORyORy
vhWw1M5HNPgE0Dr2ORy5M5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJOcK/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn27kuFEM9eQC/Ll7hIyWdkQt9CE+jwqayVv4F3DtLTUlxKng0wxGfTmcHbSYPbAIblBD0lTlr1XuUrSKh4bcR80n4zqJkf54UTRknrIpUCptHI2GyDy3gUPYVYFPN9I9GqzOR4Q==" autocomplete="off" />
<label>Nome</label><input id="formularioTeste:inputTextNome" type="text" name="formularioTeste:inputTextNome" value="asodk" /><label>Assunto</label><input id="formularioTeste:inputTextAssunto" type="text" name="formularioTeste:inputTextAssunto" value="saokd" /><label>Texto</label><textarea id="formularioTeste:inputTextAreaTexto" name="formularioTeste:inputTextAreaTexto">12039</textarea><input id="formularioTeste:commandButtonProcessar" type="submit" name="formularioTeste:commandButtonProcessar" value="Processar" />
</form></body>
</html>
Depois de mudar a configuração no arquivo web.xml, reiniciar o servidor e reenviar o formulário através do botão 'processar', agora a resposta (response) do servidor não vem mais com o ID da árvore e sim com um inputHidden contendo a serialização da árvore. Lembrando que o valor serializado corresponde aos componentes e não ao estado da aplicação (ManagedBeans).
Você pode pensar que esta é a solução 100% ideal, mas infelizmente você está enganado. A solução proposta acima, serialização da árvore, é ótima por poupar a memória do servidor evitando o problema de Heap Space, mas lembra que citamos em determinado trecho que o servidor precisa “recriar a tela toda vez que uma nova requisição é solicitada” e isso consome processamento da CPU. Obviamente que não teremos um estouro de processamento ou algo parecido, mas dada uma grande quantidade de requisições o servidor poderá ficar cada vez mais lento. Solução ideal? Depende do seu projeto e de vários fatores, tais como: hardware do seu servidor, quantidade de acessos aproximados e etc.
Como dissemos ao longo deste artigo, não há solução ideal e isso depende de cada projeto e suas características. A documentação da Mojarra indica que a melhor solução geralmente é usar a configuração do web.xml como client-state poupando a memória e consumindo a CPU, e de fato, na maioria das vezes, isto funciona muito bem, mas existem casos específicos, por isso não podemos generalizar.
Existem ainda soluções “mixadas” que oferecem a serialização da árvore na sessão. Você mesmo pode fazer teste de desempenho e provar qual seria a melhor alternativa para usar em seu projeto: com o comando “top”, no Sistema Operacional Linux, você pode acompanhar o uso de memória e CPU da sua máquina, testando o server-state e o client-state em diferentes situações.