Chat JMS com ActiveMQ
Algum tempo atrás postei no blog um tutorial sobre como criar um chat muilti usuários com a utilização da classe Socket
e o uso de threads, agora irei mostrar uma outra forma de criar um chat multi usuários, sem a utilização de Sockets e nem de threads e sim utilizando a API JMS.
Java Message Service, ou simplesmente JMS, é uma API da linguagem Java para middleware orientado a mensagens. atraves da API JMS, uma ou mais aplicações podem se comunicar trocando mensagens atraves de um provider. O provider é um servidor que gerencia e armazena essas mensagens.
Vários servidores Java como, JOnAS, GlassFish, JBoss , entre outros, possuem suporte a JMS. Mas existe também alguns servidores JMS que nao estão vinculados a um servidor web, entre eles temos o Joram, OpenJMS e o ActiveMQ, o qual sera o foco deste artigo para a criação do chat atraves do conceito de topicos.
1. Arquitetura JMS
A arquitetura JMS disponibiliza dois tipos de troca de mensagens, o Queue (modelo ponto a ponto ou modelo de filas) e o modelo Topic ou Publish/Subscribe.
Com o modelo de troca de mensagens por filas, poderíamos ter um ou vários produtores enviando mensagens para uma mesma fila, mas teríamos apenas um único consumidor. Neste modelo, cada vez que uma mensagem é consumida, ela deixa de existir na fila, por isso, podemos ter apenas um único consumidor.
O modelo Topic, permite uma situação em que teríamos de um a vários consumidores para o mesmo topico. Funciona como um sistema de assinaturas, cada cliente assina um topico e a mensagem fica armazenada no servidor até todos assinantes a tenham consumido. Este sera o modelo usado neste artigo.
2. Elementos da API JMS
- Cliente JMS: Uma aplicação ou objeto Java que produz (Programas Java que criam e enviam mensagens) e que consome (Programas Java que recebem mensagens) as mensagens. As mensagens são objetos que contém os dados que são transferidos entre os clientes JMS. Essas transferências são feitas a partir de um Provedor JMS.
- conexao JMS: A partir do momento que uma factory é obtida, conexões para o provedor JMS podem ser criadas. Uma conexao representa a ligação entre a aplicação cliente e a aplicação servidora. Dependendo do tipo da conexao, ela permitirá que os clientes criem sessões para o envio e recebimento de mensagens de filas ou topicos.
- Factory de conexões JMS: É um objeto administrado que a aplicação cliente utiliza para a criação de conexões para o provedor JMS. Normalmente o cliente obtém as factories atraves de interfaces portável, desta forma, mesmo se as configurações do provedor JMS mudar, o código do cliente permanece inalterado. Os administradores mantêm as configurações em objetos (objetos da classe factory), que são obtidos atraves de buscas (lookup) na JNDI. Dependendo do tipo da mensagem, o cliente obterá uma factory para topico ou para fila.
- Destino: É um objeto administrado que encapsula a identidade do destino das mensagens, que é para onde as mensagens são enviadas e consumidas, são uma fila ou um topico. O administrador JMS cria estes objetos, e os usuários os obtém atraves de buscas na JNDI. Da mesma forma que as factories de conexões, o administrador pode criar dois tipos de classe de destino, fila e topico. O destino recebe um nome, que é pelo qual o produtor e o consumidor se comunicam com o provedor.
- Consumidor: Um objeto criado atraves de uma sessao JMS. Ele recebe mensagens de um destino. O consumidor pode receber mensagens de maneira síncrona ou assíncrona de filas ou topicos.
- Produtor: Um objeto criado atraves de uma sessao JMS. Ele envia mensagens para um destino.
-
Mensagens: São objetos enviados entre consumidores e produtores de mensagens. Eles contêm um objeto que encapsula os dados que serão trafegados pelas mensagens. As mensagens são pacotes independentes de dados de negócios. Uma mensagem possui três partes principais:
- Cabeçalho (Contém informações de roteamento de rede e identificadores de mensagens), propriedades (Contém metadados para a mensagem. O JMS dita algumas das propriedades, mas programadores também podem incluir suas próprias propriedades) e carga útil (Contém os verdadeiros dados do negócio. A carga útil é totalmente controlada pelo programador).
-
Corpo da mensagem pode conter seis tipos de mensagens:
TextMessage
(padrão para textos e xml),ObjectMessage
,ByteMessage
,MapMessage
,StreamMessage
. Todos esses tipos são subtipos da interfacejavax.jms.Message
, mensagem genérica sem corpo, que contém apenas informações como cabeçalho e propriedades. - A interface de uma mensagem é extremamente flexível e permite várias formas de customização de conteúdo.
- Provedor JMS: Representa uma interface para um software de middleware orientado à mensagens. Ele suporta a interface JMS que é especificada pela Sun MicroSystems, recentemente adquirida pela Oracle. Ele é basicamente um adaptador de um middleware.
3. Apache ActiveMQ
O ActiveMQ é um provedor open source(licence Apache 2.0). O ActiveMQ pode ser utilizado por linguagens como Java, .NET, C/C++, Delphi, Perl, entre outras, atraves do chamado “Cross Language Clients”.
Para obter o ActiveMQ devemos acessar o endereço http://activemq.apache.org/activemq-542-release.html e fazer o download da versão apache-activemq-5.4.2-bin.zip. apos baixar o arquivo, vamos descompactá-lo no diretório “c:\”. Para nosso projeto vamos precisar adicionar a biblioteca activemq-all-5.4.2.jar, encontrada no diretório raiz do pacote.
Para ativar o ActiveMQ, entre no diretório bin e execute o arquivo activemq.bat. nao é necessária nenhuma alteração em configuração do ActiveMQ para executar o exemplo deste artigo, você deve ter apenas o Java nas variáveis de ambiente do Windows.
Você pode fazer um teste pelo browser, procure no log lançado no console por uma linha tipo esta: INFO | ActiveMQ Console at http://0.0.0.0:8161/admin
. O importante aqui é o número da porta, se a sua nao for 8161
, nao tem problema algum. Digite no browser este endereço e substitua os zeros por localhost
, ficando assim: http://localhost:8161/admin
, e caso sua porta seja outra, também substitua o número dela. Você deverá ter uma página como a da figura 2.
4. Entendendo o JMS
Para escrever aplicações que enviam ou recebem mensagens é preciso realizar uma serie de etapas:
- Obter um destino e uma fábrica de conexões via JNDI
- Usar a fábrica para obter uma conexao
- Usar a conexao para obter uma ou mais sessões
- Usar a sessao para criar uma mensagem
- Iniciar a sessao
- Enviar e/ou receber mensagens
- Cadastrar ouvintes para receber mensagens
Um topico JMS pode ser de dois tipos, durável e nao durável. Quando um topico é configurado como durável o assinante nao precisa estar conectado no momento em que o produtor envia a mensagem, ela permanece no provedor até que o assinante (consumidor) esteja ativo e faça o consumo dela. O topico nao durável o assinante deve estar ativo para receber a mensagem, caso nao esteja a mensagem nao sera entregue.
Cada topico pode ter vários assinantes, e para um assinante receber as mensagens enviadas para o topico, eles já devem ser assinantes antes da mensagem ser enviada. Isto significa que se dois assinantes receberem a mensagem “Java com JMS”, e um terceiro assinante se inscrever no topico apos o envio desta mensagem, este terceiro assinante nao irá receber a mensagem.
O contexto utilizado para a conexao JMS é feito atraves de uma conexao JNDI que pode ser efetuada programaticamente ou por um arquivo de configurações do tipo properties. Os elementos desta configuração são o endereço e a porta do provedor, o nome da fabrica de conexao, o nome do topico e a classe JNDI de criação do objeto do contexto. Essa classe nao é uma classe padrão para qualquer provedor, cada um possui a sua própria classe como pode diferenciar também a maneira de configurar as demais propriedades.
5. Configurando um topico
Antes de tudo precisamos configurar um topico no provedor, para isso, inicie o provedor como mostrado na sessao 3 e acesse a url do administrador.
Quando a página abrir, como a figura 2, clique em Topics e no campo TopicName insira topicChat
e clique em create. Você verá em seguida na tabela Topics, que sera criado um topico com este nome.
Feito isto, vamos criar o arquivo jndi.properties para depois adicioná-lo no classpath da aplicação, conforme listagem 1.
#classe jndi para o ActiveMQ java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory #url para conexao com o provedor java.naming.provider.url = tcp://localhost:61616 #nome da conexao connectionFactoryNames = TopicCF #nome do topico jms topic.topicChat = topicChat
6. Classe Chat
Crie a classe Chat
conforme a listagem 1. nao se esqueça de adicionar ao projeto a biblioteca activemq-all-5.4.2.jar, ela contém todas as classes necessárias para a criação dos serviços JMS.
Os comentários estão linha a linha no código, para indicar o que cada linha faz durante o processo de criação do chat.
package br.mb.tutorialActiveMQ.chat; import javax.jms.JMSException; import javax.jms.TopicConnection; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.jms.TopicConnectionFactory; import javax.jms.Session; import javax.jms.Topic; import javax.jms.TopicSubscriber; import javax.jms.Message; import javax.jms.TextMessage; import javax.jms.MessageListener; import javax.naming.InitialContext; import java.util.Scanner; public class Chat implements MessageListener { private TopicSession pubSession; private TopicPublisher publisher; private TopicConnection connection; private String username; /* Construtor usado para inicializar o cliente JMS do Chat */ public Chat(String topicFactory, String topicName, String username) throws Exception { // Obtem os dados da conexao JNDI atraves // do arquivo jndi.properties InitialContext ctx = new InitialContext(); // O cliente utiliza o TopicConnectionFactory // para criar um objeto do tipo // TopicConnection com o provedor JMS TopicConnectionFactory conFactory = (TopicConnectionFactory) ctx.lookup(topicFactory); // Utiliza o TopicConnectionFactory para criar //a conexao com o provedor JMS TopicConnection connection = conFactory.createTopicConnection(); // Utiliza o TopicConnection para criar a sessao para o produtor // Atributo false -> uso ou nao de transacoes (tratar uma serie de // envios/recebimentos como unidade atomica e controla-la via commit e rollback) // Atributo AUTO_ACKNOWLEDGE -> Exige confirmacao automatica apos recebimento correto TopicSession pubSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); // Utiliza o TopicConnection para criar a sessao para o consumidor TopicSession subSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); // Pesquisa o destino do topico via JNDI Topic chatTopic = (Topic) ctx.lookup(topicName); // Cria o topico JMS do produtor das mensagens // atraves da sessao e o nome do topico TopicPublisher publisher = pubSession.createPublisher(chatTopic); // Cria (Assina) o topico JMS do consumidor das // mensagens atraves da sessao e o nome do topico TopicSubscriber subscriber = subSession.createSubscriber(chatTopic); // Escuta o topico para receber as mensagens // atraves do metodo onMessage() subscriber.setMessageListener(this); // Inicializa as variaveis do Chat this.connection = connection; this.pubSession = pubSession; this.publisher = publisher; this.username = username; // Inicia a conexao JMS, permite que // mensagens sejam entregues connection.start(); } // Recebe as mensagens do topico assinado public void onMessage(Message message) { try { // As mensagens criadas como TextMessage // e devem ser recebidas como TextMessage TextMessage textMessage = (TextMessage) message; System.out.println(textMessage.getText()); } catch (JMSException jmse) { jmse.printStackTrace(); } } // Cria a mensagem de texto e a publica no topico. Evento referente ao produtor public void writeMessage(String text) throws JMSException { // Recebe um objeto da sessao para criar // uma mensagem do tipo TextMessage TextMessage message = pubSession.createTextMessage(); // Seta no objeto a mensagem que sera enviada message.setText(username + ": " + text); // Publica a mensagem no topico publisher.publish(message); } // Fecha a conexao JMS public void close() throws JMSException { connection.close(); } // Roda o Chat public static void main(String[] args) { try { //Habilita o envio de mensagens por linha de comando Scanner commandLine = new Scanner(System.in); System.out.print("Digite seu nome: "); String name = commandLine.nextLine(); // Faz uma chamada ao construtor da classe para iniciar o chat Chat chat = new Chat("TopicCF", "topicChat", name); // Depois da conexao criada, // faz um loop para enviar mensagens while (true) { //captura a mensagem digitada no console String s = commandLine.nextLine(); //para sair digite exit if (s.equalsIgnoreCase("exit")) { //fecha a conexao com o provedor chat.close(); //sai do sistema System.exit(0); } else { //envia a mensagem digitada no // console para o metodo writeMessage que vai publica-la chat.writeMessage(s); } } } catch (Exception e) { e.printStackTrace(); } } }
nao se esqueça de adicionar no projeto, dentro do diretório src
(root dos fontes), o arquivo jndi.properties
para ser possível a conexao com o provedor. Quando quiser para o provedor utilize “ctrl+c
” e na próxima pergunta confirme.
Conclusão
Este artigo exemplificou como instalar e configurar um provedor do tipo ActiveMQ utilizando a API JMS. Foi criado um chat com os recursos do tipo Topic, onde a mesma aplicação envia e recebe mensagens. O exemplo do chat foi retirado da referência O’REILLY com algumas poucas modificações.
Referências
- O’REILLY, Java Message Service, Second Edition by Mark Richards, Richard Monson-Haefel, and David A. Chappell.
- ORACLE, Java Naming and Directory Interface(JNDI) disponível em http://www.oracle.com/technetwork/java/jndi/index.html
- ORACLE, Java Message Service(JMS) disponível em http://www.oracle.com/technetwork/java/jms/index.html