I had a hard time figuring out how to implement a Spring Boot JMS Listener, listening to an ActiveMQ queue within a JBoss application server.
Therefore I choose to post a question and answer it with my final solution, hoping it could save some of you a few hours.
ActiveMQ is supported by Spring Boot autoconfiguration, but since it was inside the JBoss server Spring Boot was failing to connect ActiveMQ.
In fact you need to define connectionFactory and jmsListenerContainerFactory beans yourself by doing a lookup on the JNDI provider.
#Configuration
#EnableJms
public class ActiveMqConnectionFactoryConfig {
#Value("${broker.url}")
String brokerUrl;
#Value("${borker.username}")
String userName;
#Value("${borker.password}")
String password;
#Value("${queue}")
String queueName;
private static final String INITIAL_CONTEXT_FACTORY = "org.jboss.naming.remote.client.InitialContextFactory";
private static final String CONNECTION_FACTORY = "jms/RemoteConnectionFactory";
#Bean
public ConnectionFactory connectionFactory() {
try {
System.out.println("Retrieving JMS queue with JNDI name: " + CONNECTION_FACTORY);
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName(CONNECTION_FACTORY);
jndiObjectFactoryBean.setJndiEnvironment(getEnvProperties());
jndiObjectFactoryBean.afterPropertiesSet();
return (QueueConnectionFactory) jndiObjectFactoryBean.getObject();
} catch (NamingException e) {
System.out.println("Error while retrieving JMS queue with JNDI name: [" + CONNECTION_FACTORY + "]");
} catch (Exception ex) {
System.out.println("Error");
}
return null;
}
Properties getEnvProperties() {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, brokerUrl);
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
return env;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
JndiDestinationResolver jndiDestinationResolver = new JndiDestinationResolver();
jndiDestinationResolver.setJndiEnvironment(getEnvProperties());
factory.setDestinationResolver(jndiDestinationResolver);
return factory;
}
Then if you want to consume the queue you just define your JMS consumer class with a method annotated with #JmsListener(destination = "${queue}")
#JmsListener(destination = "${queue}")
public void receive(Message message) {
System.out.println("Received Message: " + message);
}
Hope that helps save a few hours of research ;)
Cheers
Related
So recently I was exploring on how to make a client app utilising Redis PubSub feature and make it resemble Apache Kafka messaging. I know that those two have their own characteristics, therefore I might not be able to make the client app, a Redis Subscriber, to work RESEMBLE Apache Kafka Consumer.
What I'm currently tweaking is The Redis Subscribers behaviour on the client app side, so they will not receiving (or 'consuming') the same message sent their subbed channel. I know basically it's kind of impossible, given Redis Pubsub's behaviour that all subscribers will received the published message equally...
... But, I did found a way.
FYI, I am using :
Java
Maven, as dependecy injection
SpringBoot
Dependecies...
Spring Data Redis
Jedis (3.6.0)
json - org.json (20210307)
So here's my setup...
BeanConfiguration.java
#Configuration
public class BeanConfiguration{
#Bean
public JedisConnectionFactory connectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
JedisConnectionFactory connFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return connFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> connTemplate = new RedisTemplate<String, Object>();
connTemplate.setConnectionFactory(connectionFactory());
connTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
connTemplate.setKeySerializer(new StringRedisSerializer());
connTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
connTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return connTemplate;
}
#Bean
public MessageListenerAdapter messageListener() {
return new MessageListenerAdapter(new RedisMessageSubscriber(redisTemplate()));
}
#Bean
public ChannelTopic topic() {
return new ChannelTopic("mychannel");
}
#Bean
public RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.addMessageListener(messageListener(), topic());
container.setTaskExecutor(Executors.newFixedThreadPool(4));
return container;
}}
RedisMessageSubscriber.java
#Service
public class RedisMessageSubscriber implements MessageListener {
private RedisTemplate<String, Object> redisTemplate;
public RedisMessageSubscriber(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
#Override
public void onMessage(Message message, byte[] pattern) {
JSONObject jsonMessage = new JSONObject(message.toString());
lockAndProcessMessage(jsonMessage);
}
private void lockAndProcessMessage(JSONObject jsonMessage) {
String key = jsonMessage.getString("id");
String value = jsonMessage.getString("value");
Boolean isNotExist = redisTemplate.opsForValue().setIfAbsent(key, value, 5, TimeUnit.MINUTES);
if (isNotExist) {
System.out.println("SUCCESSFULLY SET ['" + key + "'] Expired in '1 minute'");
try {
System.out.println("Processing ['" + key + "'] ...");
Thread.sleep(60000);
} catch(InterruptedException ex) {
ex.printStackTrace();
} finally {
redisTemplate.opsForValue().getOperations().delete(key);
System.out.println("UNLOCKED ['" + key + "']");
}
} else {
System.out.println("FAILED TO LOCKED ['" + key + "']");
}
}}
Using codes above, I was able to achieve the "no-double-consuming" condition, but there is some circumstances that made the "double-consuming" happened.
If instance "A" is processing at queue 15, while instance "B" is still processing at queue 6 and by the time "A" is done with 15 and "B" caught up with queue 15, "B" WILL PROCESSED queue 15 again due to the marker left by "A" had already been deleted (Due to the processing of 15 is done by "A").
Is there anyway to counter this "weakness"? I am also open to any solution, suggestion, discussion, and findings of weakness in my sample codes. Thank you
Sorry if my english is bad :D
I am trying to integrate HornetMQ Consumer in Springboot application. I have seen different example but all of them are pointing ActiveMQ implementation which make me little bit confuse. I have written a standard HornetQ Consumer in java. Here is a code:
public class HornetQClient {
private String JMS_QUEUE_NAME;
private String MESSAGE_PROPERTY_NAME;
private ClientSessionFactory sf = null;
private static final Logger LOGGER = LoggerFactory.getLogger(TCPClient.class);
public HornetQClient(String hostName, String hostPort, String queueName, String propertyName) {
try {
Map<String, Object> map = new HashMap<String, Object>();
map.put("host", hostName);
map.put("port", hostPort);
this.JMS_QUEUE_NAME = queueName;
this.MESSAGE_PROPERTY_NAME = propertyName;
ServerLocator serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(NettyConnectorFactory.class.getName(), map));
sf = serverLocator.createSessionFactory();
startReadMessages();
} catch (Exception e) {
e.printStackTrace();
}
}
private void startReadMessages() {
ClientSession session = null;
try {
if (sf != null) {
session = sf.createSession(true, true);
while (true) {
ClientConsumer messageConsumer = session.createConsumer(JMS_QUEUE_NAME);
session.start();
ClientMessage messageReceived = messageConsumer.receive(1000);
if (messageReceived != null && messageReceived.getStringProperty(MESSAGE_PROPERTY_NAME) != null) {
System.out.println("Received JMS TextMessage:" + messageReceived.getStringProperty(MESSAGE_PROPERTY_NAME));
messageReceived.acknowledge();
} else
System.out.println("no message available");
messageConsumer.close();
Thread.sleep(500);
}
}
} catch (Exception e) {
LOGGER.error("Error while adding message by producer.", e);
} finally {
try {
session.close();
} catch (HornetQException e) {
LOGGER.error("Error while closing producer session,", e);
}
}
}
This one is working fine but is there any standard way to write Message Consumer in spring boot application or should i directly create a bean of this client and use in Springboot application
--------------- hornetq-jms.xml---------
<?xml version="1.0"?>
<configuration xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:hornetq">
<!--the connection factory used by the example -->
<connection-factory name="ConnectionFactory">
<connectors>
<connector-ref connector-name="netty-connector" />
</connectors>
<entries>
<entry name="ConnectionFactory" />
</entries>
<consumer-window-size>0</consumer-window-size>
<connection-ttl>-1</connection-ttl>
</connection-factory>
<queue name="trackerRec">
<entry name="trackerRec" />
</queue>
</configuration>
You could perhaps use Spring JMS and JmsTemplate for this. The default set up for Spring boot is using an ActiveMQ connection factory, but if you exchange this for a HornetQConnetionFactory, you should be good to go:
#Configuration
#EnableJms
public class JmsConfig {
#Bean
public ConnectionFactory connectionFactory() {
final Map<String, Object> properties = new HashMap<>();
properties.put("host", "127.0.0.1");
properties.put("port", "5445");
final org.hornetq.api.core.TransportConfiguration configuration =
new org.hornetq.api.core.TransportConfiguration("org.hornetq.core.remoting.impl.netty.NettyConnectorFactory", properties);
return new org.hornetq.jms.client.HornetQJMSConnectionFactory(false, configuration);
}
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
}
I added the fully qualified class names for clearity.
Then, in some bean, you can just do this to consume a message:
#JmsListener(destination = "some.queue", containerFactory = "myFactory")
public void receiveMessage(#Header("some.header") final String something) {
System.out.println("Received <" + something + ">");
}
Disclaimer: I have not actually tried this in practice, it is based on my experience with Spring and ActiveMQ, as well as these sources: https://dzone.com/articles/connecting-spring https://spring.io/guides/gs/messaging-jms/
You might have to do some digging to get this to work the way you want, but I think this approach is a bit more "high-level" than the one you are going for.
I need help, my app (client) connect to RabbitMQ server and when server shutdown, my app cannot start....
Listener can't created and app failed start.
Or when virtual host dont have queue my app cant start too
So my question
1) How to process exception in my config (all exception, i need was my app start if RabbitMQ server have problems)
2) What in my config look bad and need refactor
i use
Spring 4.2.9.RELEASE
org.springframework.amqp 2.0.5.RELEASE
Java 8
My 2 classes
1) Config for Beans RabbitMq
2) Listener annotation
#EnableRabbit
#Configuration
public class RabbitMQConfig {
#Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory factoryRabbit = new com.rabbitmq.client.ConnectionFactory();
factoryRabbit.setNetworkRecoveryInterval(10000);
factoryRabbit.setAutomaticRecoveryEnabled(true);
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory(factoryRabbit);
connectionFactory.setHost("DRIVER_APP_IP");
connectionFactory.setPort(5672);
connectionFactory.setConnectionTimeout(5000);
connectionFactory.setRequestedHeartBeat(10);
connectionFactory.setUsername("user");
connectionFactory.setPassword("pass");
connectionFactory.setVirtualHost("/vhost");
return connectionFactory;
}
#Bean
public RabbitTemplate rabbitTemplate() {
try {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setRoutingKey(this.DRIVER_QUEUE);
rabbitTemplate.setQueue(this.DRIVER_QUEUE);
return rabbitTemplate;
} catch (Exception ex){
return new RabbitTemplate();
}
}
#Bean
public Queue queue() {
return new Queue(this.DRIVER_QUEUE);
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(3);
factory.setMaxConcurrentConsumers(10);
return factory;
}
}
#Component
public class RabbitMqListener {
#RabbitListener(bindings = #QueueBinding(
value = #Queue(value = DRIVER_QUEUE, durable = "true"),
exchange = #Exchange(value = "exchange", ignoreDeclarationExceptions = "true", autoDelete = "true"))
)
public String balancer(byte[] message) throws InterruptedException {
String json = null;
try {
"something move"
} catch (Exception ex) {
}
}
I found solution my problem
First it's Bean container!
We need this
factory.setMissingQueuesFatal(false);
this property give our when queue lost on server RabbitMQ, our app don't crash and can start
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMissingQueuesFatal(false);
factory.setConcurrentConsumers(3);
factory.setStartConsumerMinInterval(3000L);
factory.setMaxConcurrentConsumers(10);
factory.setRecoveryInterval(15000L);
factory.setStartConsumerMinInterval(1000L);
factory.setReceiveTimeout(10000L);
factory.setChannelTransacted(true);
return factory;
}
and second
#Component
public class RabbitMqListener {
#RabbitListener(containerFactory = "rabbitListenerContainerFactory", queues = DRIVER_QUEUE)
public String balancer(byte[] message) throws InterruptedException {
String json = null;
try {
"something move"
} catch (Exception ex) {
}
}
I set containerFactory and Queue in #RabbitListener and drop other propertys,
because i don't need it
I hope it's help somebody, thank all for you attention and sorry for my English
I'm developing Spring Boot application which must connect to several WebSphere JMS connections with different ports or even ip addresses. I need receive and send messages to different queues.
I took example of connection from this source - https://github.com/lzp4ever/IBM_WebSphere_MQ_Spring_Boot_JMS
But when i add second connectionFactory Spring Boot failes to start, it just don't know which once to use.
My question is How should i configure my config file to listen several queues? Is it good idea connecting SpringBoot app to several different JMS servers?
Solution
i just copy and paste same beans(like at git link above) second time and add Bean(name) to separate them. It was not work and then i added new JmsListenerContainerFactory bean to each of my config file.
One of my config file is:
#Bean(name = "mqQueueConnectionFactory2")
public MQQueueConnectionFactory mqQueueConnectionFactory2() {
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
mqQueueConnectionFactory.setHostName(host);
try {
mqQueueConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
mqQueueConnectionFactory.setCCSID(1208);
mqQueueConnectionFactory.setChannel(channel);
mqQueueConnectionFactory.setPort(port);
mqQueueConnectionFactory.setQueueManager(queueManager);
} catch (Exception e) {
logger.error("MQQueueConnectionFactory bean exception", e);
}
return mqQueueConnectionFactory;
}
#Bean(name = "userCredentialsConnectionFactoryAdapter2")
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter2(#Qualifier("mqQueueConnectionFactory2") MQQueueConnectionFactory mqQueueConnectionFactory) {
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter = new UserCredentialsConnectionFactoryAdapter();
userCredentialsConnectionFactoryAdapter.setUsername(username);
userCredentialsConnectionFactoryAdapter.setPassword(password);
userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqQueueConnectionFactory);
return userCredentialsConnectionFactoryAdapter;
}
#Bean(name = "cachingConnectionFactory2")
//#Primary
public CachingConnectionFactory cachingConnectionFactory2(#Qualifier("userCredentialsConnectionFactoryAdapter2") UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter) {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setTargetConnectionFactory(userCredentialsConnectionFactoryAdapter);
cachingConnectionFactory.setSessionCacheSize(500);
cachingConnectionFactory.setReconnectOnException(true);
return cachingConnectionFactory;
}
#Bean(name = "jmsTransactionManager2")
public PlatformTransactionManager jmsTransactionManager2(#Qualifier("cachingConnectionFactory2") CachingConnectionFactory cachingConnectionFactory) {
JmsTransactionManager jmsTransactionManager = new JmsTransactionManager();
jmsTransactionManager.setConnectionFactory(cachingConnectionFactory);
return jmsTransactionManager;
}
#Bean(name = "jmsOperations2")
public JmsOperations jmsOperations2(#Qualifier("cachingConnectionFactory2") CachingConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
jmsTemplate.setReceiveTimeout(receiveTimeout);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory<?> myFactory2(#Qualifier("cachingConnectionFactory2") CachingConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// 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;
}
Then i change my sender code from this:
#Autowired
private JmsOperations jmsOperations;
to this
#Autowired
#Qualifier("jmsOperations2")
private JmsOperations jmsOperations;
also i change my receiver to:
#JmsListener(destination = "${project.queues.uzb.recieve}", containerFactory = "myFactory2")
public void receiveMessage(JMSTextMessage data) {
}
it seems to me it worked!!!
But one of my CachingConnectionFactory must be marked as #Primary. If i delete #Primaty from one of my config files then i am gettig this error:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-03-28 12:28:37 -
APPLICATION FAILED TO START
Description:
Parameter 1 of method myFactory in com.config.UzbConnection required a bean of type 'org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer' in your configuration.
Thanks
Just my 2 cents. If you have problems with multiple JMS connections because you have a project using a mix of Spring-boot and JMS with Spring xml configuration to make your connection factory, you can disable autostart of spring-boot-jms with this in your application:
#SpringBootApplication(exclude = {JmsAutoConfiguration.class})
this way you can mix both.
Spring JDBC allows to specify in properties file for PROD:
jdbc.driverClassName = oracle.jdbc.OracleDriver
jdbc.url = jdbc:oracle:thin:#...
and for tests
jdbc.driverClassName = org.h2.Driver
jdbc.url = jdbc:h2:mem:test;INIT=...
Thus it's possible to instantiate needed java.sql.DataSource instance depends of configuration settings with generic code
#Bean
public DataSource dataSource(
#Value("${jdbc.driverClassName}") String driverClass,
#Value("${jdbc.url}") String url,
#Value("${jdbc.username}") String username,
#Value("${jdbc.password}") String password
) {
DriverManagerDataSource dataSource = new DriverManagerDataSource(url, username, password);
dataSource.setDriverClassName(driverClass);
return dataSource;
}
Is it possible in Spring to configure specific type of java.jms.ConnectionFactory via driver and URL properties' strings like in Spring JDBC?
Actually, my goal is to use Tibco connection factory for PROD and ActiveMQ for tests.
You can use spring profiles to pull in a different Bean for tests or you can simply override the connection factory bean with a different one in the test case.
EDIT
#Bean
public FactoryBean<ConnectionFactory> connectionFactory(#Value("${jms.url}") final String urlProp) {
return new AbstractFactoryBean<ConnectionFactory>() {
#Override
public Class<?> getObjectType() {
if (urlProp.startsWith("activemq:")) {
return ActiveMQConnectionFactory.class;
}
// ...
throw new RuntimeException("bad url: " + urlProp);
}
#Override
protected ConnectionFactory createInstance() throws Exception {
if (urlProp.startsWith("activemq:")) {
URI uri = new URI(urlProp.substring(urlProp.indexOf(":") + 1));
return new ActiveMQConnectionFactory(uri);
}
// ...
throw new RuntimeException("bad url: " + urlProp);
}
};
}
and
jms.url=activemq:vm://localhost