JMS com Spring

Bruno Braga on July 22nd, 2008

Eu estava viajando, então estive ausente do PC por alguns dias :P
De volta agora é hora de colocar o assunto em dia.

Em dois posts anteriores eu falei algo sobre JMS. Um que tratava da integração do Oracle com Java e outro da comunicação do Websphere MQ via JMS. Então para completar o assunto vou mostrar na prática como enviar e receber mensagens usando JMS e Spring.
Provavelmente esse post vai ser melhor aproveitado por quem já conhece ou utiliza o Spring, mas como é um framework popular é fácil encontrar referencias sobre suas configurações na web.

Connection Factory:

Nosso objetivo é enviar mensagens via JMS para o Websphere MQ (MQSeries), dando seqüencia ao post Websphere MQ e JMS. Para isso vamos usar o Connection Factory da IBM com.ibm.mq.jms.MQQueueConnectionFactory para gerenciar a conexão. Segue abaixo o trecho de configuração disso no Spring (arquivo applicationContext.xml):

<bean id="jmsQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
	<property name="hostName">
		<value>${mq.hostName}</value> 
	</property>
	<property name="queueManager">
		<value>${mq.queueManager}</value>
	</property>
	<property name="channel">
		<value>${mq.channel}</value>
	</property>
	<property name="port">
		<value>${mq.port}</value>
	</property>
	<property name="transportType">
		<value>1</value>
	</property>
</bean>

Esse transportType é referente ao tipo de comunicação utilizado. O valor 1 é referente a constante  com.ibm.mq.jms.JMSC.MQJMS_TP_CLIENT_MQ_TCPIP que diz que a comunicação é via TCP IP.

Configuração:

As demais configurações do Spring para JMS são simples, e similares a isso:

<bean id="jmsSpringConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory102">
	<property name="targetConnectionFactory">
		<ref local="jmsQueueConnectionFactory" />
	</property>
</bean>
		
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate102">
	<property name="connectionFactory" ref="jmsSpringConnectionFactory"/>
	<property name="pubSubDomain" value="false"/> <!-- false seta para queue (point to point) -->
	<property name="receiveTimeout" value="500"/>
</bean>

Utilização:

Para usar é muito simples, basta instanciar o Spring e depois enviar e receber mensagem com praticamente uma linha de código.

Instanciando o Spring:

BeanFactoryLocator bfs = SingletonBeanFactoryLocator.getInstance("applicationContext.xml");
BeanFactoryReference bf = bfs.useBeanFactory("mqSpring");

O Spring irá instanciar as classes configuradas nos beans, e a partir dai é só usar.

Enviando mensagem:

JmsTemplate jmsTemplate = (JmsTemplate) bf.getFactory().getBean("jmsQueueTemplate");
jmsTemplate.convertAndSend("fila", "mensagem");

Recebendo mensagem:

JmsTemplate jmsTemplate = (JmsTemplate) bf.getFactory().getBean("jmsQueueTemplate");
TextMessage textMessage = (TextMessage) jmsTemplate.receive("fila");

Listener:

Uma outra opção para receber mensagens do MQ é criar um listener que fica escutando uma determinada fila e irá receber automaticamente novas mensagens.

<bean id="messageListener" class="br.com.globalvalue.exemplomq.ExemploListener" />
	
<bean id="jmsListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
	<property name="connectionFactory" ref="jmsSpringConnectionFactory"/>
	<property name="messageListener" ref="messageListener" />
	<property name="destinationName"><value>${mq.queue.inbox}</value></property>
</bean>
public class ExemploListener implements MessageListener {
	public void onMessage(Message message) {
		if (message instanceof TextMessage) {
			try {
				System.out.println(((TextMessage) message).getText());
			}
			catch (JMSException ex) {
				throw new RuntimeException(ex);
			}
		}
		else {
			throw new IllegalArgumentException("Message must be of type TextMessage");
		}
	}
}

Para executar o código é necessário os jar`s do Spring (e dependencias) e os jar`s da IBM para o Websphere MQ. Espero que o nível de detalhes tenha sido suficientes. Vimos que se o projeto utiliza Spring é fácil enviar e receber mensagens utilizando JMS, mesmo com classes (Factory) de terceiros como no caso do nosso exemplo para Websphere MQ.

Subscribe to this blog's RSS feed

Websphere MQ e JMS

Bruno Braga on July 4th, 2008

Existem várias formas de realizar comunicação entre aplicações. Entre elas estão: troca de arquivos,  compartilhamento de banco de dados, chamada de métodos remotos (RMI, SOAP) e mensageria.

O MQ (Message Queue) é um padrão para mensageria adotado por várias empresas, entre elas a IBM que possui o produto Websphere MQ (antigamente chamado de MQSeries).

Um dos pontos fortes do MQ se comparado com outras tecnologias é a troca de mensagens assíncronas e garantia de entrega. Ou seja: conseguimos enviar uma mensagem para a fila de um aplicativo sem que este aplicativo esteja no ar e sem ficar parado esperando uma resposta (comunicação assíncrona). Temos ainda a certeza de que se a mensagem saiu da fila é porque ela foi lida (entregue).
Uma comunicação como esta é muito mais segura do que um envio de e-mail, troca de arquivos ou transferências de dados similares.

Um detalhe importante é que apesar da comunicação ser assíncrona, é possível utilizá-la de forma online – onde enviamos e recebemos a resposta em “tempo real”. Vou explicar neste post como isso funciona. Alias, já vi implementações que tentam dar essa aparência de comunicação online realizando pooling (loop) nas filas do MQ para verificar se existem novas mensagens. Mas isso não é o procedimento correto. O MQ permite que a aplicação assine uma fila e receba novas mensagens automaticamente sem perder tempo no pooling ou degradar o ambiente.

No caso do Websphere MQ podemos realizar a comunicação de duas maneiras: a primeira é utilizando o Websphere MQ Client que possui uma API para programação em C, Cobol, CPlus, .Net, Java e VB 6. E outra forma bastante útil para o pessoal de Java é utilizando JMS (Java Message Service). O produto Websphere MQ suporta JMS, e neste caso não é necessário instalar ou utilizar o Websphere MQ Client.

Como este é um blog sobre Java vamos citar alguns detalhes da comunicação usando JMS.

Olhando a arquitetura do JMS existem maneiras diferentes de realizar a interação com uma fila (queue).
A primeira é uma comunicação ponto a ponto onde a aplicação 1 envia mensagem para uma fila e somente a aplicação 2 irá ler. Vejamos a imagem abaixo:

Neste modo ponto a ponto, quando a aplicação 2 ler a mensagem via JMS o próprio protocolo irá enviar um acknowledge para a fila (comportamento padrão) e a mensagem será apagada pelo MQ (leitura ocorreu com sucesso).

Se pararmos para pensar esse modo não funcionará caso seja necessário enviar a mesma mensagem para mais de uma aplicação. Outro problema de partir do cliente a requisição de mensagens é que ele teoricamente tem que fazer pooling, verificando de tempos em tempos se existem novas mensagens na fila.

Se for necessário contornar essas limitações existe uma outra forma de comunicação, que é a Publish and Subscribe. Onde vários aplicativos podem assinar uma vila e receber as mensagens quando chegarem. A mensagem só será apagada da fila quando todos receberem.

Um detalhe interessante é que as mensagens não precisam ser necessariamente do tipo texto. O JMS suporta os seguintes tipos de dados: text, map, bytes, stream, e object. O stream é uma boa opção para mensagens muito grandes (enquanto uma ponta está enviando a mensagem a outra já pode ir recebendo), enquanto o tipo object está mais próximo de linguagens orientadas a objeto.

Bom, basicamente é assim que funciona. Nós próximos posts vou tentar mostrar alguns passos de como escrever uma aplicação que envie e receba mensagens via JMS.

Oracle vs Java

Bruno Braga on July 1st, 2008

Atualmente estou atuando em um projeto onde é necessário a integração do Oracle com Java. Ou seja: é necessário executar código Java dentro do Oracle para uma determinada funcionalidade.

Esse projeto faz parte de um treinamento/consultoria sobre Websphere MQ (antigamente chamado de MQSeries) para um cliente, e vai ser o primeiro tema / “puxão de orelha” depois da reformulação do blog.

Puxão de orelha porque alguns itens referentes a integração do Oracle com Java não são legais. Mas para entender o problema e o funcionamento da integração vamos a um rápido cenário do aplicativo:

Além do treinamento de Websphere MQ o cliente solicitou que fosse desenvolvido um conector MQ em Java para posteriormente ser utilizado pelo sistema deles. Então optamos por desenvolver o conector MQ usando JMS e abstrair toda a complexidade da comunicação. Ele iria enviar mensagens com um comando e receber mensagens com um comando a partir do aplicativo Java. Até esse ponto estava excelente.

A dificuldade começou quando o cliente solicitou que o Conector MQ fosse executado dentro do Oracle através de uma trigger já que o Oracle suportava Java. Realmente o Oracle suporta Java, mas não tão bem como era esperado.
Somente para deixar claro: classes Java simples (recursos nativos da JVM) rodam muito bem no Oracle, isso foi um ponto bem positivo. O problema é executar uma aplicação ou conector que tem vários jars como API.

Objetivamente seguem detalhes sobre pontos problemáticos do Oracle versão 10.2:

  • ele não aceita jar’s externos. Todos os jar’s devem ser carregados para o banco usando o comando loadjava.
  • o comando loadjava carrega as classes do jar mas deixa todas com status INVALID !?!?… a maneria de resolver isso é usar o comando loadjava com o parâmetro “-resolve”.
  • o problema do parâmetro “-resolve” é que ele tenta “re-compilar” todas as classes do jar (sendo que um jar já é algo pronto para utilizar). Para cada classe o Oracle solicita as dependências (classes que estão no import). Então para incluir um jar de um driver do MQ preciso de N outros jars para satisfazer as dependências de compilação;
  • uma forma de resolver o problema acima podemos até utilizar o parâmetro “-genmissing” do comando loadjava, ele vai gerar uma classe fake para cada dependência e evita problemas de compilação, mas é obvio que se a classe fake for usada em qualquer ponto do processo (direta ou indiretamente) vai dar erro (ORA-29532: Java call terminated by uncaught Java exception: java.lang.NoClassDefFoundError:
    !!!ERROR!!! generated by genmissing) – então o genmissing não é muito útil, somos obrigados a ter as dependências para compilar / resolver quase tudo usando o “-resolve”;
  • o Oracle carrega para dentro do banco a classe que queremos importar + todos os jars necessários para execução + todas as dependências. Então temos um excesso de classes desnecessariamente;

Por causa dessas limitações a integração do Oracle com Java não é tão transparente. Não acredito que isso tenha um tratamento melhor no Oracle 11. Apesar do banco Oracle não ser um Application Server, poderia pelo menos seguir a arquitetura Java e ler as libs de um CLASSPATH. A parte de ter que carregar os jar para dentro do Oracle e re-compilar matou parte do suporte a Java do banco. Esse trabalho todo não vale a pena :)

Então esse foi o puxão de orelha, e todos esses problemas foram suportados pelo suporte oficial Oracle (http://metalink.oracle.com).

Mas em tempo, o SQLJ é bastante poderoso: podemos escrever uma classe Java (usando recursos nativos) e em determinados pontos utilizar #sql() (querys) com váriaveis Java. Então considerando somente esta necessidade a utilização é válida.