Java: Servlets e JSP (Webflow)

20/05/2019

Neste post pretendo apresentar os primeiros modelos utilizados no controle de fluxo para aplicações web. Em seguida quero citar exemplos de implementação destes modelos. Por fim, apresentar os frameworks recentes que utilizam, direta ou indertamente, estes modelos de controle de fluxo no cerne de suas arquiteturas.


Enterprise Application & Web Application

Antes de começar a escrever sobre as Servlets e JSPs, é importante apresentar dois elementos fundamentais que servem como infra estrutura para soluções web e/ou corporativas. Tratam-se dos servidores de aplicações e dos servidores web. Os servidores web são a infra estrutura base para nossas aplicações web.

Já os servidores de aplicações, são elementos mais robustos, que, além de possuírem infra estrutura para receber aplicações web, possuem também infra estrutura para receber enterprise application. Ou seja, implementam toda a especificação J2EE, gerenciando transações, disponibiliza o controles de autenticação e autorização, também é um servidor web, gerencia também mensagens, conectores etc.

Web Container

O elemento central de um servidor web é o web container. Este componente é responsável por gerenciar as aplicações publicadas no servidor web, bem como garantir permissões de acesso.

O ciclo de vida das aplicações web também é controlado por este elemento.

O container é responsável por instanciar, iniciar e parar as aplicações; é o responsável por mapear e direcionar requisições/URLs para as respectivas aplicações; é o responsável por gerenciar e direcionar respostas aos pedidos recebidos; também é o responsável por gerenciar o estados dos objetos utilizados pelas aplicações.

Em outras palavras, o web container é uma espécie de camada entre os elementos que fazem pedidos e os elementos que processam os pedidos para produzirem respostas. Uma camada que possibilita a execução de regras durante o fluxo de comunicação. Estas regras são uma espécie de especificações as ser seguidas pelo container ao carregar e disponibilizar as aplicações em seu "interior". As regras podem ser definidas em ficheiros XML ou através de anotações.

Os elementos principais a serem "publicados" dentro do web container são as Servlets e as JSPs. Digo principais, pois são estes elementos que possuem lógicas de programação e processamento, particular de cada aplicação.

No ambiente Java web, as aplicações devem ser publicadas seguindo alguns padrões para que o container consiga carrega-las e disponibiliza-las. As aplicações devem estar "compactadas" em ficheiros segundo as definições das estruturas ".ear" (quando publicadas em Servidores de Aplicação) ou ".war" (quando publicadas em Servidores Web ou de Aplicações).

Estas estruturas devem ter em seu interior, além dos elementos de programação (como classes e bibliotecas java), elementos de configuração "web.xml", "application.xml" entre outros recursos adicionais como imagens, ficheiros de estilo, ficheiros de scripts etc.

As estruturas ".ear" são conhecidas como Enterprise Applications. Basicamente uma enterprise application consiste em um conjunto de sub aplicações que compõem uma aplicação maior. Ou seja, em sua estrutura existem diversas ".war".  Dentro desta estrutura também encontram-se os EJBs (em ficheiros ".jar") e bibliotecas adicionais.

Uma estrutura ".war" corresponde a uma web apllication, ou seja, uma aplicação completa a ser dispinibilizado no contexto web. Como dito anteriormente, esta também pode fazer parte de uma solução maior.

Servlets

Como já disse, as Servlets são os elementos centrais das aplicações web. Quando iniciado, o container irá instanciar estes elementos, cada Servlet será iniciada e o método "init" será executado, permitindo que os desenvolvedores escrevam lógicas de programação (inicialização de variáveis, por exemplo).

Quando o servidor é desligado, as Servlets são "notificadas" e o método "destroy" será executado, permitindo mais uma vez que códigos sejam processados neste momento.

No momento de carregar (instanciar e executar) uma Servlet, o container irá em busca do ficheiro de configuração para saber como deverá proceder. Este ficheiro deve seguir uma estrutura de difinição padrão em XML. Neste definimos o elemento Servlet, o mapeamento URL para este elemento Servlet, regras de acesso entre outras coisas.

Como consequência disto, o container saberá que sempre que uma determinada URL for requisitada (ou padrão de URL), esta será direcionada para a respectiva Servlet mapeada.

Observe a seguinte requisição: "https://meudominio.com/minhaURL", nesta requisição, estamos a indicar que o pedido dever ir para o Servidor "meudominio.com", o protocolo utilizado é o "http", a porta é a padrão (80) e que estamos a invovar o recurso "minhaURL". Por fim, este recurso está associado a uma Servlet.

Quando nossa aplicação web esta dentro de uma enterprise aplication, o "mapeamento" de URL é um pouco diferente. Neste caso, surge a figura do contexto da aplicação. O contexto corresponde a um "agrupamento" lógico de elementos.  Já falamos da existência do ficheiro "application.xml" para este tipo de aplicação. É neste ficheiro que definimos o contexto raiz de nossa aplicação.

Como nocenário de enterprise applications, as aplicações web se encontram dentro de uma aplicação maior, o "caminho" para nos referirmos a uma aplicação web específica passa a conter o contexto raiz. Observe agora esta outra requisição: "https://meudominio.com/meuContexto/minhaURL", repare que a raiz da URL agora é "meuContexto", o que indica qual "enterprise application" deve ser utilizada. Em seguida possuímos o mapeamento da Servlet ("minhaURL"), dentro da Enterprise Aplication que estamos a referir.

Entendido como as requisiçõe serão direcionadas a Servlet, é preciso compreender o que acontece na Servlet. Em resumo, o método "service" será executado e serão fornecidos como parametros os objetos "Request" e "Response".

O objeto Request representa o pedido, com todas as informações recebidas e as estruturas do protocolo utilizado. Este objeto é muito importante dentro do fluxo de processamento do pedido, pois trata-se de um componente que irá "transitar" por todos os elementos envolvidos neste processo. Veremos mais adiante que no fluxo de processamento do pedido, podemos ter redirecionamentos e encaminhamentos, fazendo com que diversos componentes atuem neste processo. E o objeto Request estará presente em quase todos estes elementos.

O objeto Response representa a resposta que será enviada de retorno a quem solicitou o pedido. Em contexto de aplicações que disponibilizam serviçõs e não interfaces gráficas, este elemento ganha importância, pois é através dele que configuramos o "pacote" de resposta e seu conteúdo. Já no processo de produção de internfaces gráficas, este elemento perde um pouco de protagonismo. Pois, por praticidade e boas práticas, o fluxo de processamento normalmente irá "terminar" em uma JSP. Afinal, a construção de JSPs é muito mais produtivo por ser feito através de ferramentas gráficas.

A própria infra estrutura será responsável por atribuir ao elemento Response, o conteúdo da JSP utilizada como "resposta".

Um outro objeto importante dentro deste contexto é o Session. A session é um elemento transversal associado a um usuário durante todo o processo de iteração do mesmo com a aplicação. Quando o servidor recebe o primeiro pedido de um navegador, este inicia um contexto global para este usuário. A este contexto é atribuido um identificador. Sempre que o usuário volta a fazer pedidos, este identificador é fornecido e o contexto associado é "recuperado" e disponibilizado no fluxo de processamento do pedido. Isto faz com que a Session esteja presente em todos os Requests. Então, este elemento nos permite manter estado e armazenar "coisas" para serem consideradas em fluxos de processamentos distintos.

Apresentados estes elementos, podemos perceber que existe um conceito de contextos dentro de uma aplicação web: o contexto de aplicação, definido no elemento Session; o contexto de requisição, definido no elemento Request; e um último contexto não apresentado ainda que é o contexto de Página, Este contexto é o mais restrito de todos, e existe somente quando o fluxo de processamento do pedido é direcionado para uma JSP e durante o processamento desta JSP.

Em outras palavras, o contexto de página permite que sejam criados e utilizado objetos enquanto a página é construída.

Então para criarmos nossa Servlet precisamos escrever uma classe que seja derivada da classe GenericServlet, criar os ficheiros de configurações e contruir um pacote ".war" para em seguida publicá-lo em servidor web.

Quando estamos a desenvolver solução para um ambiente HTTP, podemos utilizar como elemento raiz para nossa Servlet, a HttpServlet (ao invés da GenericServlet), esta classe incrementa as funcionalidades da GenericServlet, disponibilizando novos métodos para recessão do pedido. basicamente a classe verifica a origem do pedido e identifica o método do pedido efetuado e direciona a execução para o respectivo método. Ou seja, esta classe possui, além do método "service", os métodos "doGet", "doPost", "doDelete", "doHead", "doOptions", "doPut" e "doTrace".

O elemento Servlet esta em constante evolução, incorporando novas funcionalidades e caraterísticas. A melhor forma de se manter atualizado é acompanhar as especificações mais recentes. A altura deste post a especificação mais recente é a 4.0.

JSP (Java Server Pages)

Como dito anteriormente o uso mais comum das páginas JSP são na construção das interfaces a serem exibidas como resultado final do processo de pedido e resposta.

Porém a JSP é muito mais robuzta, em termos de funcionalidade, do que simplesmente isto. Existe uma estrutura de TAGs próprias, que nos permitem construir "páginas limpas", facilitando assim a incorporação de lógica de programação aos elementos gráficos e as TAGs HTML. As TAGs JSP dividem-se em dois grupos, as TAGs de fluxo de código, que permitem inserir elementos de decisão e repetição; e as TAGs de dados, que permite acesso aos elementos da página e/ou dos contextos.

Então, a construção de uma página JSP consistem em programar o conteúdo a ser disponibilizado ao usuário. Quando estamos a falar de JSP, estamos a falar de páginas dinâmicas, diferente das página estaticas construídas somente com HTML.

Além das TAGs, a programação JSP pode ser feita através de "scriptlets". Os "scriptlets" são "pedaços" de códigos java, inseridos diretamente na JSP. Para adicionarmos um código Java a uma JSP, precisamos indicar o início e o final destes blocos de códigos através dos marcadores "<%" e "%>", indicando respectivamente o início e o final do bloco. Também podemos inserir "scriptlets" inline através do marcador "<%= CÓDIGO %>", onde o CÓDIGO corresponde a uma operação a ser processada e o seu resultado deverá produzir algum conteúdo a ser adicionado a página JSP em construção.

É importante não confundir o conteúdo dinâmico gerado em uma JSP, com o conteúdo dinâmico (DHTML) exibido em nosso navegador. Pois o conteúdo gerado pela JSP é produzido ainda no servidor, quando a página esta a ser processada. Já o DHTML consiste em programar os elementos existentes do lado cliente. Ou seja, os elementos que foram resultado do processamento do lado do servidor. Isto pode ficar confuso quando os elementos de interface (cliente) fazem novas chamadas ao servidor para obter informações ou recursos.

Então, uma página JSP será sempre processada do lado do servidor antes de ser enviada ao cliente. O código original da JSP não será disponibilizado ao cliente, mas sim o resultado deste processamento.

Como curiosidade, não menos importante, uma JSP será sempre compilada em uma classe Servlet quando publicada e utilizada pela primeira vez. Diferente da Servlet, quando alteramos o conteúdo de uma JSP e voltamos a utilizá-la, uma nova Servlet será produzida no lado do Servidor, em tempo de execução, para incorporar as alterações. Quero apontar com isto, que podemos alterar e verificar o resultado destas mudanças em tempo real. Este comportamento não se verifica para o caso das Servlets.

Uma JSP pode ser acessada diretamente, sem a necessidade de que ocorra um prévio processamento de uma Servlet para só então o fluxo chegar até estas páginas. Podemos inclusive ter aplicações web que são construídas apenas com páginas JSP.

Page Centric vs Servlet Centric

Nas primeiras implementações de aplicações web, debatia-se qual seria a melhor abordagem estrutural para desenhar e implementar as regras de negócio e as funcionalidades da aplicação.

Nesta altura duas abordagem ganharam força; uma abordagem centrada em páginas JSP e outra centrada em Servlets. Ao primeiro caso, deu-se o nome de "Page Centric" e ao segundo, "Servlet Centric".

Como dito, a abordagem "Page Centric" consiste em mapear e implementar através de links e submissão de formulários toda a aplicação. Com isto as regras de négócio e funcionalidades ficavam espalhadas por toda a aplicação. Tornando o processo de manutenção e ajustes algo complexo e custoso. Observe que neste contexto a lógica de programação se encontra inserida junto aos demias elementos da página.

Já a aborgam "Servlet Centric" consiste em criar um ponto central onde todas as requisições são direcionadas e através de um controlador, a devida regra de negócio e/ou funcionalidade será processada. Desta forma a gestão da aplicação passa ser centralizada, as rotinas que implementam as regras de negócios passam a estar isoladas e com isto a manutenção e os ajustes passam a ser mais rápidos e menos complexos.

Desenhando o Modelo Servlet Centric

Como dito a pouco, no modelo Servlet Centric todas as requisições concentram-se em um ponto para a seguir serem distribuídas.

Passada a etapa de configuração da Servlet e do deploy desta no servidor web, o foco agora será na implementação da gestão dos fluxos de execução. O "service" será sempre executado no contexto de requisição e resposta. Logo, este será o ponto de concentração que iremos utilizar.

Para permitir uma diferenciação de fluxo, iremos assumir e padronizar que em nosso modelo, todos as requisições deverão incorporar um parametros de controlo de fluxo. Vamos chamar este parâmetro de "flux". Ou seja, toda chamada a nossa aplicação, dentro do servidor, deverá conter este parâmetro.

Ex.: "https://meudominio.com/minhaServlet?flux=IDENTIFICADOR"

Do lado do servidor, dentro do método "service" da nossa Servlet, vamos agregar uma estrutura de decisão que irá identificar e direcionar o pedido para o devido código a ser processado. Para isto devemos buscar nos parametros do elemento Request, o valor fornecido para "flux".

Ex.:
String flux = request.getParameter("flux");
switch (flux){
  case "IDENTIFICADOR":
    processaIDENTIFICADOR (request, response);
    break;
  case "IDENTIFICADOR2":
     processaIDENTIFICADOR2 (request, response);
    break;
  default:
    goToMainPage();
}

O exemplo acima é bastante básico, entretanto é fácil perceber a idéia principal do modelo. Os métodos a serem executados, como resultante da avalição do fluxo, podem estar em classe distinta a da Servlet. Fazendo assim com que seja mais fácil identificar e localizar uma lógica que necessite de uma manutenção, por exemplo.

Observe que os métodos a serem executados recebem como parametros de entrada os elementos Request e o Response. Estes elementos são fornecidos como parametros pelo próprio método "service". Porque injetá-los nos métodos de processamento? Esta proposta de modelo prevê esta injeção para permitir que dentro dos respetivos métodos de processamento, as lõgicas implementadas também tenham acesso a valores que possam estar associados ao pedido que foi submetido pelo navegador (por exemplo, um formulário com informações a serem registadas).

Uma outra observação importante é o fato de que o método "service" da Servlet deve produzir o conteúdo da resposta a ser enviada de retorno ao requerente.tão vamos avançar na arquitetura e complementar o nosso modelo.

Ex.:
public interface FluxManager {
  public boolean process(ServletRequest request, ServletResponse response);
  public String getLandingPage();
}

public class Identificador1 implements  FluxManager {
   public boolean process(ServletRequest request, ServletResponse response)
   {
       ... codigo a ser processado
   }
   public String getLandingPage() {
       return "indicador1.jsp";
   }
}

public class Identificador2 implements FluxManager {
   public boolean process(ServletRequest request, ServletResponse response)
   {
       ... codigo a ser processado
   }
   public String getLandingPage() {
       return "indicador2.jsp";
   }
}


SERVLET
public void service(ServletRequest request, ServletResponse response){
String flux = request.getParameter("flux");
FluxManager fManager = null;

switch (flux){
   case "IDENTIFICADOR":
      fManager = newIdentificador1 ();
      break;
   case "IDENTIFICADOR2":
      fManager = newIdentificador2 ();
      break;
   default:
      request.getRequestDispatcher("main.jsp").forward(
            request, response);
   }
   if (fManager !=null && fManager. process(request, response) ) {
      request.getRequestDispatcher(fManager.getLandingPage() ).forward(
               request, response);
   } else {
       request.getRequestDispatcher("error.jsp").forward(
               request, response);
   }
}

No exemplo acima podemos perceber que de acordo com o IDENTIFICADOR fornecido, uma classe específica será associada a variável "fManage". Veja que esta variável é uma interface, entretanto, os elementos que são associados a ela, são classes que necessariamente devem implementar esta interface. Com isto, as classes passam a serem "obrigadas" a implementar os métodos definidos na interface.

A seguir, no código, utilizamos a variável "fManage"para realizar o processamento lógico do pedido (executando o método "process") e em seguida direcionamos o fluxo para a produção da resposta. Esta resposta também será dinâmica e será uma JSP definida pelo método "getLandingPage", que também faz parte da interface "FluxManager". Ou seja, a variável "fManage" também irá forncecer a JSP destino do fluxo de processamento.

Observe que a lógica existente no método "process" pode acessar o elemento Request e como consequencia o elemento Session. Com isto, o código pode, além de obter informações destes elementos, colocarrmações nestes elementos. Estas informações estarão disponíveis durante todo o contexto do fluxo de execução. Em outras palavras, estas informações estarão disponíveis no processamento da JSP para a qual o fluxo for direcionado.

O método "getRequestDispatcher" irá popular o elemento Response com o conteúdo da JSP processado e em seguida, esta resposta irá para o navegador (ou elemento que efetuou o pedido ao servidor).

XML de Configuração e Java "Reflection"

Até o momento, o nosso modelo possui todo o fluxo configurado no método "service" da Servlet. Caso seja necessário alguma alteração será preciso reescrever, recompilar e fazer um novo "deploy" da Servlet.

Imagine então se conseguissemos remover esta dependência e colocar a configuração para "fora" da Servlet. Podemos padronizar para que a configuração fique em um ficheiro "fluxConfig,xml". Assim quando precisarmos de alterar o fluxo, podemos simplesmente ajustar este ficheiro.

O ficheiro de configuração deve ser padronizado, de preferencia em estruturas XML para facilitar o "parse" do mesmo.

A cada requisição, seria necessário verificar neste ficheiro qual o fluxo e os elementos envolvidos no processamento do pedido. Ou seja, qual a classe Java que possui a lógica de processamento para o parâmetro "flux" recebido. Mas isto é um processo "pesado". Imagine a cada pedido, ter que abrir um ficheiro e fazer o "parse" para só então seguir com a execução da aplicação?

Porém, o conteúdo deste ficheiro pode ser "carregado" no momento em que a Servlet é iniciada pelo web container. Ou seja, colocamos a lógica de leitura e "parse" do conteúdo do ficheiro, no método "init()" da Servlet.

Neste ponto vale lembrar que no Java possuimos elementos estáticos. Isto mesmo, podemos usar esta funcionalidade para armazenar as configurações para que a cada requisição, não seja necessário obter o fluxo a partir do ficheiro de configurações. Assim, a cada requisição consultamos uma "tabela" em memória para obter a classe de processamento a partir da chave "flux".

Então, estamos com a seguinte situação neste novo modelo proposto. Possuimos a configuração em elementos externo a Servlet; carregamos estas configurações em memória no momento de inicialização da Servlet; obtemos a classe de processamento associada a uma chave, nesta "tabela" em memória.

Observe que quando adicionamos ou alteramos o fluxo de processamento, precisamos reinicar a nossa aplicação, pois as novas classes precisam ser agregadas ao contexto do Java (Classpath). Ao baixar e voltar a iniciar o servidor, nossa Servlet será carregada novamente, ou seja, será iniciada novamente. E as configurações serão atualizadas neste momento.

Ainda falta uma questão a ser resolvida. Como a nossa lógica vai incorporar as novas classes? Visto que o código de gestão de fluxo instancia as classes com o comando "new" do Java e este comando é específico para cada caso (para cada fluxo).

A solução esta na funcionalidade Reflection do próprio Java. Esta funcionalidade nos permite instanciar classes a partir da sua assinatura (nome completo, incluindo o pacote em que se encontra). Ou seja, como uma pequena alteração em nosso código podemos fazer com que a lógica busque na "tabela" de configuração em memória, o nome da classe que esta associado ao "flux" fornecido. Em seguida, através de reflexão, instanciamos um objeto deste tipo e chamamos os respetivos métodos "process" e "getLandingPage".

Ex.:
SERVLET

private static HashMap tabela = new HashMap();
public init(){
    carregaTabela(tabela,  "fluxConfig,xml");
}

public void service(ServletRequest request, ServletResponse response) {
  String flux = request.getParameter("flux");
  FluxManager fManager = Class.forName( (String) tabela.get(flux));

  if (fManager !=null && fManager. process(request, response) ) {
    request.getRequestDispatcher(fManager.getLandingPage() ).forward(
    request, response);
  } else {
    request.getRequestDispatcher("error.jsp").forward(
    request, response);
  }
}

O código acima não esta a considerar eventuais excessões e outras garantias, afinal o objetivo é simplesmente ilustrativo. É possível perceber todo o processo explicado anteriormente.

Evoluindo de XML para Anotações Java

Para colocar nosso modelo em um contexto mais atual, podemos criar uma anotação própria para indicar as classes Java que devem ser "carregadas" na tabela de memória com o controlo do fluxo (@Flux). Esta anotação deve ter um atributo, o "fluxId". Este atributo irá indicar o valor que deverá existir associado ao pedido, no parametro "flux" para que esta classe seja utilizada para processar o pedido.

Ex.:
@Flux (fluxId="IDENTIFICADOR1")
public class Identificador1 implements FluxManager {
  public boolean process(ServletRequest request, ServletResponse response)
  {
    ... codigo a ser processado
  }
  public String getLandingPage() {
    return "indicador1.jsp";
  }
}

Observe que a classe continua a implementar a interface "FluxManager, afinal esta é a parte "generica" da lógica de controlo de fluxo. A diferença em nossa programação do modelo ocorrerá no método "init" e no método "service". O método "init" deverá verificar entre as classes do projeto, quais possuem a anotação @Flux e colocar estas classses na tabela estática com as configurações do fluxo. Ou seja, deverá buscar o atributo "fluxId" para utilizar como chave na tabela.

Observe que neste modelo, a tabela de configurações deixa de ter no nome da classe e passa a ter a classe em si. Ou seja, será necessário adaptar a logica do método "service" para incorporar esta alteração.

Ex.:
public void service(ServletRequest request, ServletResponse response) {
   String flux = request.getParameter("flux");
   FluxManager fManager = (FluxManager) tabela.get(flux);
   if (fManager !=null && fManager. process(request, response) ) {
      request.getRequestDispatcher(fManager.getLandingPage() ).forward(
            request, response);
   } else {
      request.getRequestDispatcher("error.jsp").forward(
      request, response);
   }
}

Muito dos frameworks atuais existentes para aplicações web utilizam estes conceitos na base de sua infra estrutura. Alguns mais rebuscados, incorporando eventos, filtros, pré e pós processamentos etc. Porém estas funcionalidades agregadas podem ser perfeitamente entendidas após esta breve explicação.

Edoardo Boechat Unip LDA, Esmeriz, Portugal 4760-480
Desenvolvido por Webnode
Crie seu site grátis! Este site foi criado com Webnode. Crie um grátis para você também! Comece agora