Concrete | API Java JMS – parte 1
API Java JMS – parte 1
por concrete
"Se é para me mandar mensagem, que seja com .class" um javeiro anônimo
"Se é para me mandar mensagem, que seja com .class"
um javeiro anônimo
JMS, a tentativa Java de integração via mensageria
Alguns dos principais benefícios de usar um MOM são poder trocar mensagens em modo assíncrono e obter uma arquitetura desacoplada. Vai bem em muitos casos de uso como por exemplo distribuir processamentos demorados em várias máquinas. Como disse em Introdução à mensageria, o JMS (Java Message Service) nasceu tentando resolver os problemas de interoperabilidade e de vendor lock-in por meio de uma API Java comum, que escondesse as peculiaridades de cada fornecedor de serviços de mensageria, isto é, um MOM.
A API JMS provê uma abstração para a interação entre quem troca mensagens com o message broker de modo similar como JDBC abstrai a comunicação com os bancos de dados relacionais. A primeira versão da especificação saiu em 1998 (e não em 2001 como falei na Introdução à mensageria) com a colaboração das principais empresas que atuavam neste segmento. Em 2002 saiu a versão 1.1 da API que é usada até hoje.
É bom beber direto da fonte. Faça o download da especificação 1.1 em https://www.oracle.com/technetwork/java/docs-136352.html e veja o Javadoc em https://docs.oracle.com/javaee/6/api/index.html?javax/jms/package-summary.html
Sempre achei o JMS uma das APIs mais felizes dentre as que foram criadas dentro da Sun. A Sun foi muito criticada pelo uso abusivo do desgastado termo "enterprise" nos tempos em que o mundo só queria saber de web. Porém JMS é uma API que pode e deve ser usada por qualquer tipo de aplicação.
Apesar das qualidades, deixou de lado algumas coisas importantes tais como criptografia de mensagens e mensagens assinadas digitalmente. E mesmo sendo uma API relativamente simples, nem todos os providers (MOMs) implementam todos os seus recursos.
Alguns conceitos básicos da API
– Producer – aplicação cliente que cria e envia mensagens
– Consumer – aplicação cliente que recebe e processa mensagens
– Provider – aplicação servidora que implementa as interfaces JMS. Exemplos: Apache ActiveMQ, JBoss HornetQ, Progress SonicMQ, etc.
– Domínios – estilos de mensagens suportados por JMS: ponto a ponto (Queue) e publish/subscribe (Topic)
– Objetos administrados – objetos JMS pré-configurados que contém configurações específicas de dados do provier usados pelos clientes. Estes objetos em geral são acessados via JNDI
Modos de mensagens
O modo mais comum de troca de mensagens entre 2 aplicações é o request-and-reply síncrono. Era assim que funcionava o antigo conceito cliente/servidor e é assim que nós acessamos as páginas da web. Usando JMS é possível trocar mensagens deste modo mas o poder está na possibilidade de trocar mensagens assíncronas quando se registra um "listener" para saber quando chegam novas mensagens. São como os modos descritos abaixo:
Publish-and-subscribe (pub/sub)
O producer no domínio Topic (aqui chamado de publisher), cria e envia a mensagem para um tópico. O provider mantém a mensagem no Topic o tempo suficiente para ser recebida por quem subscreveu. Ver figura em Introdução à mensageria. Reparem que ao contrário do AMQP do RabbitMQ, o JMS permite o chamado multicast, isto é, enviar a mesma mensagem a muitos consumidores.
O subscritor recebe a mensagem no momento em que se conectar ao servidor, mesmo que tempos depois da chegada da mensagem ao tópico mas é claro não recebe mensagens anteriores ao momento da sua subscrição.
Os subscritores são anônimos, em outras palavras, o publisher não sabe para quem está mandando. Aliás nem sabe se há alguém subscrito. Mas é possível combinar com respostas do consumidor no modo request-and-reply permitindo uma espécie de confirmação de recebimento.
Ponto a ponto ou modo de Filas (Queues)
No domínio Queue, as mensagens seguem o tipo store-and-forward, ver Apoio à introdução à mensageria. Os produtores enviam mensagens para filas normalmente definidas no MOM. Cada consumidor vai retirando uma mensagem da fila. Cada mensagem vai para um único consumidor (como faz o RabbitMQ).
A fila funciona no modo FIFO mas é o provider JMS quem controla qual consumidor retira a mensagem da fila. Alguns providers fazem uma espécie de load balancing entre os consumidores mas pode existir outro que pode ficar enviando as mensagens ao mesmo consumidor até que ele pare de escutar.
Este modo é muito confiável porque as mensagens são armazenadas de modo a sobreviver ao ciclo de vida das aplicações que as enviam ou as recebem. Os providers JMS costumam permitir configurar propriedades da base de dados com as mensagens.
E este modo é ideal quando a conexão não é garantida ou não existe o tempo todo. Um exemplo seria um vendedor viajante que só consegue conexão à noite no hotel.
O consumidor pode enviar uma confirmação do recebimento.
A API JMS
A API JMS é composta de Interfaces. O livro Java Messaging de autoria do Eric Bruno resume a API em um diagrama UML:
Passos para criar e iniciar a conexão:
1. Usar lookup para obter uma ConnectionFactory (localizadas no provider via JNDI)
2. Obter uma Connection usando CreateConnection() de ConnectionFactory
3. Criar a Session que permitirá criar objetos Message
4. Usar lookup de novo desta vez para obter o objeto administrado Destination
5. Criar instância de MessageProducer (ou de MessageConsumer se for o caso) usando a Session e o objeto Destination
6. Iniciar Connection com start()
Exemplo de criação de um objeto MessageProducer em uma Session desde a criação da Connection seguindo os passos acima. Supõe que exista um arquivo jndi.properties no classpath:
[sourcecode language="java" gutter="false"]
ConnectionFactory conFactory =
(ConnectionFactory) jndi.lookup("ConnectionFactory");
Connection connection = conFactory.createConnection("", "");
false, // sem transações
Topic destino = (Topic) jndi.lookup("ObjetoDestinationValido");
MessageProducer producer = session.createProducer(destino);
Não aparece no exemplo acima mas MessageProducer possui métodos não só para enviar mensagens como também para configurar os headers da mensagem.
Todas as mensagens são compostas de 3 partes:
Header: informações úteis para o provider JMS e ao cliente para identificar e rotear as mensagens. Todas as mensagens contém o mesmo conjunto de campos header.
Properties: são campos adicionados ao header com o formato chave-valor. Podem ser propriedades específicas definidas pela aplicação que na verdade são headers customizados opcionais, propriedades opcionais definidas pela especificação JMS para todas as mensagens e propriedades definidas e usadas pelo provider JMS. Podem ser usados para filtrar mensagens, fazer algum roteamento especial ou para outra razão qualquer
Body: é o corpo da mensagem também conhecido como payload.
Corpo das mensagens
Não há mensagens específicas para os domínios Topic e Queue. Elas são separadas por conteúdo:
Com os diferentes tipos de mensagens suportados por JMS fica fácil escrever aplicações que troquem objetos Java, dados XML, texto puro e até se comunicar com outras linguagens usando um fluxo de bytes.
Exemplos de como o producer cria e envia mensagens:
String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<cotacao><ativo>PETR4</ativo><preco>20.00</preco></cotacao>";
TextMessage txt = session.createTextMessage();
txt.setText(xml);
producer.send( txt );
MapMessage mapMsg = session.createMapMessage();
mapMsg.setString("ativo", "PETR4");
mapMsg.setDouble("preco", 20.00);
producer.send( mapMsg );
Exemplo de mensagem recebida pelo consumer:
Um MessageConsumer pode consumir mensagens de forma síncrona usando um dos seus métodos receive() ou de modo assíncrono implementando MessageListener em que o método onMessage() é chamado quando chega uma mensagem nova no Destination.
Por usar a forma assíncrona, a classe cujo trecho aparece abaixo implementa MessageListener. Na listagem só aparece o método onMessage(). Já fez os passos 1 a 6 de criar e iniciar uma conexão e usou o método setMessageListener de MessageConsumer para passar a referência ao objeto MessageListener:
public void onMessage (Message message) {
MapMessage mapMsg = (MapMessage) message;
String ativo = mapMsg.getString("ativo");
Double preco = mapMsg.getDouble("preco");
Acredito que tudo ficará mais claro quando mostrarmos exemplos completos. Isto será feito em um próximo artigo desta série.
Os Message Driven Beans foram introduzidos no (perdão pela má palavra) EJB 2.0 para suportar processamento de mensagens assíncronas usando um provider JMS. A versão EJB 2.1 expandiu a definição de modo a suportar qualquer sistema de mensagens tal como JCA. Os MDBs também fazem parte da versão 3.0 do EJB (na verdade uma especificação quase toda nova e diferente que não sei porque manteve o nome EJB).
Os MDBs são componentes server-side sem estado, conscientes de que são partes de transações e criados para processar mensagens assíncronas. Enquanto os componentes são responsáveis pelo processamento das mensagens, o seu ambiente container trata das transações, segurança, concorrência, recursos e ACK das mensagens. Como vimos no uso direto de JMS, nosso programa tem que cuidar da concorrência, dos problemas do ambiente multithread, dos recursos, das transações e da segurança. Se a gente usa um servidor que já é um container EJB, então é melhor programar usando os MDBs.
Para quem conhece os conceitos de JMS, programar os MDBs é bem fácil. Os nomes tem a ver. Provavelmente só terá que aprender a usar algumas anotações que substituem uma ou mais linhas de código. E como em todo EJB, terá que entender seu ciclo de vida que aliás só tem 2 estados: "não existe" e "Method-Ready Pool" (similar ao instance pool dos stateless session beans).
Isso é tudo que pretendo falar de Message Driven Beans nesta série. Caso você precise usar JMS e sua aplicação já rode em um container EJB, tal como o JBoss por exemplo, é natural que escreva seu código com MDBs. Meu intuito é falar de conceitos de mensageria, então vou me contentar com as aplicações stand-alone que não precisam de container JEE.
Palavras finais deste primeiro artigo sobre a API Java JMS
Dentro do assunto geral mensageria, este é o primeiro artigo de uma série sobre JMS. Ainda faltam muitos conceitos como headers e properties das mensagens, tópicos hierárquicos, seletores que permitam filtrar o consumo de mensagens, subscrições duráveis, confiabilidade das mensagens, ACK das mensagens, etc.
Fico devendo também mostrar exemplos rodando nos providers JMS Open Source mais famosos como o ActiveMQ e o HornetQ, sobre os quais também pretendo falar um pouquinho.