JSF - Lidando com o estado da view
JSF é um framework web MVC que foi criado para desenvolver aplicações web de maneira stateful (além de vários outros motivos). São os componentes do JSF que guardam este estado e por causa deles JSF consegue se lembrar, por exemplo, qual converter ou validator é para usar, ou qual era o valor que o usuário digitou na requisição anterior.
O simples formulário abaixo declara um [h:inputText](http://www.horstmann.com/corejsf/jsf-tags.html#Table4_7 "Documentação da tag")
e um [h:commandButton](http://www.horstmann.com/corejsf/jsf-tags.html#Table4_15 "Documentação da tag")
. Quando o JSF recebe a requisição inicial (HTTP GET
) serão instanciados esses componentes que são do tipo [UIForm](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UIForm.html "Javadoc da classe")
, [UIInput](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UIInput.html "Javadoc da classe")
(que é associado com um validator [DoubleRangeValidator](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/validator/DoubleRangeValidator.html "Javadoc da classe")
) e um UICommand
. Cada componente estende a classe [UIComponent](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UIComponent.html "Javadoc da classe")
e pode ter filhos, assim usando o famoso design pattern _Composite_, veja o exemplo:
<h:form> <h:inputText value="#{formularioBean.valor}" id="valor"> <f:validateDoubleRange minimum="0" /> </h:inputText> <h:commandButton value="Enviar" action="#{formularioBean.mostra}"/> </h:form>
O resultado é a árvore de componentes (também é chamado view
ou [UIViewRoot](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UIViewRoot.html "Javadoc da classe")
). Ela fica salva na HttpSession
do usuário. Por isso, JSF sabe se lembrar qual valor (setter) deve ser usado para preencher o nosso modelo (é o componente que guarda isso!) e por isso também não é preciso definir na URI qual método deve ser chamado no bean, que é tão comum nos frameworks action-based. No formulário acima essa informação está encapsulado no [UICommand](http://download.oracle.com/docs/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UICommand.html "Javadoc da classe")
(h:commandButton
).
Cada vez que o controlador JSF recebe um novo HTTP GET
para uma página (request inicial) será criado uma árvore nova. Para distinguir entre as árvores diferentes, JSF renderiza no HTML sempre um campo a mais que representa a identificação da árvore dentro da sessão HTTP:
<input id="javax.faces.ViewState" value="-5331242430046946924:4161480279607048884" type="hidden" />
javax.faces.ViewState
guarda a identificação da árvore no lado do cliente e para cada GET
se repete este procedimento (nova árvore, nova identificação). Ou seja, todas as página vão ter uma ou mais árvores de componentes na sessão (mesmo para uma aplicação com poucas páginas, o usuário poderia usar várias abas da mesma página). Para testar isso basta chamar uma página (GET
) e verificar o número do campo javax.faces.ViewState
(a identificação deve mudar):
1. HTTP GET -> ViewState ID: 3695116712045768933 2. HTTP GET -> ViewState ID:-7330234494192939826 3. HTTP GET -> ViewState ID: 6490700310803488872 (3 requisições para a mesma página geram 3 árvores diferentes na sessão do usuário)
Isso significa que um usuário mal intencionado poderia enviar milhares de GET para uma página JSF para causar a criação de árvores na sessão até a memória HEAP acabar. Para evitar esse tipo de problema, o controlador JSF limita a quantidade de árvores por usuário. Usando a implementação referencial Mojarra o padrão é de 15 árvores no máximo por sessão. Caso o usuário abra, por exemplo, mais do que 15 abas no navegador, o controlador JSF automaticamente vai tirar a árvore menos usada (LRU) da sessão. Para testar isso podemos usar um filtro que imprime o conteúdo da sessão, como mostra a imagem ao lado. Se o usuário usa uma aba com uma ID que o controlador já tirou recebemos a famosa [ViewExpiredException](http://download.oracle.com/javaee/5/api/javax/faces/application/ViewExpiredException.html "Javadoc da exception")
:
Através de uma configuração no web.xml
, específica para cada implementação JSF, podemos mudar o padrão. Segue a definição para Mojarra:
<context-param> <param-name>com.sun.faces.numberOfViewsInSession</param-name> <param-value>5</param-value> </context-param>
O parâmetro é específico para a implementação Mojarra e só funciona na versão 1.2. Mojarra 2.x não respeita o parâmetro apesar da documentação indicar que ela sirva para ambas versões. A implementação MyFaces também possui uma configuração parecida.
Conseguimos então limitar a quantidade de árvores na sessão. Dependendo da quantidade de usuários e complexidade das telas comparado com o tamanho disponível de memória no servidor podemos mesmo assim enfrentar problemas. Por isso a especificação JSF oferece um recurso para não gravar a árvore de componentes na sessão. Mas onde fica o estado então? Toda árvore do componentes é serializada e é devolvida através de um inputHidden
na resposta da requisição. Esse não é o comportamento padrão e deve ser configurado no web.xml
, mas faz parte da especificação JSF:
<context-param> <param-name>javax.faces.STATE\_SAVING\_METHOD</param-name> <param-value>client</param-value> <!-- server é o padrão --> </context-param>
Nesse caso não fica mais a identifição da árvore no HTML gerado, e sim a árvore serializada:
Em cada requisição trafega a árvore inteira entre navegador e servidor e no início de cada requisição o servidor recria essa tela. Isso aumenta a banda e uso da CPU no servidor, mas diminui o uso da memória. É importante mencionar que apenas o ViewState
será serializado (os componentes) e não o estado da aplicação (ManagedBean
s). Caso colocarmos algum ManagedBean
na sessão, o bean continua não sessão, não fazendo parte da serialização. O filtro mostra esse comportamento e continua imprimindo o [FormularioBean](https://gist.github.com/1286181 "Implementação do FormularioBean")
:
A documentação da Mojarra indica que normalmente o client-state
é a melhor solução, mas isso é um tradeoff entre desempenho e uso de memoria e deve ser avaliado para cada projeto. Mojarra até oferece soluções misturadas, podemos por exemplo serializar o view-state
, mas deixar a árvore serializada na sessão.