In my Spring Boot application I'm listening message queue. When a message appears I need to execute it synchronously(one by one) in some task-executor.
I'm using Amazon SQS, this is my config:
/**
* AWS Credentials Bean
*/
#Bean
public AWSCredentials awsCredentials() {
return new BasicAWSCredentials(accessKey, secretAccessKey);
}
/**
* AWS Client Bean
*/
#Bean
public AmazonSQS amazonSQSAsyncClient() {
AmazonSQS sqsClient = new AmazonSQSClient(awsCredentials());
sqsClient.setRegion(Region.getRegion(Regions.US_EAST_1));
return sqsClient;
}
/**
* AWS Connection Factory
*/
#Bean
public SQSConnectionFactory connectionFactory() {
SQSConnectionFactory.Builder factoryBuilder = new SQSConnectionFactory.Builder(
Region.getRegion(Regions.US_EAST_1));
factoryBuilder.setAwsCredentialsProvider(new AWSCredentialsProvider() {
#Override
public AWSCredentials getCredentials() {
return awsCredentials();
}
#Override
public void refresh() {
}
});
return factoryBuilder.build();
}
/**
* Registering QueueListener for queueName
*/
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory());
messageListenerContainer.setMessageListener(new MessageListenerAdapter(new QueueListener()));
messageListenerContainer.setDestinationName(queueName);
return messageListenerContainer;
}
Also I need to have possibility to check the status of this task-executor, for example - number of scheduled tasks.
Is it a good idea to use Spring SyncTaskExecutor for this purpose ? If so, could you please show an example how it can be used with Spring Boot.
EDIT:
After revealing your messaging technology and Spring configuration for it, simplest way for you is to configure SyncTaskExecutor (or Executors.newFixedThreadPool(1) would do the job also) as executor for your DefaultMessageListenerContainer. Use this method.
You can register Task executor as separate bean (via #Bean annotation) and autowire it to defaultMessageListenerContainer() method (just add TaskExectuor as parameter).
Below answer is relevant for JMS messaging. It was created before AWS SQS usage was revealed in question:
You didn't mention which messaging technology are you using, therefore I assume JMS.
If synchronous execution is requirement, I believe you can't use native JMS listeners (need to avoid SimpleJmsListenerContainerFactory or SimleMessageListenerContainer).
Instead I would suggest to use #JmsListener annotation with DefaultJmsListenerContainerFactory (this uses long polling instead of native JMS listeners) and configure SyncTaskExecutor (or Executors.newFixedThreadPool(1) would do the job also) as executor for mentioned container factory: DefaultJmsListenerContainerFactory.setTaskExecutor().
This is simple Spring Boot JMS example with DefaultJmsListenerContainerFactory configured. You just need to plug in suitable task executor.
Related
We are currently introducing ActiveMQ into our existing application which was running on a different Queueing system. Spring JMS is used to make use of the existing integration within the Spring framework.
Most of our applications use point-to-point (queue) communication, with the exception of one. It needs to be able to listen to the topic created by another producing application while publishing to multiple queues at the same time.
This means that application needs to support both Topics and Queues. However, when setting the global property
jms:
pub-sub-domain: true
the setting is global and all queue subscribers are immediately subscribing to topics, which we can see in the ActiveMQ web interface.
Is there a way to configure the application to support both topics and queues at the same time?
The boot property is used to configure the default container factory used by #JmsListener methods, as well as to configure the JmsTemplate.
Simply override Boot's default container factory...
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
DefaultJmsListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
and then add a second one
#Bean
public DefaultJmsListenerContainerFactory jmsTopicListenerContainerFactory(
DefaultJmsListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true); << override the boot property
return factory;
}
Then refer to the alternate factory in the #JmsListener for the topic.
Alternatively, if you don't have listeners for both types, set the property to true, but override Boot's JmsTemplate configuration.
How would I configure Spring Cloud AWS XML config with annotations?
I am especially interested in changing default taskExecutor.
I found that there is SimpleMessageListenerContainerFactory used to configure for AWS messaging via Java .
So changing default taskExecutor is just matter for overriding this default container factory bean. Something like this:
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSqs) {
SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
factory.setAmazonSqs(amazonSqs);
Executor executor = Executors.newFixedThreadPool(1);
ConcurrentTaskExecutor taskExecutor = new ConcurrentTaskExecutor(executor);
factory.setTaskExecutor(taskExecutor);
return factory;
}
I'm using JmsMessageSender inside WebServiceTemplate for Spring-WS communication over JMS. I have to work on topics, so I used setPubSubDomain method of JmsMessageSender's superclass, and the messages go correctly on target topic. However, the for handling response, a temporary queue is created, not topic. How can I setup spring beans to have temporary topic for the response, not queue?
To add one hint, there is a setReplyPubSubDomain method of AbstractMessageListenerContainer class, which looks exactly what I need, but I have never used this listener container and I'm not sure how could I wrap it into my beans.
My configuration below:
#Bean
public WebServiceTemplate webServiceTemplate() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMessageFactory(messageFactory());
webServiceTemplate.setMessageSender(messageSender());
webServiceTemplate.setDefaultUri("jms:topicname.topicname.topicname?priority=3&deliveryMode=NON_PERSISTENT&messageType=TEXT_MESSAGE");
return webServiceTemplate;
}
#Bean
public JmsMessageSender messageSender() {
JmsMessageSender messageSender = new JmsMessageSender();
messageSender.setConnectionFactory(connectionFactory());
messageSender.setPubSubDomain(true);
return messageSender;
}
I am using Spring's #JmsListener (spring-jms-4.3.4.RELEASE.jar) for receiving messages from ActiveMQ using the below code:
#Component
public class TopicSubscriber {
#JmsListener(destination="xyz.topic1", subscription="xyz_topic_durable_subscription")
public void send(Product product) {
System.out.println(" reveived message ***"+product);
}
}
As per the Spring API's documentation (link given below), the above code should create a durable subscription with subscription name as xyz_topic_durable_subscription:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jms/annotation/JmsListener.html#subscription--
But, the issue is that the above code creates only Non-Durable subscription which I could find by monitoring the ActiveMQ using admin console (added screenshot below, look for 'xyz.topic1' Destination under 'Active Non-Durable Topic Subscribers' section).
Are there any changes to be made in the code to make the durable subscription ?
You need to configure the ListenerContainerFactory appropriately:
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
factory.setSubscriptionDurable(true);
factory.setClientId("jmsDemo");
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
return factory;
}
There interesting part is here:
factory.setSubscriptionDurable(true);
factory.setClientId("jmsDemo");
Now when you enter the ActiveMQ WebConsole you should see this:
In the answer marked as correct above, the code:
factory.setPubSubDomain(true);
factory.setSubscriptionDurable(true);
factory.setClientId("jmsDemo");
must come after
configurer.configure(factory, connectionFactory);
or you will lose those settings.
You also need to configure the listener container factory to create a container for durable subscriptions.
I am writing a Java application that users Spring for dependency injection and AWS for various services. I will be deploying the application to EC2. The issue I am having is setting the AWS credentials in a secure way during development/deployment. Because the service is running on EC2, I would like to use the InstanceProfileCredentialsProvider in production. However, these credentials are not available during development.
Almost all the AWS clients are currently injected using Spring. Here is an example using DynamoDB:
#Lazy
#Configuration
public class SpringConfiguration {
#Bean(name = "my.dynamoDB")
public DynamoDB dynamoDB() {
return DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient(
new AWSCredentialsProvider() /* What should go here? */));
}
}
Any thoughts?
Try creating a separate bean that returns a credentials provider. Within that method switch between the two credential sources based on stage or host type.
/**
* #return: an AWSCredentialsProvider appropriate for the stage.
*/
#Bean
public AWSCredentialsProvider awsCredentialsProvider() {
if(isProd() /* define what this means in your configuration code */) {
return new InstanceProfileCredentialsProvider()
} else {
return new AWSCredentialsProvider()
}
}