Using AWS SQS as JMS provider with Spring

Recently AWS published a new client library that implements the JMS 1.1 specification and uses their Simple Queue Service (SQS) as the JMS provider (see Jeff Barr’s post here). In my post I will show you how to set up your Maven project to use the Spring Framework to use this library.
We will perform the following steps:

  • Create the queue in the AWS Management Console
  • Set up your AWS credentials on your machine
  • Set up your Maven project
  • Create the Spring configuration
  • Create the Java files to produce and receive messages

This post will only show some basic use of the SQS possibilities but should be good enough to get you started. I assume you have already created your AWS Account and you are familiair with Maven and basic Spring setup.


Create the queue in the AWS Management Console

First step is to create the queue so we can use it in our program. I show you how to create the queue with the Management Console but you can also create the necessary queues programmatically.
When you go to the Management Console you can select the SQS page and click the ‘Create New Queue’ button:
Screenshot at Mar 28 20-10-00
Enter the name of the queue and accept the default settings for now by clicking the ‘Create Queue’ button:
Screenshot at Mar 28 20-10-59

Set up your AWS credentials on your machine

To be able to access your AWS stuff with the Java SDK the easiest way is to create a ‘credentials.proeprties’ file in your users home directory. Create your credentials file at ~/.aws/credentials (C:\Users\USER_NAME\.aws\credentials for Windows users) and save the following lines after replacing the capitalized values with your own.

[default]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY


Set up your Maven project

When using Maven to setup your project you can add the following dependencies to your pom to be able to use AWS SQS with Spring 4:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.pascalalma.aws</groupId>
    <artifactId>sqs-client</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <version.spring>4.1.5.RELEASE</version.spring>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${version.spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${version.spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${version.spring}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>amazon-sqs-java-messaging-lib</artifactId>
            <version>1.0.0</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</project>


Create the Spring configuration

In the first example I will use a simple MessageProducer and MessageConsumer class, in which the producer will put a message on the queue and the consumer will read one message from the queue. This is model is reffered to by AWS as a ‘synchronously’ call. The Spring config looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="net.pascalalma.aws.sqs"></context:component-scan>

    <bean id="credentialsProviderBean" class="com.amazonaws.auth.DefaultAWSCredentialsProviderChain"/>

    <bean id="connectionFactoryBuilder" class="com.amazon.sqs.javamessaging.SQSConnectionFactory$Builder">
        <property name="regionName" value="eu-west-1"/>
        <property name="numberOfMessagesToPrefetch" value="5"/>
        <property name="awsCredentialsProvider" ref="credentialsProviderBean"/>
    </bean>

    <bean id="connectionFactory" class="com.amazon.sqs.javamessaging.SQSConnectionFactory"
          factory-bean="connectionFactoryBuilder"
          factory-method="build"/>

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="defaultDestinationName" ref="queueName"/>
    </bean>

    <bean id="queueName" class="java.lang.String">
        <constructor-arg value="DefaultDemoQueue"/>
    </bean>
</beans>


Create the Java files to produce and receive messages

The last step is to create the necessary Java files. I think they are simple enough and self-explaining so I just show the source code here. First we have the MessageProducer that puts a message on the queue when it is executed:

package net.pascalalma.aws.sqs;

import org.apache.log4j.Logger;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;

@Service("myMessageProviderService")
public class MyMessageProvider {

    final static Logger logger = Logger.getLogger(MyMessageProvider.class);

    @Resource(name = "jmsTemplate")
    private JmsTemplate jmsTemplate;

    public void sendMessage(final String txt) {
        logger.debug(String.format("Sending message with txt: %s", txt));
        jmsTemplate.send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                final TextMessage msg = session.createTextMessage(txt);
                return msg;
            }
        });
        logger.debug("Message sent ");
    }
}

Next is the MessageConsumer which in this example just reads one message from the queue when it is executed:

package net.pascalalma.aws.sqs;

import org.apache.log4j.Logger;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.TextMessage;

@Service("myMessageConsumerService")
public class MyMessageConsumer {

    final static Logger logger = Logger.getLogger(MyMessageConsumer.class);

    @Resource(name = "jmsTemplate")
    private JmsTemplate jmsTemplate;

    public void readMessage() throws JMSException {
        logger.debug("Reading message");
        Message msg = jmsTemplate.receive();

        if (msg instanceof TextMessage) {
            TextMessage txtmsg = (TextMessage) msg;
            logger.info(String.format("Received text: %s", txtmsg.getText()));

        }
        logger.debug(msg.getClass());
        logger.info("Done");
    }
}

Finally there is the Main class that reads the Spring config and runs the Producer en Consumer:

package net.pascalalma.aws.sqs;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.jms.JMSException;

public class SpringMain {

    final static Logger logger = Logger.getLogger(SpringMain.class);

    public static void main(String[] args) {
        //Build application context by reading spring-config.xml
        ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"application-context.xml"});

        //Get an instance of ProductService class;
        MyMessageProvider prdSvc = (MyMessageProvider) ctx.getBean("myMessageProviderService");

        MyMessageConsumer conSvc = (MyMessageConsumer) ctx.getBean("myMessageConsumerService");

        //Call getProduct method of ProductService
        prdSvc.sendMessage("This is a test");

        try {
            conSvc.readMessage();
        } catch (JMSException e) {
            logger.error(e);
        }
    }
}

When you run the Main class you will see the following output when everything works:

2015-03-29 10:26:39 DEBUG net.pascalalma.aws.sqs.MyMessageProvider(28) - Sending message with txt: This is a test
2015-03-29 10:26:41 DEBUG net.pascalalma.aws.sqs.MyMessageProvider(35) - Message sent 
2015-03-29 10:26:41 DEBUG net.pascalalma.aws.sqs.MyMessageConsumer(24) - Reading message
2015-03-29 10:26:41 INFO  net.pascalalma.aws.sqs.MyMessageConsumer(29) - Received text: This is a test
2015-03-29 10:26:41 DEBUG net.pascalalma.aws.sqs.MyMessageConsumer(32) - class com.amazon.sqs.javamessaging.message.SQSTextMessage
2015-03-29 10:26:41 INFO  net.pascalalma.aws.sqs.MyMessageConsumer(33) - Done

Process finished with exit code 0
Advertisements

About Pascal Alma

Pascal is a senior IT consultant and has been working in IT since 1997. He is monitoring the latest development in new technologies (Mobile, Cloud, Big Data) closely and particularly interested in Java open source tool stacks, cloud related technologies like AWS and mobile development like building iOS apps with Swift. Specialties: Java/JEE/Spring Amazon AWS API/REST Big Data Continuous Delivery Swift/iOS
This entry was posted in AWS, Spring Framework and tagged , , , . Bookmark the permalink.