Proper ultimate way to migrate JMS event listening to Spring Integration with Spring Boot - spring

I got a JmsConfig configuration class that handles JMS events from a topic in the following way:
It defines a #Bean ConnectionFactory, containing an ActiveMQ implementation
It defines a #Bean JmsListenerContainerFactory instantiating a DefaultJmsListenerContainerFactory and passing it through Boot's DefaultJmsListenerContainerFactoryConfigurer
It defines a #Bean MessageConverter containing a MappingJackson2MessageConverter and setting a custom ObjectMapper
I use #JmsListener annotation pointing to myfactory on a method of my service. This is the only use I have for the topic, subscription alone.
Now I want to move to Spring Integration. After reading a lot, and provided I don't need a bidirectional use (discarding Gateways) neither a polling mechanism (discarding #InboundChannelAdapter), I am going for a message-driven-channel-adapter, in traditional XML configuration wording. I found that Java idiom should be accomplished by means of the new Spring Integration DSL library, and thus, I look for the proper snippet.
It seems JmsMessageDrivenChannelAdapter is the proper equivalent, and I found a way:
IntegrationFlows.from(Jms.messageDriverChannelAdapter(...))
But the problem is that this only accepts the ActiveMQ ConnectionFactory or an AbstractMessageListenerContainer, but no my boot pre-configured JmsListenerContainerFactory !
How should this be implemented in an ultimate way?

JmsListenerContainerFactory is specific for the #JmsListener, it's a higher level abstraction used to configure a DefaultMessageListenerContainer. Boot does not provide an auto configuration option for a raw DefaultMessageListenerContainer; you have to wire it up yourself. But you can still use the Boot properties...
#Bean
public IntegrationFlow flow(ConnectionFactory connectionFactory,
JmsProperties properties) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(container(connectionFactory, properties)))
...
.get();
}
private DefaultMessageListenerContainer container(ConnectionFactory connectionFactory,
JmsProperties properties) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConcurrentConsumers(properties.getListener().getConcurrency());
container.setMaxConcurrentConsumers(properties.getListener().getMaxConcurrency());
...
return container;
}

There is even a better approach. I am surprised Gary did not comment it.
There's an out-of-the-box builder called Jms.container(...).
#Bean
public IntegrationFlow jmsMyServiceMsgInboundFlow(ConnectionFactory connectionFactory, MessageConverter jmsMessageConverter, MyService myService, JmsProperties jmsProperties, #Value("${mycompany.jms.destination.my-topic}") String topicDestination){
JmsProperties.Listener jmsInProps = jmsProperties.getListener();
return IntegrationFlows.from(
Jms.messageDrivenChannelAdapter( Jms.container(connectionFactory, topicDestination)
.pubSubDomain(false)
.sessionAcknowledgeMode(jmsInProps .getAcknowledgeMode().getMode())
.maxMessagesPerTask(1)
.errorHandler(e -> e.printStackTrace())
.cacheLevel(0)
.concurrency(jmsInProps.formatConcurrency())
.taskExecutor(Executors.newCachedThreadPool())
.get()))
)
.extractPayload(true)
.jmsMessageConverter(jmsMessageConverter)
.destination(topicDestination)
.autoStartup(true)
//.errorChannel("NOPE")
)
.log(LoggingHandler.Level.DEBUG)
.log()
.handle(myService, "myMethod", e -> e.async(true).advice(retryAdvice()))
.get();

Related

Mutil Consumer means my server using multi thread?

I am working on a simple project in Java.
I want to know that if I write this code, SimpleRabbitListenerContainerFactory.setConcurrentConsumers(2) then my program use multi thread?
Simple answer is yes.
Assuming that you are configuring it inside #Bean annotated method that configures your listener.
According to Spring AMPQ docs it should look somehow similar to that:
#Configuration
#EnableRabbit
public class AppConfig {
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(3);
factory.setMaxConcurrentConsumers(10);
return factory;
}
}
As you can see, there is also max number of concurrent consumers set up which means that you will be using something between min=3 and max=10 consumers.
If you set only .setConcurrentConsumers(3) it will mean that you are using fixed number. No more, no less.
Recommended reads are:
Listener Concurrency
Annotation driven async config

Spring 3 and Rabbit MQ integration (not Spring Boot)

I'm having difficulty getting a Spring 3 application to integrate with RabbitMQ, in order to receive messages from a queue (I do not need to send messages).
Part of the challenge is much of the documentation now relates to Spring Boot. The related Spring guide is helpful, but following the steps does not seem to work in my case. For instance, the guide includes the text:
The message listener container and receiver beans are all you need to listen for messages.
So I have setup the listener container and receiver beans with the following code.
Setting up message handler
#Component
public class CustomMessageHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
Setting up configuration
#Configuration
public class RabbitConfig {
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory){
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setRoutingKey("queue-name");
return rabbitTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("...host...");
connectionFactory.setPort(5671);
connectionFactory.setVirtualHost("...virtual host..");
connectionFactory.setUsername("...username...");
connectionFactory.setPassword("...password...");
return connectionFactory;
}
#Bean
public MessageListenerAdapter messageListenerAdapter(CustomMessageHandler messageHandler) {
return new MessageListenerAdapter(messageHandler, "handleMessage");
}
#Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory,
MessageListenerAdapter messageListenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setQueueNames("queue-name");
container.setConnectionFactory(connectionFactory);
container.setMessageListener(messageListenerAdapter);
return container;
}
}
Unfortunately with this setup, the application will start up, but it never triggers the message handler. The queue it is trying to read from also has one message sitting in it, waiting to be consumed.
Any ideas on something that is missing, or appears misconfigured?
Thanks to some dependency management assistance from #GaryRussell, I was able to see that the version of spring-rabbit and spring-amqp were too recent. Using the older 1.3.9.RELEASE unfortunately proved to add additional challenges.
Some other assistance came in the form of using an actual RabbitMQ Java client. This option was much simpler to implement, and avoided the dependency problems. Ultimately I needed to include the following dependency:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
And then I simply followed their documentation on creating a connection, and consuming messages.
Voila, it works!

Spring Integration - kafka Outbound adapter not taking topic value exposed as spring bean

I have successfully integrated kafka outbound channle adapter with fixed topic name. Now, i want to make the topic name configurable and hence, want to expose it via application properties.
application.properties contain one of the following entry:
kafkaTopic:testNewTopic
My configuration class looks like below:
#Configuration
#Component
public class KafkaConfig {
#Value("${kafkaTopic}")
private String kafkaTopicName;
#Bean
public String getTopic(){
return kafkaTopicName;
}
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
#Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");//this.brokerAddress);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// set more properties
return new DefaultKafkaProducerFactory<>(props);
}
}
and in my si-config.xml, i have used the following (ex: topic="getTopic") :
<int-kafka:outbound-channel-adapter
id="kafkaOutboundChannelAdapter" kafka-template="kafkaTemplate"
auto-startup="true" sync="true" channel="inputToKafka" topic="getTopic">
</int-kafka:outbound-channel-adapter>
However, the configuration is unable to pick up the topic name when exposed via bean. But it works fine when i hard code the value of the topic name.
Can someone please suggest what i am doing wrong here?
Does topic within kafka outbound channel accept the value referred as bean?
How do i externalize it as every application using my utility will supply different kafka topic names
The topic attribute is for string value.
However it supports property placeholder resolution:
topic="${kafkaTopic}"
and also SpEL evaluation for aforementioned bean:
topic="#{getTopic}"
Just because this is allowed by the XML parser configuration.
However you may pay attention that KafkaTemplate, which you inject into the <int-kafka:outbound-channel-adapter> has defaultTopic property. Therefore you won't need to worry about that XML.
And one more option available for you is Spring Integration Annotations configuration. Where you can define a #ServiceActivator for the KafkaProducerMessageHandler #Bean:
#ServiceActivator(inputChannel = "inputToKafka")
#Bean
KafkaProducerMessageHandler kafkaOutboundChannelAdapter() {
kafkaOutboundChannelAdapter adapter = new kafkaOutboundChannelAdapter( kafkaTemplate());
adapter.setSync(true);
adapter.setTopicExpression(new LiteralExpression(this.kafkaTopicName));
return adapter;
}

spring-boot configure non exposed properties

I am using spring-boot to configure jms and activemq connectivity. Due to a defect in activemq I need to set the idle timeout on the PooledConnectionFactory. This configuration is not exposed by spring-boot. How do I set it?
I have a #Bean to create a messageListenerContainer which has the connectionFactory as an argument. I can instanceof check the factory and configure it here but this seems not the correct way.
Downcasting to PooledConnectionFactory and calling setIdleTimeout is a perfectly reasonable approach, in my opinion.
If you'd prefer not to do it as part of the creation of the message listener container, you could declare your own ConnectionFactory bean while still making use of ActiveMQProperties. Something like this:
#Configuration
#EnableConfigurationProperties(ActiveMQProperties.class)
class CustomActiveMQConnectionFactoryConfiguration {
#Autowired
private ActiveMQProperties properties;
#Bean
public ConnectionFactory jmsConnectionFactory() {
ConnectionFactory connectionFactory = this.properties.createConnectionFactory();
if (connectionFactory instanceof PooledConnectionFactory) {
((PooledConnectionFactory) connectionFactory).setIdleTimeout(1000);
}
return connectionFactory;
}
}

complete jms:listener migration to JavaConfig

Like the title says..
I have read this valuable How to add multiple JMS MessageListners in a single MessageListenerContainer for Spring Java Config link
The author of that post is working through
messageListenerContainer.setMessageListener(new TaskFinished());
BTW: I use
#Autowired
private ConsumerListener consumerListener;
defaultMessageListenerContainer.setMessageListener(consumerListener);
I am not using the new operator.
OK, the restriction of the setMessageListener method is: the class must implements the MessageListener interface, I have tested and works
My problem is, according with 23.6 JMS Namespace Support
How represent the following:
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
through JavaConfig?
They are simple pojo (see the ref and method attributes)
I want use how an option a simple pojo (#Component or #Service) instead of a MessageListener object
In the DefaultMessageListenerContainer API, there is no something to work around this requirement or situation.
Thanks in advance..
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
This xml uses a MessageListenerAdapter which you can hand a delegate (the ref and a method to execute (default 'handleMessage`).
#Configuration
public MyJmsConfiguration {
#Bean
public DefaultMessageListenerContainer consumerJmsListenerContainer() {
DefaultMessageListenerContainer dmlc = new DefaultMessageListenerContainer();
...
MessageListenerAdapter listener = new MessageListenerAdapter();
listener.setDelegate(orderService());
listener.setDefaultListenerMethod("placeOrder");
dmlc.setMessageListener(listener);
return dmlc;
}
To use it from Java config use something like the snippet above.

Resources