JavaServer Faces: Árvore de Componentes do JSF
Neste artigo veremos como o JSF trata os estados dos componentes que são trafegados através de requisições GET e POST e como ele consegue manter o estado dos componentes.
Para chegarmos até este ponto mais avançado e complexo 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, como mostra a Listagem 1.
<!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 da Listagem 1 para nosso teste, pois precisaremos de características específicas como o 'id' em cada componente que será enviado.
Nosso XHTML acima precisa conectar-se a um ManagedBean que receberá a requisição. Vejamos na Listagem 2 o nosso ManagedBean.
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 você consegue ver o conteúdo do request e do response, que estudaremos nas seções abaixo.
Montado seu formulário e o servidor devidamente iniciado, preenchemos os dados e clicamos no botão 'Processar', onde neste momento é enviada um HTTP GET ao controlador do JSF. Veja na Listagem 3 os dados que estamos enviando.
formularioTeste:
formularioTeste
javax.faces.ViewState:
-9148576975950225668:-2390461136904853257
formularioTeste:inputTextNome:
aoskd
formularioTeste:inputTextAssunto:
oaskd
formularioTeste:inputTextAreaTexto:
12093
formularioTeste:commandButtonProcessar:
Processar
O conteúdo acima 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 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 “” transforma-se em UIForm no lado java, assim ele poderá manipular seus valores e atributos. O “” transforma-se em UIInput e estes estão todos ligados através do UIViewRoot. Observe a Figura 1.
Nosso UIViewRoot possui o UIForm() 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 é 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 como a 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 (Listagem 4) é 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 5.
<!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 6.
<!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: 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, assim poderíamos colocar cinco por sessão, como mostra a Listagem 7.
<context-param>
<param-name>com.sun.faces.numberOfViewsInSession</param-name>
<param-value>5</param-value>
</context-param>
A configuração acima funciona para a implementação Mojarra, mas se você usa então o correto seria o código da Listagem 8.
<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 acima 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 um. Um usuário qualquer acessar o sistema e abre a tela de consulta de clientes (uma árvore instanciada), mas este usuário ao mesmo tempo quer ver as categorias de clientes que estão cadastradas abre também 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, temos o total de uma árvore na sessão que corresponde a tela 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, então um erro é retornado: ViewExpiredException. Mas, 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. Então 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 com o código da Listagem 9.
<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. Assim, teremos como retorno o mesmo da Listagem 10.
<!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/mSML6dJDV
vdTiejm47I43OkfMS0HDWTqxeXO0XfFwt7cSNNMeydT0nQT+p0MpLvKYm7Ewthi5CHv3BbPbq
XUwOgogasL8kZZwNYEZACVtMidnYEGjJTWm57ccVklp7JNqpZ9tK+/zTJMrNcDzz
GUg5cDh9pNCtQHnM6TmSP8yHrYIYNtbJVVt4iWZWc8mUCJgNrY7hciZWFyEYPfyWOKVAtzxD32
wkMj2iufMj1I7+7Ev+C3VVTOnU0tfR/9deAvzfwzRAkUMZ+dFN0PAnwoXsfEmJw
WOmewbgGNrGwiU/Sn3/1LqkxUaudolS5A2D+PYNwocTUhgIc9VY3tNwyMu7V6TXnao0vhWw1M5
HNPgE0Dr2ORy5M5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJOcK
/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn27kuFEM9eQC
/Ll7LCoCNnaB/ih3iF6PrKGi8KHuvxSRrb9njVjmFGEe7B7MtTP+m72gE0Dr2ORy5M5L3NcAv+C3VV
CrUnjcGjw3fLnnD4qyy58Gw12XWJwN2mq9/fPRSRSTkIQ00Dvo+GJF+CuTJPU0UU+q0qcn
XxBRxEdK+2o78Z9tQx+/c/FKF75GsdHgqAtiWGRSjBUySLd2Y8A9JgNnOKzeDcpyy1xCCgG
DEUt5lNzOiqoRcpHK5t8FTOgz24FYmiv4BdvwWZ5dcI70S/yND4hS+I/LW/3Pu7QNjc3wu
7VwwMo1OGXD/BMio8w4LK324i0dSoMhhXEN9Ok/64YAJ2cwXvbrioaRxkGRgHQwXFpMu6kxhj
XHadsYxlpnAGddj8p4u9D7qRemwRgO4Wdl4zAkQKVqxkeNsNA3yWoLB
vhWw1M5HNPgE0Dr2ORy5MRSRS5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJO
cK/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn
27kuFEM9eQC/Ll7ORyORy
vhWw1M5HNPgE0Dr2ORy5M5L3NcAv+C3VVTOnU0tErX2K018AqZSDVdHDVmgKLhXgoJOcK
/7DrSlk48KwZGWeaRSgfHvmE2+lKDxo2ZnKbPNVCbN4e9PXdpMRvqYRLtqbHtn27kuF
EM9eQC/Ll7hIyWdkQt9CE+jwqayVv4F3DtLTUlxKng0wxGfTmcHbSYPbAIblBD0lT
lr1XuUrSKh4bcR80n4zqJkf54UTRknrIpUCptHI2GyDy3gUPYVYFPN9I9GqzOR4Q==" 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 dela. 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, pois apenas temos prós, 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.
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.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo