INTRODUÇÃO À MESSAGE DRIVEN BEANS
Uma visão geral sobre o desenvolvimento de componentes para processamento de mensagens assíncronas em J2EE
Alexandre Bastos Borges ( alexandre@bankware.com.br)
Um Message Driven Bean (ou simplesmente MDB) é um Enterprise Java Bean que permite o processamento de mensagens de forma assíncrona, em aplicações desenvolvidas com J2EE.
Na verdade, excetuando-se algumas diferenças de estrutura que serão listadas no decorrer do artigo, a grande vantagem desse novo componente acrescido à especificação EJB 2.0 frente aos session beans é exatamente a possibilidade de se desenvolver aplicações cujo o processamento de envio e recebimento de mensagens JMS seja assíncrona.
Esse artigo visa oferecer uma introdução ao desenvolvimento de Message Driven Beans. Para rodar o exemplo que iremos fornecer no decorrer do artigo, foi utilizado o servidor de aplicações JBoss, na versão 3.2.6, rodando sob o SDK 1.4.2_0.6.
Introdução
Para uma aplicação cliente, um message driven bean funciona como um listener de mensagens. O MDB, diferentemente dos outros tipos de EJB, não possui interfaces home e remote, porque não existe entre a aplicação cliente e o componente um acoplamento, via código. O cliente interage com o MDB através de JMS (Java Message Service), enviando mensagens ao destino JMS. Esse diferencial é de grande valia no desenvolvimento corporativo. (Para saber mais sobre JMS veja a MundoJava número 8).
Imagine uma aplicação web que, dada uma determinada situação necessite gerar um arquivo com milhões de linhas, com diversos select’s, updates, controle transacional, etc. Suponha que esse processamento dure uns 5 minutos. Esse cenário, muito comum no desenvolvimento corporativo, poderia ser disparado por uma mensagem assíncrona, assim o processamento ocorreria em paralelo, sem impacto para o cliente.
Por este motivo, é necessário ao leitor um conhecimento básico da api do Java Message Service para um melhor entendimento do funcionamento desse componente. Observe a figura 1:

Figura 1. Processamento de mensagens com Message Driven Beans (fonte: TheServerSide)
Analisando o gráfico acima fica fácil perceber a separação entre o cliente e o MDB. O cliente envia a mensagem através de JMS, e do outro lado o message driven a consome.
API’s
Na construção de um Message Driven Bean, três interfaces são de extrema importância, sendo que duas devem ser diretamente implementadas no Bean e uma tem sua implementação fornecida pelo container. Discutiremos as três à seguir:
• Interfaces que devem ser implementadas:
Um message driven bean deve necessariamente implementar as seguintes interfaces:
• javax.ejb.MessageDrivenBean
• javax.jms.MessageListener
A interface javax.jms.MessageListener
Essa interface é utilizada para tornar o MDB um ouvinte, assim o Bean pode receber as mensagens entregadas em um determinado destino.
Ela somente define o método onMessage, para a implementação da lógica de processamento da mensagem. A assinatura do método onMessage é a seguinte:
public abstract void onMessage( javax.jms.Message message )
A interface javax.ejb.MessageDrivenBean
O container utiliza os métodos dessa interface para notificar o MDB sobre os eventos de seu ciclo de vida. Ela define dois métodos: setMessageDrivenContext e ejbRemove. Abaixo segue a assinatura de ambos:
public abstract void setMessageDrivenContext( MessageDrivenContext context ) throws javax.ejb.EjbException
public abstract void ejbRemove() throws javax.ejb.EjbException
O setMessageDrivenContext é chamado pelo container para passar um o objeto de contexto ao MDB (leia a definição da interface MessageDrivenContext abaixo), e o ejbRemove é chamado antes do container remover a instância do MDB.
• Interface com a implementação fornecida pelo container:
A interface javax.ejb.MessageDrivenContext
Um objeto MessageDrivenContext é fornecido pelo container a cada instância de MDB criada. A interface MessageDrivenContext oferece acesso ao contexto da mensagem à instância do MDB em tempo de execução. Ela herda de javax.ejb.EJBContext, e oferece os seguintes métodos:
• boolean getRollbackOnly: retorna se foi efetuado rollback na transação. Só deve ser chamado por MDBs com transações gerenciadas por container.
• UserTransaction getUserTransaction: Retorna uma instância de UserTransaction. Somente pode ser chamdo por MDBs com transações gerenciadas por bean.
• boolean isCallerInRole: retorna se o chamador recebeu uma função de segurança.
• void setRollbackOnly: efetua rollback na transação. Somente MDBs com transação gerenciada por container podem chamar esse método.
• Principal getCallerPrincipal, EJBHome getEJBHome, EJBLocalHome getEJBLocalHome: herdados de EJBContext, não tem função em MDBs e não podem ser chamados.
Criando um Message Driven Bean
Com o prévio conhecimento das interfaces necessárias para desenvolver um MDB, vamos agora a um simples exemplo prático. Iremos criar um Message Driven que lê o texto contido em uma mensagem e a imprime no console. O seu código fonte abaixo na listagem 1:
Listagem 1. Implementação do Message Driven Bean MundoJavaMDBBean.
package br.com.mundojava;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
public class MundoJavaMDBBean implements MessageDrivenBean, MessageListener {
private MessageDrivenContext context;
public MessageDrivenContext getMessageDrivenContext() {
return context;
}
public void setMessageDrivenContext(MessageDrivenContext ctx) {
context = ctx;
}
public void onMessage(Message msg) {
try {
TextMessage message = (TextMessage) msg;
System.out.println( message.getText() );
} catch (JMSException e) {
System.out.println("ERROR: " + e.getMessage());
}
}
public void ejbCreate() {
}
public void ejbRemove() {
}
}
Note que declaramos o método ejbCreate() no nosso MDB de exemplo. Embora a implementação desse método não seja exigido por nenhuma das interfaces utilizadas, devemos implementá-lo pois o mesmo sempre é chamado pelo container no momento de criação de uma nova instância. Portanto o mesmo pode ser utilizado para tarefas como inicialização de conexões, inicialização de variáveis de instância, etc.
Para efetuarmos o deploy desse MDB no JBOSS, precisamos ainda escrever o ejb-jar.xml (deployment-descriptor, único para qualquer container EJB) e o jboss.xml (arquivo auxiliar de deploy, específico para o JBOSS). O fonte dos dois segue abaixo:
Listagem 2. ejb-jar.xml.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar id="ejb-jar_ID">
<display-name>EJB</display-name>
<enterprise-beans>
<message-driven>
<ejb-name>MundoJavaMDB</ejb-name>
<ejb-class>br.com.mundojava.MundoJavaMDBBean</ejb-class>
<transaction-type>Container</transaction-type>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
</assembly-descriptor>
</ejb-jar>
ejb-name: especifica o nome do EJB;
ejb-class: o nome totalmente qualificado do MDB;
transaction-type: o tipo de gerenciamento trabsacional do MDB. ‘Container’ especifica que esse gerenciamento será efetuado pelo container, e ‘Bean’ especifica que você fará esse gerenciamento programaticamente.
acknowledge-mode: Modo de reconhecimento das mensagens pelo bean, para o nosso exemplo usaremos AUTO_ ACKNOWLEDGE, ou seja, reconhecimento automático.
destination-type: Tipo de destino do MDB, Queue para modelos “point-to-point” e Topic para modelos “publish/subscribe”.
Listagem 3. jboss.xml.
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>MundoJavaMDB</ejb-name>
<destination-jndi-name>queue/MundoJavaQueue</destination-jndi-name>
</message-driven>
</enterprise-beans>
</jboss>
O arquivo jboss.xml se faz necessário, pois você precisa informar ao container qual é o nome da fila a qual o Message Driven Bean estará escutando para receber mensagens (Veja o quadro 1). No nosso arquivo, especificamos que nosso MDB será um listener da fila “MundoJavaQueue”.
Agora precisamos criar nosso destino no JBOSS. Para isso devemos editar o arquivo chamado jbossmq-destinations-service.xml, localizado em jboss (seu diretório de instalação do JBOSS)\server\default\deploy\jms. Abaixo da tag <server> copie o seguinte trecho de código:
Quadro 1. Fila:
Uma fila é um repositório, um destino para onde as mensagens são enviadas. Pense na fila exatamente como uma fila de pessoas num caixa de banco. A primeira pessoa que chegou será a primeira à ser atendida. Demais pessoas que cheguem ao caixa entrarão na fila por ordem de chegada. Por essa razão, filas são conhecidas como estruturas de dados do tipo FIFO (First-in, First-out – primeiro a entrar, primeiro a sair). Trazendo esse conceito para o nosso exemplo, as mensagens enviadas para o destino “MundoJavaQueue” serão processadas por ordem de chegada. No mercado existem diversas soluções para gerenciamento de filas, como o MQ Series (IBM) e o MSMQ (Microsoft). No nosso exemplo iremos usar o JBossMQ, disponível com a distribuição oficial do JBoss.
Quadro 2. Criando um destino no JBOSS:
<mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=MundoJavaQueue">
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
Assim criamos no JBOSS o destino chamado MundoJavaQueue. Esse passo na verdade é opcional para o correto funcionamento do nosso teste, pois o JBOSS, caso não encontre o destino com o nome MundoJavaQueue especificado nos nossos descritores de distribuição no arquivo jbossmq-destinations-service.xml, irá criar um destino temporário com o mesmo nome.
Outros recursos configuráveis, como número de tentativas de ser consumir uma mensagem, máximo de objetos que deverão ficar no pool, política de conexões, etc, são específicos de cada servidor de aplicações, por isso sugiro uma pesquisa na documentação do seu fornecedor.
Empacotando a aplicação
Após a criação dos fontes e dos descritores de deployment, nossa estrutura de diretórios deverá ser a seguinte:
Quadro 3. Estrutura de diretórios:
META-INF\ejb.jar.xml
META-INF\jboss.xml
br\com\mundojava\MundoJavaMDBBean.class
Agora você pode usar sua IDE favorita para empacotar o bean (caso use, não esqueça de dar o nome de MundoJavaMDB ao jar) ou usar o comando:
jar cvf MundoJavaMDB.jar *
Para efetuar o deploy do nosso exemplo no JBOSS, basta copiar o arquivo .jar para o diretório jboss (seu diretório de instalação do JBOSS)\server\default\deploy.
Caso os passos anteriores tenham sido efetuados com sucesso, devemos ver uma mensagem parecida com essa no start do JBOSS:
Quadro 4. Mensagem no log do JBOSS:
17:13:54,111 INFO [EjbModule] Deploying MundoJavaMDB
17:13:54,551 INFO [EJBDeployer] Deployed: file:/C:/jboss/jboss-3.2.6/server/default/deploy/MundoJavaMdb.jar
Criando um aplicativo de teste
Agora desenvolveremos uma classe para testar nosso message driven. O código segue na listagem 4:
Listagem 4. MDBCliente.java
package br.com.mundojava;
import java.util.Properties;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
/**
* @author Alexandre
*
*/
public class MDBCliente {
public static void main(String[] args) {
try {
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.put(Context.PROVIDER_URL, "localhost:1099");
Context context = new InitialContext(props);
QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup( "java:/XAConnectionFactory" );
Queue queue = (Queue) context.lookup( "queue/MundoJavaQueue" );
QueueConnection queueConn = factory.createQueueConnection();
QueueSession session = queueConn.createQueueSession( false, Session.AUTO_ACKNOWLEDGE );
QueueSender sender = session.createSender( queue );
TextMessage message = session.createTextMessage( "Hello Mundo Java!" );
sender.send( message );
} catch (Throwable e) {
e.printStackTrace();
}
}
}
O cliente passo-a-passo:
Quadro 5. Obtendo o contexto inicial:
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.put(Context.PROVIDER_URL, "localhost:1099");
Context context = new InitialContext(props);
Aqui informamos qual é a classe de fábrica que provê contexto inicial (org.jnp.interfaces.NamingContextFactory) e o URL do serviço de provedor (localhost:1099)
Quadro 6. Obtendo o QueueConnectionFactory e o Queue:
QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup( "java:/XAConnectionFactory" );
Queue queue = (Queue) context.lookup( "queue/MundoJavaQueue" );
Obtemos aqui uma referência ao QueueConnectionFactory e ao Queue, através de JNDI.
Quadro 7. Criando um QueueConnection, um QueueSession, um QueueSender e o TextMessage:
QueueConnection queueConn queueConn = factory.createQueueConnection();
QueueSession session = queueConn.createQueueSession( false, Session.AUTO_ACKNOWLEDGE );
QueueSender sender = session.createSender( queue );
TextMessage message = session.createTextMessage( "Hello Mundo Java!" );
Criamos um QueueConnection através do objeto de fábrica de conexões, um QueueSession e um QueueSender para o envio da mensagem, e um TextMessage para enviarmos ao destino.
Quadro 8. Enviando a mensagem:
sender.send( message );
Usamos então o objeto QueueSender para enviar o TextMessage criado ao nosso destino.
Para rodar nosso cliente é necessário incluir no seu classpath as bibliotecas contidas no diretório client, dentro de jboss_home.
Pronto. Caso todos os passos tenham sido executados corretamente deveremos ver a seguinte mensagem no console do JBOSS:
Quadro 9. Mensagem:
12:15:49,102 INFO [STDOUT] Hello Mundo Java!
Para Saber Mais
http://www.onjava.com/pub/a/onjava/2001/05/22/ejb_msg.html
Tutorial sobre Message Driven Beans do site OnJava.
http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/MDB.html#74108
Tutorial da Sun sobre Message Driven Beans.
http://www.theserverside.com/articles/article.tss?l=Pramati-MDB
Tutorial TheServerSide sobre MDB’s.
Vantagens e Desvantagens da utilização de MDB
Ao se optar pela utilização dos Message Driven Beans no seu projeto, junto com os benefícios citados acima, como processamento assíncrono, desacoplamento, etc. você terá todo o poder e benefícios providos pelo container EJB, alguns listados abaixo:
• Load Balancing - balanceamento de carga entre diversos servidores;
• Fail-Over – Caso um dos servidores quebre, o controle será passado a outro de maneira transparente ao cliente;
• Transações – Uso de transações one-face e two-faces de forma declarativa;
• Clustering – Para um sistema robusto e tolerante a falhas;
• Threading – Acessos simultâneos, sincronização, deadlock, tudo relativo a threads passa a ser responsabilidade do container;
• Life Cicle Object – O container cria/destrói instâncias de acordo com a demanda;
• Segurança – A segurança de um MDB pode ser totalmente definida de forma declarativa;
Porém, gostaria de lembrar também que a utilização de MDB’s, assim como qualquer EJB, deve ser bem analisada, pois junto com os benefícios dos serviços oferecidos pelo container, vem também a necessidade de mais recursos de hardware, mão de obra especializada, etc. Em muitos casos, soluções “caseiras” resolvem o problema sem a necessidade do poder de um EJB.
Considerações Finais
Nosso objetivo ao escrever esse artigo foi dar uma visão geral sobre o funcionamento dos Message Driven Beans. Naturalmente, o assunto é muito extenso, e para um aprofundamento maior se faz necessário a leitura de livros e artigos especializados, e principalmente colocando a mão na massa, codificando. Assim você irá se deparar com as dificuldades de certa forma inerentes ao conhecimento de uma nova tecnologia, e aprender com as mesmas. Felizmente existe hoje uma gama considerável de material publicado sobre o tema, de forma que caso você precise usar os MDB’s no seu projeto irá encontrar muito material a respeito.
Referências
Budi Kurniawan, 2002. Java para a Web com Servlets, JSP e EJB – Ciência Moderna
Deepak Alur, John Crupi, Dan Malks, 2002. Core J2EE Patterns – Ed.. Campus
http://java.sun.com – Site oficial do Java
http://www.theserverside.com – TheServerSide, muitas informações sobre J2EE.
http://www.jboss.org – Site oficial do JBOSS
Autor
“Alexandre Bastos Borges” (alexandre@bankware.com.br) é Arquiteto de Sistemas da Bankware S/A, atuando com Java há mais de 5 anos. Possui as certificações oficiais SCJP e SCWCD
|