BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Notícias Java EE6: EJB3.1 é uma evolução irresistível

Java EE6: EJB3.1 é uma evolução irresistível

A especificação Enterprise Java Bean 3.0 (EJB 3) marcou uma direção muito importante na longa marcha da comunidade java enterprise. Aparentemente, ela foi construída usando realmente o feedback da comunidade, representando-se como um paradigma de serviços muito mais consistente, que é mais amigável com POJOs e geralmente mais simples (ou menos complicado). O nível de indireção proporcionado pelas anotações do Java 5 fizeram com que o paradigma muito mais poderoso, enquanto exigem muito menos do desenvolvedor. A boa vontade em abandonar algumas decisões ruins do passado, priorizando soluções novas e diferentes, fizeram com que o framework ficasse muito mais interessante pas as pessoas que anteriormente rejeitavam o EJB. Os Entity Beans desapareceram, substituídos por Entidades JPA. Aquele monte de classes Java e interfaces necessárias para um bean comum EJB 2.1 ou anterior foram reduzidas para pouco mais de duas classes ou interfaces. Alguns padrões construidos sobre os princípios de convenções ao invés de configurações entraram em cena para fazer com que pudéssemos ter um início bem rápido, sem muita burocracia. EJB 3.0 definitivamente foi uma grande revolução.

Se o EJB 3.0 foi uma revolução, EJB 3.1 é uma evolução bem capaz e bem-vinda. Com uma gama de funcionalidades que dão a impressão de que elas já deveriam ter sido disponibilizadas no EJB 3.0. Até podemos compreender os passos cautelosos da especificação - melhor termos 80% de acerto do que congelar um modelo imperfeito em uma evolução glacial de especificação. Apesar disso, a chegada do EJB 3.1 é bem vinda, obviamente. Vale citar que mesmo com todas essas novas funcionalidades, a especificação EJB 3.1 e todas as retrocompatibilidades e melhorias oferecidas, possui 626 páginas, 14 a menos do que a especificação EJB 2.1 de praticamente uma década atrás!

Nós revisaremos algumas dessas novidades neste post, e mostraremos as suas utilidades.

Novas funcionalidades do EJB 3.1

Algumas das maiores mudanças do EJB 3.1 não são adições à plataforma, mas reduções na burocracia necessária no uso da plataforma. Também contamos com algum crescimento nas APIs para os usuários, trazendo mais flexibilidade.

Singletons

Um singleton é um novo tipo de session bean que fornece uma garantia extra: o bean será criado apenas uma vez por JVM em execução. Existem vários usos que se aproveitam dessa funcionalidade: Cacheamento, por exemplo. Uma outra é a possibilidade de garantir uma "visão compartilhada" em um recurso que o servidor de aplicação não tenha disponibilizado. Armazenamento simples para dados importantes que não necessitem de persistência, mas talvez sejam muito custosos de serem recriados. Vejamos um exemplo. Vamos assumir que a entidade User já foi criada em outro lugar (talvez usando JPA 2.0).

@javax.ejb.Singleton 
public class ChatRoom { 

   private java.util.Map

   @PostConstruct 
   public void setup (){ 
     userComments = new java.util.concurrent.ConcurrentHashMap
     /* ...*/
   }
   public void join(User usr){ /* ...*/ }
   public void disconnect(User usr){  /* ...*/ }
   public void say(String msg){ /* ...*/  }
}

Todos os clientes podem atualizar o mesmo estado mutável simplesmente adquirindo uma referência para a instância de ChatRoom disponibilizada, já que é garantido que todos eles acessarão a mesma instância após a atualização. Como ele é um session bean, oferece as mesmas garantias que qualquer outro session bean forneceria: esse bean é totalmente thread-safe.

@javax.ejb.EJB 
private  ChatRoom  chatRoom ; 

 /* ... */

chatRoom.join(myUser) ;
chatRoom.say( "Hello, world!");
chatRoom.disconnect();

Singletons foram projetados para permitir acesso concorrente. A especificação dá ao desenvolvedor um controle sofisticado sobre o gerenciamento de concorrência. Talvez você use o container para forçar certos tipos de acesso de maneira declarativa. Esse comportamento é o padrão. Você pode declarar explicitamente que quer que o container gerencie a concorrência anotando o código com @javax.ejb.ConcurrencyManagement(CONTAINER). Se você quiser exercer um controle maior sobre o bean, utilize @javax.ejb.ConcurrencyManagement(BEAN). A concorrência gerenciada pelo container permite que você estipule o tipo de acesso a nível de metódo ou classe. Você poderia estipular por padrão que todos os métodos de negócio devem ser serializados usando @javax.ejb.Lock(WRITE) no nível da classe e posteriormente, otimizar os métodos que são de fato, somente para leitura, que não possuem nenhum comportamento que altere estado. Neste caso, você anotaria o método com @Lock(READ). Todos acessos em um método anotado com @Lock(WRITE) é serializado, e bloqueia os acessos de cliente até que o método retorne, ou um timeout aconteça. Você pode exercer controle sobre o tempo de timeout usando a anotação @AccessTimeout, que recebe um argumento do tipo java.util.concurrent.TimeUnit. Dessa forma, nós podemos reescrever o código da primeira implementação da ChatRoom para aproveitar o controle de concorrência.

@javax.ejb.Singleton 
@javax.ejb.Lock(WRITE)
public class ChatRoom { 

   private java.util.Map

   @PostConstruct 
   public void setup (){ 
     userComments = new java.util.concurrent.ConcurrentHashMap
     /* ...*/
   }
   public void join(User usr){ /* ...*/ }
   public void disconnect(User usr){  /* ...*/ }
   public void say(String msg){ /* ...*/  }

   @javax.ejb.Lock(READ)
   public int getCountOfActiveUsers(){ /* ... run through the map and count ... */ } 
}

É claro que uma ChatRoom morreria por falta de memória conforme o número de usuários e posts lotassem a memória do servidor de aplicação. Seria bom usar algum tipo de mecanismo de expiração. Apenas para demonstração, vamos imaginar um coletor de lixo periódico que varre os posts da ChatRoom e remove os posts usando um algoritmo de LRU (ou, talvez, usando JPA e o EntityManager.persist e depois) destruindo dados velhos do chat.

O Timer EJB

O EJB já possui um mecanismo de Timer desde a versão 2.1. Entretanto, ele sempre funcionou em intervalos de milissegundos e sempre foi muito chato de configurar. A situação melhorou parcialmente com a versão 3.0, mas o fato é que os timer continuavam sendo essencialmente construídos de forma totalmente procedural, e baseados apenas em intervalos. Se você quiser que alguma tarefa seja agendada para o começo da semana, por exemplo, poderia considerar isto como um grande desafio. EJB 3.1 melhora isso fornecendo um serviço de timer declarativo e flexível que tira o melhor de vários outros agendadores de tarefas, como o Quartz ou Flux. Para as mais simples necessidades de agendamento, incluindo os agendamentos do tipo CRON, EJB 3.1 é uma solução interessante. Vamos rever nossa sala de chat. Queremos agendar um bean para fazer a varredura e limpeza de dados mais antigos de nossa sala de chat.

@javax.ejb.Singleton 
public class ChatRoom {

   private java.util.Map
        
   @javax.ejb.Schedule(minute="1", hour="*") 
   public void cleanOutOldDataFromChatLogs() { 
      /** ... not reprinting all the existing code ... */
   }
}

Escrevemos um método que varrerá e verificará os dados, e conforme a necessidade, eliminará os dados velhos ou irrelevantes do chat, de maneira que possamos manter o volume de dados em um estado gerenciável. Aqui, utilizamos um modelo declarativo para nos assegurarmos que o método será executado de hora em hora. Logicamente, poderíamos ter injetado o TimerService do EJB 3.0.

Session Beans sem InterfaceNo-Interface Views

No EJB 3.0, os beans deveriam obrigatóriamente ter pelo menos uma interface (local ou remota) que seria usada para expor as operações do bean para os clientes. Enquanto indireção via interfaces é uma técnica poderosa, algumas vezes acaba por complicar código que poderia ser muito mais simples. No EJB 3.1, você pode construir um bean que não possua interfaces. Neste caso, os métodos públicos do bean serão expostos ao cliente.

@javax.ejb.Stateless 
public class Calculator { 

  public float add ( float a , float b) { 
    return a + b; 
  } 

  public float subtract (float a , float b){ 
    return a - b ; 
  }

} 

Clientes desse bean podem simplesmente adquirí-lo como sempre via injeção e invocá-lo assim:

@javax.ejb.EJB 
private Calculator calculator; 

...
float result = calculator.add( 10 , 10 ) ;
...

Serviços Assíncronos

A maneira mais simples de lidar com problemas de escalabilidade é simplesmente... não tratá-los! (enquanto recursos de máquina estiverem disponíveis) Essa abordagem é caracterizada pelo padrão SEDA, onde os gargalos são evitados enfileirando tarefas. Isso permite o enfileiramento de tarefas de maneira que o cliente consiga prosseguir na utilização do sistema, até que o servidor termine o processamento e devolva uma resposta. Se um componente na fila demorar muito tempo para ser processado, e o sistema estiver sob forte carga, esse padrão se certifica que o processo lento não levará o sistema a um estado de indisponibilidade.

Outra abordagem para escalonamento é não bloquear as invocações de clientes na troca de mensagens de uma via, ou simplesmente trabalhar de forma assíncrona, deixando a execução no cliente prosseguir até que um resultado seja retornado. Todas essas abordagens foram incorporadas no novo suporte a serviços assíncronos do EJB 3.1. Uma classe de um session bean, ou métodos individuais, podem ser anotados com @javax.ejb.Asynchronous, então todos métodos no bean serão deferidos para processamento posterior; Caso contrário, só os métodos anotados com @Asynchronous serão deferidos. Métodos assíncronos podem retornar void, ou uma instância de java.util.concurrent.Future<V>. O cliente pode consultar essa instância procurando o resultado a qualquer momento posteriormente, mas o resultado da invocação é devolvido imediatamente à thread do cliente, e não bloqueia. Desta forma, se o EJB levar uma hora ou duas, não importa - o cliente não será afetado. Conceitualmente é a mesma coisa do que chamar um serviço cuja única tarefa é mandar a requisição para uma fila JMS.

A instância de Future<V> pode ser usada para cancelar a tarefa, ou esperar pelo resultado. O contexto transacional de um cliente não é propagado a um método assíncrono. REQUIRED, então, efetivamente REQUIRES_NEW para o método assíncrono.

Vamos ver um exemplo: Queremos fazer um serviço que se integre com diversos outros webservices e agregue os resultados. Queremos os resultados, mas não podemos deixar a requisição do cliente (uma página web, talvez?) congelada. Um serviço desses seria mais ou menos desta forma:

@javax.ejb.Stateless
public CarHotelAndAirLineBookingServiceBean implements CarHotelAndAirLineBookingService  {
  @javax.ejb.Asynchronous  
  public Future bookCarHotelAndAirLine( Car rental, Hotel hotel, AirLine airLine) { 
    /**  ...  */
   } 
 } 

No cliente, uma action do JSF, poderíamos invocar o serviço da seguinte forma:

@Named 
public BookingAction { 

        @javax.ejb.EJB  private  CarHotelAndAirLineBookingServiceBean bookingService; 

        private Future confirmation;

        public String makeRequest(){ 

                Hotel hotelSelection = ... ;
                Car rentalSelection = ... ;
                AirLine airLineSelection = ... ; 

                confirmation = bookingService.bookCarHotelAndAirLine(
                    rentalSelection, hotelSelection, airLineSelection ) ; 
                return "showConfirmationPending";
        }

        public Future getConfirmation(){ /* ... */ }

        /* ... */
}

Implantação Simplificada

O EJB 3.1 também marca a primeira versão da especificação que fornece um paradigma radicalmente simples de implantação dentro de um arquivo .WAR. Uma classe com uma anotação de definição de componentes se torna um enterprise bean quando empacotado dentro de WEB-INF/classes ou em um arquivo .jar dentro de WEB-INF/lib. Enterprise beans podem também ser definidos usando um arquivo WEB-INF/ejb-jar.xml. Beans empacotados dentro de um arquivo .WAR compartilham um único namespace, e se tornam parte do ambiente do arquivo .WAR. Empacotar um .jar dentro de WEB-INF/lib é portanto semânticamente equivalente a colocar as classes em WEB-INF/classes.

Outra novidade na especificação é o EJB Lite. Para muitas aplicações, a tecnologia EJB é mais do que necessária. O EJB lite fornece uma forma mais simples de trabalhar baseado em componentes session bean. Ele fornece uma forma de usar componentes EJB de maneira embarcada também, o que simplifica os testes unitários. Suporta beans stateless, stateful e singleton. Os beans podem ter uma interface local ou nenhuma interface. Eles podem funcionar com interceptadores, e usar os serviços do container como transações e segurança.

EJB 3.1 é uma ferramenta poderosa nas mãos do desenvolvedor, e está claramente sendo evoluída em algo que atenderá 80% dos casos de uso da maioria das aplicações. O futuro parece bem favorável para esta especificação. Esta é tambem a primeira release da especificação que indica algumas especificações para o mecanismo de remoção do Java SE. Aspectos da especificação foram marcados para uma possível remoção futura, incluindo o suporte a versões anteriores à 3.0 para antigas formas de persistência gerenciada por container e gerenciada programaticamente (BEAN), a visão EJB 2.1 no cliente de entity beans, EJB QL (a linguagem de pesquisa do EJB 2.1), e web-services baseados em JAX-RPC (tanto endpoints como as partes de cliente, que foram adicionadas no J2EE 1.4)

Claramente, EJB 3.1 é uma atualização com retrocompatibilidade muito interessante, e representa um excelente próximo passo do trabalho iniciado na JSR 220 (EJB 3.0), que começou a mais de 5 anos atrás.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT