Nas últimas versões os Servlets têm adquirido novidades indispensáveis aos desenvolvedores. O suporte assíncrono e o novo recurso de E/S sem bloqueio permitem que possamos utilizar melhores os recursos do servidor, cada vez mais indispensáveis quanto mais aumentamos o tamanho das aplicações e a base de usuários que cresce a cada dia. No restante do artigo veremos esses dois assuntos e também o Web Fragments que vem sendo cada vez mais utilizado nas aplicações corporativas.

Suporte Assíncrono

Os recursos do servidor são importantes e devem ser utilizados de forma conservadora. Consideremos um Servlet que precisa esperar uma conexão JDBC estar disponível a partir de um pool de conexões, ou um Servlet que espera receber uma mensagem JMS ou ainda um Servlet aguardando a leitura de um recurso do sistema de arquivos. Aguardar pela execução de processos de longa execução bloqueando a thread não é considerado uma boa prática de uso dos recursos do servidor.

Este é um caso onde o servidor pode ser processado de forma assíncrona. O processamento assíncrono permite que o controle (ou a thread) seja retornado para o container para que ele possa assim executar outras tarefas enquanto aguardamos que um processo de longa execução seja completado. O processamento de solicitações continua na mesma thread após a resposta do processo de longa execução ser retornado, ou então pode ser encaminhado para um novo recurso a partir do processo de longa execução.

O comportamento assíncrono precisa ser explicitamente habilitado em um Servlet. Podemos fazer isso apenas adicionando o atributo “asyncSupported” em @WebServlet. Segue na Listagem 1 um exemplo.

Listagem 1. Habilitando o processamento assíncrono no Servlet.


  @WebServlet(urlPatterns="/async", asyncSupported=true)
  public class MyAsyncServlet extends HttpServlet {
           //. . .
  }

Também podemos habilitar o comportamento assíncrono configurando o elemento no arquivo web.xml para “true” ou chamando “ServletRegistration.setAsyncSupported(true)” programaticamente.

Podemos então iniciar o processamento assíncrono em uma thread separada usando o método “startAsync” na requisição. Este método retorna “AsyncContext” que representa o contexto de execução da requisição assíncrona. Dessa forma, podemos completar a requisição assíncrona chamando explicitamente “AsyncContext.complete” ou de forma implícita enviando para outro recurso. Caso quisermos completar a requisição de forma implícita o container completará a invocação da requisição assíncrona.

Podemos implementar um processamento assíncrono conforme o código da Listagem 2.

Listagem 2. Exemplo implementação de um processo assíncrono.


  class MeuServicoAssincrono implements Runnable {
           AsyncContext ac;
   
           public MyAsyncService(AsyncContext ac) {
                     this.ac = ac;
           }
   
           @Override
           public void run() {
                     //. . .
                     ac.complete();
           }
  }

Este serviço pode ser invocado através do método doGet, conformeexemplificado na Listagem 3.

Listagem 3. Exemplo invocando o serviço implementado anteriormente.


  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) {
           AsyncContext ac = request.startAsync();
   
           ac.addListener(new AsyncListener() {
                     public void onComplete(AsyncEvent event) throws IOException {
                              //. . .
                     }
   
                     public void onTimeout(AsyncEvent event) throws IOException {
                              //. . .
                     }
   
                     //. . .
           });
   
           ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
   
           executor.execute(new MeuServicoAssincrono (ac));
  }

Neste código o “request” é colocado em um modo assíncrono. AsyncListener é registrado para escutar eventos quando o processamento de uma requisição esta completo, ou tem o tempo excedido, ou ainda se resultou em algum erro.

O serviço de longa execução é invocado em uma thread separada e chama AsyncContext.complete para sinalizar a conclusão do processamento da solicitação.

Uma requisição pode ser enviada de um Servlet assíncrono para um síncrono, mas ao contrário é ilegal.

O comportamento assíncrono está disponível no Servlet Filter também.

E/S Sem bloqueio

O Servlet 3.0 permitiu que utilizássemos o processamento de requisições assíncronas, mas só é permitido o tradicional I/O, que dessa forma restringiu a escalabilidade dos aplicativos.

Em uma aplicação típica, o ServletInputStream é lido em um loop while, conforme exemplificado na Listagem 4.

Listagem 4. Exemplo de como é lido um ServletInputStream.


  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
           ServletInputStream input = request.getInputStream();
           byte[] b = new byte[1024];
           int tam = -1;
   
           while ((tam = input.read(b)) != -1) {
                     //. . .
           }
  }

Se os dados de entrada estão bloqueando ou transmitindo mais lentamente do que o servidor pode ler, então a thread do servidor fica à espera desses dados. O mesmo pode acontecer se os dados são gravados em ServletOutputStream. Este tipo de comportamento acaba restringindo a escalabilidade do Container Web.

O Nonblocking I/O (Entrada/Saída Sem Bloqueio) permite que os desenvolvedores leiam dados quando eles estiverem disponíveis ou então gravar os dados quando for possível. Isto não aumenta somente a escalabilidade do Container Web, mas também o número de conexões que podem ser gerenciadas simultaneamente.

A E/S Sem Bloqueio apenas funciona com o processamento de requisições assíncronas em Servlets, Filter e Upgrade Processing.

O Servlet 3.1 permite a funcionalidade E/S Sem Bloqueio através da introdução de duas novas interfaces: ReadListener e WriteListener.

Esses listeners possuem métodos de callback que são invocados quando o conteúdo está disponível para ser lido ou quando ele pode ser escrito sem bloqueio.

Para isso precisamos reescrever o método doGet anterior, conforme exemplificado no código da Listagem 5.

Listagem 5. Exemplo do método doGet reescrito.


  AsyncContext context = request.startAsync();
  ServletInputStream input = request.getInputStream();
  input.setReadListener(new MeuReadListener(input, context));

Invocando métodos setXXXListener indica que a E/S Sem Bloqueio é usada ao invés da E/S tradicional.

O ReadListener tem três métodos de callback, são eles:

  • O método onDataAvailable. Este método é chamado sempre que um dado pode ser lido sem bloqueio.
  • O método onAllDataRead. Este método é invocado sempre que os dados para a requisição atual são completamente lidos.
  • O método onError. Este método é invocado se houver um erro ao processar o pedido.
Segue na Listagem 6 um exemplo de implementação usando os métodos de callback anteriores.

Listagem 6. Exemplo utilizando os métodos do ReadListener.


  @Override
  public void onDataAvailable() {
           try {
                     StringBuilder sb = new StringBuilder();
                     int tam = -1;
                     byte b[] = new byte[1024];
   
                     while (input.isReady() && (tam = input.read(b)) != -1) {
                              String data = new String(b, 0, tam);
                     }
           } catch (IOException ex) {
                     //. . .
           }
  }
   
  @Override
  public void onAllDataRead() {
           context.complete();
  }
   
  @Override
  public void onError(Throwable t) {
           t.printStackTrace();
           context.complete();
  }

Neste código o método onDataAvailable é invocado sempre que a informação pode ser lida sem bloqueio. O método ServletInputStream.isReady é usado para checar se o dado pode ser lido sem bloqueio e então o dado é lido.

O context.complete é chamado no método onAllDataRead e onError para sinalizar a conclusão da leitura de dados.

ServletInputStream.isFinished pode ser usado para checar o status de uma leitura sem bloqueio de I/O.

No máximo, um ReadListener pode ser registrado em ServletIntputStream.

O WriteListener por sua vez tem dois métodos de callback:

  • O método onWritePossible. Este método é chamado sempre que o dado pode ser escrito sem bloqueio.
  • O método onError. Este método é invocado se existe um erro ao processar a resposta.

No máximo, um WriteListener pode ser registrado em um ServletOutputStream.

Por fim, ServletOutputStream.canWrite é um novo método para checar se o dado pode ser escrito sem bloqueio.

Web Fragments

Um Web Fragment é parte ou todo o arquivo web.xml incluído em uma biblioteca ou no diretório “META-INF” do JAR. Se o arquivo estiver no diretório “WEB-INF/lib”, o container faz a configuração automática sem exigir que o desenvolvedor o faça explicitamente.

Este arquivo pode incluir praticamente todos os elementos que podem ser especificadas em web.xml. No entanto, o elemento de nível superior deve ser chamado “webfragment” e o arquivo correspondente deve ser chamado webfragment.xml. Isso permite o particionamento lógico da aplicação web:

Segue na Listagem 7 um exemplo de um arquivo “webfragment.xml”.

Listagem 7. Exemplo de um arquivo webfragment.xml.


  <web-fragment>
           <filter>
                     <filter-name>MeuFilterExemplo</filter-name>
                     <filter-class>app.exemplo.MeuFilterExemplo</filter-class>
   
                     <init-param>
                              <param-name>meuInitParam</param-name>
                              <param-value>...</param-value>
                     </init-param>
           </filter>
   
           <filter-mapping>
                     <filter-name>MeuFilterExemplo</filter-name>
                     <url-pattern>/*</url-pattern>
           </filter-mapping>
  </web-fragment>

O desenvolvedor pode especificar a ordem que os recursos especificados no “web.xml” e “web-fragment.xml” devem ser carregados. O elemento no web.xml é usado para especificar a ordem exata ou absoluta que os recursos deveriam ser carregados, e o elemento dentro de web-fragment.xml é usado para especificar a ordem relativa.

As duas ordens são mutuamente exclusivas, e a ordem absoluta sobrescreve a ordem relativa. A ordem absoluta contém um ou mais elementos “” que especifica o nome dos recursos e a ordem na qual eles necessitam ser carregados.

Especificando o elemento “” permitimos que outros recursos não mencionados na ordem sejam carregados. Segue na Listagem 8 um exemplo de como podemos especificar uma ordem absoluta:

Listagem 8. Exemplo de como especificamos a ordem absoluta no arquivo web.xml.

 
  <web-app>
           <name>MinhaAplicacao</name>
           <absolute-ordering>
                     <name>MeuServletExemplo</name>
                     <name>MeuFilterExemplo</name>
           </absolute-ordering>
  </web-app>

Neste código, os recursos especificados em web.xml são carregados primeiro e seguido por MeuServletExemplo e MeuFilterExemplo.

Também podemos ter zero ou um elemento e em para especificarmos os recursos que precisam ser carregados antes e depois do recurso chamado no “webfragment” ser carregado:


  <web-fragment>
           <name>MeuFilterExemplo</name>
           <ordering>
                     <after>MeuServletExemplo</after>
           </ordering>
  </web-fragment>

Este código vai exigir que o Container carregue o recurso MeuFilterExemplo depois que o recurso MeuServletExemplo (definido em outro lugar) for carregado.

Se web.xml tem um metadata-complete setado para true, então o arquivo web-fragment.xml não é processado.

O arquivo web.xml sempre possui uma prioridade mais alta ao resolver os conflitos entre “web.xml” e “web-fragment.xml”.

Por fim, vale ressaltar que se um arquivo “web-fragment.xml” não tem um elemento “” e o web.xml não tem um elemento “”, os recursos são considerados como não possuírem qualquer dependência de ordem.

Bibliografia

[1]Josh Juneau. Java EE 7 Recipes: A Problem-Solution Approach. Apress, 2013.

[2]Josh Juneau. Introducing Java EE 7: A Look at What's New. Apress, 2013.

[3]Arun Gupta. Java EE 7 Essentials. O'Reilly, 2013.