Posts Tagged ‘web’

Repitam comigo: Redirect não é forward

Saturday, July 12th, 2008

É engraçado o número de desenvolvedores que se utilizam da tag de navegação <redirect/> quando configuram suas regras de navegação no faces-config.xml sem entender o porquê de sua finalidade, na maioria das vezes a única coisa que eles tem conhecimento -e acreditam que esta é finalidade da tag- é que ao utiliza-la a url na barra de endereços do browser muda.

Depois disso eles não entendem porque os valores não existem mais no managed bean ou porque as mensagens de erro não são mais exibidas ao usuário ou mesmo porque uma nova instância do managed bean foi criada, enfim, eles não entendem porque afinal a aplicação parou de funcionar!

Tentarei com este post esclarer a diferença entre um redirect e um forward e como contornar o problema acima para que novos desenvolvedores não caiam em “maus lençóis”.

Redirect não é forward

Um servlet (controller) pode executar tanto uma operação de um redirect como de um forward no final do processamento de uma requisição, e isso influencia como as páginas no browser serão recarregadas. É importante que um desenvolvedor entenda os fundamentos por trás de uma aplicação web, e um deles é a diferença entre redirect e um forward.

Segue abaixo a diferença entre eles:

Forward

  • É executado internamente pelo servlet (controller);
  • O browser não sabe o que está ocorrendo durante o processamento no servidor, ou seja, não sabe por quais servlets ou páginas a requisição está passando;
  • No final do processamento da requisição a url da barra de endereços do browser não muda;
  • O reload da página resultante irá executar a requisição original;

Redirect

  • É um processo de dois passos, ao receber uma requisição a aplicação web “pede” ao browser para acessar uma segunda url, por isso a url muda;
  • O reload de página não repetirá a requisição original, mas sim a nova url (2ª requisição);
  • É um processo muito mais lento que um forward, pois são necessárias duas requisições, e não uma;
  • Objetos colocados no escopo do request original são perdidos durante o segundo request;

Resumindo, um redirect é uma nova requisição que o cliente (browser) faz a pedido da aplicação web, logo ele fica ciente sobre como está ocorrendo a navegação e para onde ele está sendo redirecionado, enquanto um forward pode executar várias requisições no lado servidor sem o conhecimento do cliente e no final retornar uma página qualquer. Atentem também que um forward mantém os atributos e parâmetros do request original, já um redirect não.

Como podem ver, de maneira sútil eles fazem a mesma coisa, mas são bem diferentes.

O problema

Vamos observar um caso clássico abaixo:

Imagine que temos um managed bean configurado em escopo de request e que temos uma regra de navegação no nosso faces-config.xml na qual explicitamos o uso de redirect através da tag <redirect/>:

<navigation-rule>
	<from-view-id>/pages/pageX.jsp</from-view-id>
	<navigation-case>
		<from-outcome>nova_pagina_com_redirect</from-outcome>
		<to-view-id>/pages/pageY.jsp</to-view-id>
		<redirect />
	</navigation-case>
</navigation-rule>

E em uma determinada página X o usuário clica em um botão que executa um método no managed bean e depois disso o usuário é enviado para uma outra página Y com uma mensagem de sucesso (ou erro) e alguns objetos populados no managed bean para serem exibidos, logo teríamos um método no managed bean semelhante a isso:

public String submit() {
	// processa algo
	BacalhauService.processaRequisicao();

	// popula atributo do managed bean
	this.att1 = "A ligeira raposa marrom saltou sobre o cão preguiçoso.";

	// adiciona mensagem de sucesso
	FacesContext ctx = FacesContext.getCurrentInstance();
	ctx.addMessage(null, new FacesMessage("Operação concluída com sucesso."));

	return "nova_pagina_com_redirect";
}

Imagine que quando o usuário executou a ação o processamento no método do managed bean ocorreu, a navageção para a página Y funcionou perfeitamente e a url na barra de endereços do browser mudou como previsto, mas espere um pouco! “What the hell is this?” A mensagem de sucesso e os dados contidos no managed bean não foram exibidos, onde elas foram parar afinal de contas?

No caso acima nós navegamos da página X para a página Y através de um redirect (lembram da tag <redirect/> na regra de navegação?), e já sabemos que quando há um redirect todos os dados no escopo de request são perdidos, logo o nosso managed bean e nossa mensagem de sucesso foram perdidos.

Agora que já entendemos o funcionamento do redirect não foi uma surpresa o managed bean ter sido perdido (reinstanciado) pois sabiamos que ele estava configurado com escopo de request, mas e a mensagem de sucesso? Bem, isso fica óbvio ao saber que as mensagens do JSF são adicionadas no escopo de request, mesmo que seu managed bean esteja noutro escopo.

A solução

Como resolver isso? Bem, existem algumas soluções, porém a mais simples sem dúvida é remover a tag <redirect/> da regra de navegação, assim a navegação entre páginas ocorrerá através de forward (que é o default do framework), e os dados do seu managed bean e mensagens serão exibidos ao usuário como esperado.

Na maioria das aplicações web um dos motivos para se utilizar um redirect é para evitar que no reload/refresh da página ocorra uma resubmissão do form, evitando-se assim resultados inesperados na aplicação, sendo, uma solução para isso seria a utilização do Post-Redirect-Get Pattern (PRG). Uma excelente solução é a implementação do padrão PRG através deste Phase Listener implementado pelo BalusC.

Não tenho certeza, mas acho (quase certeza) que o JBoss Seam já tem algo implemetado para solucionar o problema. Se alguém puder confirmar, eu ficaria grato :)

Felizmente hoje em dia as aplicações web estão caminhando para uma GUI mais rica e versátil, e ao abrir mão da “velha escola” conseguimos contornar os problemas citados acima facilmente, como também obter inúmeras outras vantagens.

Concluindo

Como podem ver, o problema não era do JavaServer Faces (JSF) ou mesmo de qualquer outro framework MVC, mas certamente da falta de fundamentos sobre a web. Espero sinceramente que os desenvolvedores busquem entender os fundamentos dos frameworks, padrões e da web antes de desenvolver uma aplicação web, pois utilizar algo nas cegas provavelmente trará vários problemas mais cedo ou mais tarde.

Enfim, este é um problema decorrente na lista de discussão do javasf, e sempre acabamos por explicar a diferença entre as duas operações, que no final resolve-se apenas seguindo o caminho mais simples. Fico aqui e espero que este post tenha servido de ajuda a muitos desenvolvedores.

JSF e Naming Container

Tuesday, July 1st, 2008

Todo desenvolvedor que se utiliza de JavaServer Faces deveria saber como funciona o mecanismo de geração de identificação (ID) dos componentes, pois uma hora querendo ou não ele precisará deste conhecimento, principalmente se houver a necessidade de se escrever algum código JavaScript.

Enfim, vamos ao que interessa..

Entendendo o mecanismo

Uma das caracteristicas do JSF é que todos os seus componentes possuem um atribudo “id” e este atributo deve conter um valor único, caso não seja definido um ID pelo desenvolvedor então será gerado automaticamente um ID único para cada componente durante a construção da árvore de componentes.

Sendo, observem o seguinte trecho de código JSF:

<h:form>
	<h:inputText id="nome"/>
</h:form>

No final será gerado para o cliente (browser) o seguinte código -ou algo parecido- (X)HTML:

<form id="j_id_jsp_1761815803_1" name="j_id_jsp_1761815803_1" method="post">
	<input id="j_id_jsp_1761815803_1:nome" type="text" name="j_id_jsp_1761815803_1:nome" />
</form>

Reparem que o atributo “id” do componente h:form não foi definido, porém o framework gerou um ID único para o componente, no caso o ID foi “j_id_jsp_1761815803_1“, que foi renderizado juntamente com a tag <form/> do (X)HTML.

Atentem também que o ID (gerado) do componente h:form foi concatenado ao ID (que nós definimos) do componente h:inputText separados por ‘:’ (dois pontos) na tag <input/>, logo o ID da nossa tag <input/> é “j_id_jsp_1761815803_1:nome“.

Infelizmente os IDs gerados pelo framework não são nada legíveis para nós desenvolvedores -e provavelmente não deveriam-, então para consertar isso basta definirmos o ID de cada componente na nossa página, logo nosso código JSF ficará assim:

<h:form id="form">
    <h:inputText id="nome"/>
</h:form>

E consequentemente o código (X)HTML gerado será algo semelhante a isto:

<form id="form" name="form" method="post">
	<input id="form:nome" type="text" name="form:nome" />
</form>

Como podem ver, os IDs na página gerada ficam bem mais legíveis, facilitando assim o trabalho com JavaScript no lado cliente.

Naming container

E onde afinal de contas entra essa história de Naming Container?

Se vocês observaram o componente h:inputText teve seu ID concatenado ao ID do formulário no cliente, ou seja, na tag <input/> (X)HTML, e isso aconteceria com todos os componentes dentro do componente h:form. Logo, o componente h:form é um componente e ao mesmo tempo um naming container.

Basicamente, Naming Container é uma caracteristica que alguns componentes possuem, caracteristica essa que o framework utiliza para garantir que dentro de um naming container (componente) seus componentes filhos possuam IDs  únicos, evitando assim a duplicidade de componentes com o mesmo ID na página (viewroot).

Outra caracteristica importante que você deve saber sobre naming containers é que eles podem ser aninhados, logo os componentes mais internos terão seus IDs no cliente concatenados com os IDs dos naming containers mais externos também.

Logo em um caso como:

<h:form id="form">
    <f:subview id="subview">
        <h:inputText id="nome"/>
    </f:subview>
</h:form>

Teríamos o código (X)HTML abaixo gerado:

<form id="form" name="form" method="post">
	<input id="form:subview:nome" type="text" name="form:subview:nome" />
</form>

O componente f:subview também é um naming container, porém ele não gera qualquer output no cliente, ele é utilizado basicamente para servir como naming container, principalmente quando se faz includes de páginas dinâmicas.

Simples até demais, não?

Componentes padrões como h:form, f:subview e h:dataTable são naming containers. A maioria dos conjuntos de componentes possuem alguns naming containers, logo isso depende do conjunto de componentes.

Evitando naming container no formulário

As vezes temos páginas simples em que o único naming container é o próprio componente h:form, e nestas páginas precisamos manipular alguns dados no formulário através de JavaScript, porém é algo realmente chato ficar concatenando IDs para obter o ID de um input do formulário.

Para resolver isso o componente h:form (no JSF1.2) possui o atributo “prependId” que define se o componente se comportará no cliente como naming container ou não. Para evitar que o h:form se comporte como naming container basta setarmos o atributo “prependId” para false. Assim quando o código (X)HTML for gerado para o cliente as tags internas não terão seus IDs modificados.

<h:form id="form" prependId="false">
    <h:inputText id="nome"/>
</h:form>

Sem complicações, certo?

Essa caracteristica funciona somente para os componentes filhos do h:form, caso haja um naming container dentro do h:form então os filhos deste name container terão seus IDs alterados normalmente no cliente.

Concluindo

O post ficou até longo para um assunto básico, mas acredito que consegui abordar os fundamentos sobre naming container para quem pretende apenas utilizar os componentes na construção de páginas. Para quem realmente pretende criar (ou estender) seus próprios componentes seria interessante conhecer o conceito mais a fundo.

Enfim, o conceito é básico, porém fundamental para desenvolvedores que usam JavaServer Faces. Contudo é incrível como a maioria dos desenvolvedores não se interessam pelos fundamentos do framework, fundamentos estes que evitariam diversas dúvidas e problemas durante o desenvolvimento.