I'm using Redis Publish Subscribe through Spring Data, but I'm having problems to add more than 1 Channel.
Currently I'm following the typical examples where the MessageListenerContainer is configured by adding a MessageListenerAdapter which has associated a Receiver class, as following:
The previous works perfectly and I'm able to push and receive messages.
However one I try to add a second listener adapter for creating a "channel with a differente receiver and I'm getting a NullPointerException.
The error is attached below. Is there a different way to adding a new adapter? In general I would like to add channels dynamically.
It is possible to add multiple channels associated with one specific receiver by providing a PatternTopic list at addMessageListener method.
Thanks for your help
I believe there is an important bug with Spring Redis when adding MessageListenerAdapter.
If the Receiver class does not extend from MessageListener (and so, implements onMessage) the inner method MethodInvoker() from MessageListenerAdapter class specifically ask if the Receiver is instance of MessageListener (see last line of image below).
To solve this problem just extend from MessageListener and then you can add the additional adapters directly.
It's a shame that spring-data-redis team do not enable issues in their github page to publish this bug. https://github.com/spring-projects/spring-data-redis
If anyone is still looking, use below configuration for Spring Boot 1.5.X
RedisConfig class for multiple channels:
#Configuration
public class RedisConfig {
#Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
#Qualifier("notificationListenerAdapter") MessageListenerAdapter notificationListenerAdapter,
#Qualifier("countListenerAdapter") MessageListenerAdapter countListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(notificationListenerAdapter, new PatternTopic("notification"));
container.addMessageListener(countListenerAdapter, new PatternTopic("count"));
return container;
}
#Bean("notificationListenerAdapter")
MessageListenerAdapter notificationListenerAdapter(RedisReceiver redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveNotificationMessage");
}
#Bean("countListenerAdapter")
MessageListenerAdapter countListenerAdapter(RedisReceiver redisReceiver) {
return new MessageListenerAdapter(redisReceiver, "receiveCountMessage");
}
#Bean
RedisReceiver receiver() {
return new RedisReceiver();
}
#Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
RedisReceiver to receive messages from channels.
Note: Make sure the method name matches the method names defined above.
public class RedisReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisReceiver.class);
public void receiveNotificationMessage(String message) {
LOGGER.info("Message Received from notification channel: <" + message + ">");
}
public void receiveCountMessage(String message) {
LOGGER.info("Message Received from count channel: <" + message + ">");
}
}
Test the flow:
public class TestMessages {
private static final Logger LOG = LoggerFactory.getLogger(TestMessages.class);
private final StringRedisTemplate redisTemplate;
public TestMessages(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void sendNotification(String message) {
redisTemplate.convertAndSend("notification", message);
}
public void sendCount(String message) {
redisTemplate.convertAndSend("count", message);
}
}
Related
I want to poll 100 messages from DB every 120 seconds for which I have written following bean.
#Component
class AccountConfiguration {
#Autowired
#Qualifier("outChannel") // defined in spring xml configuration
private MessageChannel outChannel;
#Bean
#InboundChannelAdapter(value = "outChannel",
poller = #Poller(fixedDelay = "120", maxMessagesPerPoll = "100"))
public List<Account> getAccounts(DataSource dataSource) {
JdbcPollingChannelAdapter adapter = newJdbcPollingChannelAdapter(dataSource);
adapter.setRowMapper(new AccountMapper());
Message<Object> result = adapter.receive();
List<Account> list = (ArrayList) result.payload();
return list;
}
}
Above code retrieves rows from DB. But now I want to pass this list to a listener below
#Component
class AccountMessageListener {
public void onMessage(List<Account> list){
System.out.println("Message received");
}
}
Above listener I am trying to call as below
#Component
class AccountService{
#Autowired
#Qualifier("outChannel") // Autowiring the same channel here used above
private MessageChannel outChannel;
#Autowired
AccountMessageListener listener;
public void generateFile(String region){
IntegrationFlows.from("outChannel").handle(listener,"onMessage").get();
}
}
#SpringBootApplication
public class SpringBootExampleApplication
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(SpringBootExampleApplication.class, args);
AccountService service = context.getBean(AccountService.class);
service.generateFile("ASIA");
}
}
// Below is from Spring xml
<int:channel id="outChannel"/>
<bean id="messageHandler" class="com.account.AccountMessageListener/>
My assumption is that when generateFile is invoked, outChannel will already have data which is passed to "outChannl" by bean getAccounts in AccountConfiguration class
But when generateFile is invoked, it seems outChannel does not have any data and so onMessage is not called.
My queries are - how can I pass data from JdbcPollingChannelAdapter -> outChannel-> onMessage of AccountMessageListener every 120 secs for 2 hours;
Also, is there a way to check number of messages in channel
Your #InboundChannelAdapter configuration is not correct. Since you deal with a JdbcPollingChannelAdapter, then exactly this one has to be a result of getAccounts() bean method:
#Bean
#InboundChannelAdapter(value = "outChannel",
poller = #Poller(fixedDelay = "120", maxMessagesPerPoll = "100"))
public JdbcPollingChannelAdapter getAccounts(DataSource dataSource) {
JdbcPollingChannelAdapter adapter = newJdbcPollingChannelAdapter(dataSource);
adapter.setRowMapper(new AccountMapper());
return adapter;
}
You just don't call MessageSource.receive() yourself. The framework knows what to do with an #InboundChannelAdapter and how to configure and poll the provided bean.
See more in docs: https://docs.spring.io/spring-integration/reference/html/configuration.html#annotations_on_beans
You don't need an XML config if you deal with Spring Boot: better to have everything configured via #Configuration or #Component.
Your usage of the Java DSL (an IntegrationFlow) is wrong. The IntegrationFlow has to be declared as a #Bean and you don't call configuration-related methods yourself:
#Bean
public IntegrationFlow generateFile(String region){
return IntegrationFlows.from("outChannel").handle(listener,"onMessage").get();
}
See docs about Java DSL: https://docs.spring.io/spring-integration/reference/html/dsl.html#java-dsl
It is not is not clear what is that region since it is out of use.
The #Autowire for outChannel bean is useless: you just don't use that property in your code.
There might be some other flaws and questions: your current code requires too much clean up and reworks.
I am learning JMS and different types of brokers out there. I am currently using ActiveMQ (Artemis) for a dummy project.
I currently have Artemis running on the default settings. I can go to the management console and see the queues and topics.
I am now creating 2 Java Spring-based apps; one for producing and one for consuming.
I have seen few tutorials out there, but I'm getting a NPE, which I'm not sure - why, as I believe I am autowiring the bean correctly.
These are my classes:
Main class:
#SpringBootApplication
public class SpringJmsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJmsApplication.class, args);
SendMessage send = new SendMessage("This is the test message");
}
}
Sender:
public class Sender {
private static final Logger LOGGER =
LoggerFactory.getLogger(Sender.class);
#Autowired
private JmsTemplate jmsTemplate;
public void send(String message) {
LOGGER.info("sending message='{}'", message);
jmsTemplate.convertAndSend("helloworld.q", message);
}
}
Sender Config:
#Configuration
public class SenderConfig {
#Value("${artemis.broker-url}")
private String brokerUrl;
#Bean
public ActiveMQConnectionFactory senderActiveMQConnectionFactory() {
return new ActiveMQConnectionFactory(brokerUrl);
}
#Bean
public CachingConnectionFactory cachingConnectionFactory() {
return new CachingConnectionFactory(
senderActiveMQConnectionFactory());
}
#Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(cachingConnectionFactory());
}
#Bean
public Sender sender() {
return new Sender();
}
}
SendMessage Service:
public class SendMessage {
#Autowired
Sender sender;
public SendMessage(String message){
this.sender.send(message);
}
}
So essentially the error is stemming from the SendMessage class, it's unable to autowire the sender bean but I'm not sure why this error is happening because the Sender bean is being created in the SenderConfig class hence surely Spring should've added it to it Spring container/Bean factory/application context?
This is the stacktrace:
Exception in thread "main" java.lang.NullPointerException
at com.codenotfound.jms.SendMessage.<init>(SendMessage.java:11)
at com.codenotfound.SpringJmsApplication.main(SpringJmsApplication.java:16)
Your problem doesn't stem from SendMessage class, this class seems OK.
Your NPE is caused by the way you obtain an instance of SendMessage class, namely, you're not really obtaining the #Bean, managed by Spring Container; rather, you're creating it manually with a new keyword, as:
SendMessage send = new SendMessage("This is the test message");
This allocates a completely new object in the Heap, which is not going to end up in the Spring Container, hence → is not going to be managed by Spring, hence → its field sender will not be #Autowired.
Here is the culprit in your main class.
SendMessage send = new SendMessage("This is the test message");
You are creating object yourself instead of getting from the context, Spring DI won't be applied to the objects created by ourself.
Solution is, mark the SendMessage as spring managed bean by annotating with #Component and get it from context.
I have a Spring application using RabbitMQ (spring-boot-starter-amqp).
I wanted to know if it's possible to use the #RabbitListener annotation across different classes.
I currently have two classes: Receiver and DeadLetterQueue
Receiver.java:
#Component
#Slf4j
public class Receiver {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#RabbitListener(queues = "queueName")
public void receiveMessage(String message) throws Exception {
logger.info("Received <{}>" + message.toString());
throw new Exception("Error with the message");
}
DeadLetterQueue.java:
#Component
#Slf4j
public class DeadLetterQueue {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#RabbitListener(queues = "otherQueueName")
public void processFailedMessages(String message) {
logger.info("Received failed message<{}>:" + message.toString());
}
}
RabbitMqConfig.java:
#Data
#Configuration
#ConfigurationProperties(prefix = "rabbitmq")
public class RabbitMqConfig {
private String host;
private int port;
private String username;
private String password;
private String queue;
private String exchange;
private String dlq;
private String dlx;
private String routingKey;
#Bean
Queue incomingQueue() {
return QueueBuilder.durable(queue)
.withArgument("x-dead-letter-exchange", dlx)
.build();
}
#Bean
FanoutExchange deadLetterExchange() {
return new FanoutExchange(dlx);
}
#Bean
Queue deadLetterQueue() {
return QueueBuilder.durable(dlq).build();
}
#Bean
Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange());
}
#Bean
DirectExchange incomingQueueExchange() {
return new DirectExchange(exchange);
}
#Bean
Binding incomingQueueBinding() {
return BindingBuilder.bind(incomingQueue()).to(incomingQueueExchange()).with(queue);
}
When I use the RabbitMQ management tool to post a message to the FanoutExchange, the processFailedMessages inside the DeadLetterQueue class doesn't log anything to the console. However if this method is inside of the Receiver class, everything works fine. Which led me to the assumption that #RabbitListener couldn't work across multiple classes unless there is some configuration which I am missing.
Other information:
I'm using Docker to run the RabbitMQ Server
Strangely, if I put the receiveMessage() method inside the DeadLetterQueue class, the expectations fail.
So: Is it possible to define queues in multiple classes with #RabbitListener?
Yes, you can have as many #RabbitListeners as you want, either in the same class or in multiple classes, as long as those classes are used in Spring beans.
Since you have #Component on both, it should work just fine, unless the DeadLetterQueue is in a package that is not scanned by Spring Boot.
Boot only looks at the packages and subpackages where the main #SpringBootApplication is located.
You can enable DEBUG logging for org.springframework to log all the bean creation during application initialization.
I'm using Docker to run the RabbitMQ Server
The location of the broker is irrelevant.
I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs
Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}
I have implemented a basic asynchronous RPC call using spring boot 1.4 and rabbit mq.
My intention is to use this example as a basis of communication
among micro services.For example, Publisher.java and Subscriber.java could be two micro services talking to each other.
The code shown works fine, but I am curious to know if there are any better ways
of doing this?
My queries as follows:
For subscriber to listen to request queue using #RabbitListener annotation , I did not had to declare directExchange() and binding() beans in configuration. But for asyncRabbitTemplate to read response from reply queue, I had to declare directExchange() and binding() beans in configuration.
Is there any way I can avoid it, because I feel it is code duplication as I am declaring these beans twice.
In real world application, there would be many such calls between micro services.And as per my understanding , I would need to declare similar rpcReplyMessageListenerContainer() and asyncRabbitTemplate() for each request-reply call.Is that correct?
Code as follows.
Link to Github
Config.java
#Configuration("asyncRPCConfig")
#Profile("async_rpc")
#EnableScheduling
#EnableRabbit
#ComponentScan(basePackages = {"in.rabbitmq.async_rpc"})
public class Config {
#Value("${queue.reply}")
private String replyQueue;
#Value("${exchange.direct}")
private String directExchange;
#Value("${routingKey.reply}")
private String replyRoutingKey;
#Bean
public Publisher publisher() {
return new Publisher();
}
#Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public Queue replyQueueRPC() {
return new Queue(replyQueue);
}
#Bean
public SimpleMessageListenerContainer rpcReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(replyQueueRPC());
simpleMessageListenerContainer.setReceiveTimeout(2000);
simpleMessageListenerContainer.setTaskExecutor(Executors.newCachedThreadPool());
return simpleMessageListenerContainer;
}
#Bean
public AsyncRabbitTemplate asyncRabbitTemplate(ConnectionFactory connectionFactory) {
return new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcReplyMessageListenerContainer(connectionFactory),
directExchange + "/" + replyRoutingKey);
}
#Bean
public DirectExchange directExchange() {
return new DirectExchange(directExchange);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(replyQueueRPC()).to(directExchange()).with(replyRoutingKey);
}
#Bean
public Subscriber subscriber() {
return new Subscriber();
}
}
Publisher.java
public class Publisher {
#Value("${routingKey.request}")
private String requestRoutingKey;
#Autowired
private DirectExchange directExchange;
private static SecureRandom SECURE_RANDOM;
static {
try {
SECURE_RANDOM = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
#Autowired
private AsyncRabbitTemplate asyncRabbitTemplate;
#Scheduled(fixedDelay = 100 * 1)
public void publishToDirectExchangeRPCStyle() {
Integer integer = SECURE_RANDOM.nextInt();
SampleRequestMessage sampleRequestMessage = new SampleRequestMessage(String.valueOf(integer));
System.out.println("Sending out message on direct directExchange:" + sampleRequestMessage);
AsyncRabbitTemplate.RabbitConverterFuture<SampleResponseMessage> sampleResponseMessageRabbitConverterFuture = asyncRabbitTemplate
.convertSendAndReceive(directExchange.getName(), requestRoutingKey, sampleRequestMessage);
sampleResponseMessageRabbitConverterFuture.addCallback(
sampleResponseMessage ->
System.out.println("Response for request message:" + sampleRequestMessage + " is:" + sampleResponseMessage)
, failure ->
System.out.println(failure.getMessage())
);
}
}
Subscriber.java
public class Subscriber {
#RabbitHandler
#RabbitListener(
bindings = {
#QueueBinding(value = #Queue("${queue.request}"),
key = "${routingKey.request}",
exchange = #Exchange(value = "${exchange.direct}", type = ExchangeTypes.DIRECT, durable = "true"))})
public SampleResponseMessage subscribeToRequestQueue(#Payload SampleRequestMessage sampleRequestMessage, Message message) {
System.out.println("Received message :" + message);
return new SampleResponseMessage(sampleRequestMessage.getMessage());
}
}
Your solution is fine.
It is not clear what you are asking...
I had to declare directExchange() and binding() beans in configuration.
Is there any way I can avoid it, because I feel it is code duplication as I am declaring these beans twice.
#QueueBinding is simply a convenience on #RabbitListener and an alternative to declaring the queue, exchange and binding as #Beans.
If you are using a common #Config class you can simply omit the bindings attribute on the listener and use queues = "${queue.reply}" to avoid the duplication.
I would need to declare similar rpcReplyMessageListenerContainer() and asyncRabbitTemplate() for each request-reply call.
Is that correct?
Yes; although with the upcoming 2.0 release, you can use a DirectReplyToMessageListenerContainer which avoids the need for a separate reply queue for each service; when you send a message.
See the documentation here and here.
Starting with version 2.0, the async template now supports Direct reply-to instead of a configured reply queue.
(Should read "as an alternative to " rather than "instead of").
So you can use the same template to talk to multiple services.