How to Bind Publisher Message to Custom class in my Receiver with out #Payload - spring-boot

In my application i am publishing Message from one of FileProcess service(which will process CSV file and convert that to CSVPojo and publish to queue by using RabbitTemplate.
rabbitTemplate.convertAndSend("spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg", "BulkSolve_GeneralrequestQueue", pojo);
I have another service BusinessProcess service that have to Listen to this queue and get messages and do some business process on those messages.To do this we intended to do this using SpringBatch, so i created a job which will listen queue and process. The trigger point for the job is as below.
#EnableRabbit
public class Eventscheduler {
#Autowired
Job csvJob;
#Autowired
private JobLauncher jobLauncher;
//#Scheduled(cron="0 */2 * ? * *")
#RabbitListener(queues ="BulkSolve_GeneralrequestQueue")
public void trigger(){
Reader.batchstatus=false;
Map<String,JobParameter> maps= new HashMap<String,JobParameter>();
maps.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(maps);
JobExecution execution=null;
try {
//JobLauncher jobLauncher = new JobLauncher();
execution=jobLauncher.run(csvJob, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobRestartException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobInstanceAlreadyCompleteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobParametersInvalidException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("JOB Executed:" + execution.getStatus());
}
}
so my job will trigger when there is a msg published to this Queue. And after my job triggered in my job iam getting exception in my reader. In reader i am getting below exception.
org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name [com.comcast.FileProcess.Pojo.CSVPojo]
Below is my Reader class which i used to read message as receiver.
#Component
public class Reader extends AmqpItemReader<List<RequestPojo>>{
#Autowired
#Qualifier("rabbitTemplate")
private RabbitTemplate rabbitTemplate;
public static boolean batchstatus;
private List<RequestPojo> reqList = new ArrayList<RequestPojo>();
/* #Autowired
private SimpleMessageListenerContainer messagelistener;*/
public Reader(AmqpTemplate rabbitTemplate) {
super(rabbitTemplate);
// TODO Auto-generated constructor stub
}
List<RequestPojo> msgList = new ArrayList<RequestPojo>();
#Override
#SuppressWarnings("unchecked")
public List<RequestPojo> read() {
if(!batchstatus){
RequestPojo msg=(RequestPojo)rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue");
//return (List<RequestPojo>) rabbitTemplate.receive();
System.out.println("I am inside Reader" );
msgList.add((RequestPojo) msg);
//Object result = rabbitTemplate.receiveAndConvert();
batchstatus=true;
return msgList;
}
return null;
}
}
Here Consumer is Getting the Pojo class with its pacakge name from publisher.
I am able to consume Messages by using #Payload below is my code using which successfully consumed messages(below is that code) but i want to consume messages by using RabbitTemplate.receiveAndConvert("QueueName") Which i showed in my Reader class.
/*Below code sucesfully consumed messages from receiver side using #Payload*/
#RabbitHandler
#RabbitListener(containerFactory = "simpleMessageListenerContainerFactory", queues ="BulkSolve_GeneralrequestQueue")
public void subscribeToRequestQueue(#Payload RequestPojo sampleRequestMessage, Message message) throws InterruptedException {
System.out.println(sampleRequestMessage.toString());
}
Can any one help on this to resolve my error to consume published messages from Receiver using RabbitTemplate.receiveAndConvert("QueueName")
As per your suggestion i have made some configuration changes as below for Jackson2JsonMessageConverter to bind the message to my custom class RequestPojo as per below but it still not bind the message to my custom class. Can you please suggest me what i am doing wrong here and suggest me what to do to make it work.
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageConverter jsonMessageConverter() {
return jsonCustomMessageConverter();
}
#Bean
public Jackson2JsonMessageConverter jsonCustomMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
#Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
idClassMapping.put("RequestPojo", RequestPojo.class);
// idClassMapping.put("bar", Bar.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
Changed as per your suggestion but getting below error .
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: org.springframework.amqp.support.converter.MessageConversionException: Cannot handle message
... 15 common frames omitted
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.comcast.BusinessProcess.Pojos.RequestPojo] for GenericMessage [payload=byte[230], headers={amqp_receivedDeliveryMode=PERSISTENT, amqp_receivedRoutingKey=BulkSolve_SummaryrequestQueue, amqp_contentEncoding=UTF-8, amqp_receivedExchange=spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg, amqp_deliveryTag=1, amqp_consumerQueue=BulkSolve_SummaryrequestQueue, amqp_redelivered=false, id=d79db57c-3cd4-d104-a343-9373215400b8, amqp_consumerTag=amq.ctag-sYwuWA5pmN07gnEUTO-p6A, contentType=application/json, __TypeId__=com.comcast.FileProcess.Pojo.CSVPojo, timestamp=1535661077865}]
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:142) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:112) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:135) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:107) ~[spring-messaging-4.3.11.RELEASE.jar!/:4.3.11.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:49) ~[spring-rabbit-1.7.4.RELEASE.jar!/:na]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:126) ~[spring-rabbit-1.7.4.RELEASE.jar!/:na]
... 14 common frames omitted
Below is my RabbitConfiguration class.
#Configuration("asyncRPCConfig")
#EnableScheduling
#EnableRabbit
public class RabbitMqConfiguration {
public static String replyQueue;
public static String directExchange;
public static String requestRoutingKey;
public static String replyRoutingKey;
//public static final int threads=3;
/*#Bean
public ExecutorService executorService(){
return Executors.newFixedThreadPool(threads);
}*/
/*#Bean
public CsvPublisher csvPublisher(){
return new CsvPublisher();
}
#Bean
public ExcelPublisher excelPublisher(){
return new ExcelPublisher();
}*/
/*#Bean
public GeneralQueuePublisher generalQueuePublisher(){
return new GeneralQueuePublisher();
}
*/
/*#Bean
public SummaryQueuePublisher summaryQueuePublisher(){
return new SummaryQueuePublisher();
}*/
/*#Bean
public Subscriber subscriber(){
return new Subscriber();
}*/
/*#Bean
public Subscriber1 subscriber1(){
return new Subscriber1();
}
#Bean
public Subscriber2 subscriber2(){
return new Subscriber2();
}
#Bean
public RestClient restClient(){
return new RestClient();
}*/
/*#Bean
public SubscriberGeneralQueue1 SubscriberGeneralQueue1(){
return new SubscriberGeneralQueue1();
}*/
/*#Bean
public SubscriberSummaryQueue1 SubscriberSummaryQueue1(){
return new SubscriberSummaryQueue1();
}*/
#Bean
public Eventscheduler Eventscheduler(){
return new Eventscheduler();
}
#Bean
public Executor taskExecutor() {
return Executors.newCachedThreadPool();
}
#Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setTaskExecutor(taskExecutor());
configurer.configure(factory, connectionFactory);
return factory;
}
/* #Bean
public SimpleRabbitListenerContainerFactory simpleMessageListenerContainerFactory_Summary(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setTaskExecutor(taskExecutor());
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 jsonCustomMessageConverter();
}
#Bean
public Jackson2JsonMessageConverter jsonCustomMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
#Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<String, Class<?>>();
idClassMapping.put("com.comcast.FileProcess.Pojo.CSVPojo", RequestPojo.class);
// idClassMapping.put("bar", Bar.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
#Bean
public Queue replyQueueRPC() {
return new Queue("BulkSolve_GeneralreplyQueue");
}
#Bean
public Queue requestQueueRPC() {
return new Queue("BulkSolve_GeneralrequestQueue");
}
/*below are the newly added method for two other queues*/
#Bean
public Queue summaryreplyQueueRPC() {
return new Queue("BulkSolve_SummaryreplyQueue");
}
#Bean
public Queue summaryrequestQueueRPC() {
return new Queue("BulkSolve_SummaryrequestQueue");
}
#Bean
public SimpleMessageListenerContainer rpcGeneralReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(replyQueueRPC());
simpleMessageListenerContainer.setTaskExecutor(taskExecutor());
//simpleMessageListenerContainer.setMessageListener(listenerAdapter1);
return simpleMessageListenerContainer;
}
#Bean
public SimpleMessageListenerContainer rpcSummaryReplyMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setQueues(summaryreplyQueueRPC());
//simpleMessageListenerContainer.setMessageListener(listenerAdapter2);
simpleMessageListenerContainer.setTaskExecutor(taskExecutor());
return simpleMessageListenerContainer;
}
/* #Bean
#Qualifier("listenerAdapter1")
MessageListenerAdapter listenerAdapter1(SubscriberGeneralQueue1 generalReceiver) {
return new MessageListenerAdapter(generalReceiver, "receivegeneralQueueMessage");
}*/
/* #Bean
#Qualifier("listenerAdapter2")
MessageListenerAdapter listenerAdapter2(SubscriberSummaryQueue1 summaryReceiver) {
return new MessageListenerAdapter(summaryReceiver, "receivesummaryQueueMessage");
}*/
#Bean
public RequestPojo requestPojo(){
return new RequestPojo();
}
/* #Bean
#Qualifier("asyncGeneralRabbitTemplate")
public AsyncRabbitTemplate asyncGeneralRabbitTemplate(ConnectionFactory connectionFactory) {
AsyncRabbitTemplate asyncGeneralRabbitTemplate = new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcGeneralReplyMessageListenerContainer(connectionFactory),
"spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg" + "/" + "BulkSolve_GeneralreplyQueue");
AsyncRabbitTemplate at = new AsyncRabbitTemplate(connectionFactory, "spring-boot-rabbitmq-examples.async_rpc", "rpc_request", "replyQueueRPC","replyQueueRPC");
return asyncGeneralRabbitTemplate;
}
template defined for other 2 queues
#Bean
#Qualifier("asyncSummaryRabbitTemplate")
public AsyncRabbitTemplate asyncSummaryRabbitTemplate(ConnectionFactory connectionFactory) {
AsyncRabbitTemplate asyncSummaryRabbitTemplate = new AsyncRabbitTemplate(rabbitTemplate(connectionFactory),
rpcSummaryReplyMessageListenerContainer(connectionFactory),
"spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg" + "/" + "BulkSolve_SummaryreplyQueue");
AsyncRabbitTemplate at = new AsyncRabbitTemplate(connectionFactory, "spring-boot-rabbitmq-examples.async_rpc", "rpc_request", "replyQueueRPC","replyQueueRPC");
return asyncSummaryRabbitTemplate;
}*/
#Bean
public DirectExchange directExchange() {
return new DirectExchange("spring-boot-rabbitmq-BulkSolve.async_BulkSolve_Msg");
}
//Added new exchange
#Bean
public DirectExchange directExchange1() {
return new DirectExchange("spring-boot-rabbitmq-BulkSolve_summary.async_BulkSolve_Msg");
}
#Bean
public List<Binding> bindings() {
return Arrays.asList(
BindingBuilder.bind(requestQueueRPC()).to(directExchange()).with("BulkSolve_GeneralrequestQueue"),
BindingBuilder.bind(replyQueueRPC()).to(directExchange()).with("BulkSolve_GeneralreplyQueue"),
BindingBuilder.bind(summaryrequestQueueRPC()).to(directExchange1()).with("BulkSolve_SummaryrequestQueue"),
BindingBuilder.bind(summaryreplyQueueRPC()).to(directExchange1()).with("BulkSolve_SummaryreplyQueue")
);
}
}
//Below is my Reader class
#Component
public class Reader extends AmqpItemReader<List<RequestPojo>>{
#Autowired
#Qualifier("rabbitTemplate")
private RabbitTemplate rabbitTemplate;
public static boolean batchstatus;
private List<RequestPojo> reqList = new ArrayList<RequestPojo>();
/* #Autowired
private SimpleMessageListenerContainer messagelistener;*/
public Reader(AmqpTemplate rabbitTemplate) {
super(rabbitTemplate);
// TODO Auto-generated constructor stub
}
List<RequestPojo> msgList = new ArrayList<RequestPojo>();
#Override
#SuppressWarnings("unchecked")
public List<RequestPojo> read() {
if(!batchstatus){
RequestPojo msg=(RequestPojo)rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue" );
//rabbitTemplate.receiveandco
//return (List<RequestPojo>) rabbitTemplate.receive();
System.out.println("I am inside Reader" + msg);
msgList.add(msg);
//Object result = rabbitTemplate.receiveAndConvert();
batchstatus=true;
return msgList;
}
return null;
}
}
Below is my Trigger point code which trigger Job when message is there in queue.
#EnableRabbit
public class Eventscheduler {
#Autowired
Job csvJob;
#Autowired
private JobLauncher jobLauncher;
//#Scheduled(cron="0 */2 * ? * *")
#RabbitListener(queues ="BulkSolve_GeneralrequestQueue")
public void trigger(){
Reader.batchstatus=false;
Map<String,JobParameter> maps= new HashMap<String,JobParameter>();
maps.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(maps);
JobExecution execution=null;
try {
//JobLauncher jobLauncher = new JobLauncher();
execution=jobLauncher.run(csvJob, jobParameters);
} catch (JobExecutionAlreadyRunningException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobRestartException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobInstanceAlreadyCompleteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JobParametersInvalidException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("JOB Executed:" + execution.getStatus());
}
}
Thanks.

As you noticed there is slight difference between subscribeToRequestQueue(#Payload RequestPojo sampleRequestMessage) and rabbitTemplate.receiveAndConvert("BulkSolve_GeneralrequestQueue"). And right, that one is exactly that #Payload RequestPojo which is missed in the raw receiveAndConvert(). Therefore when this method is performed there is no target type to consult for the expected conversion. This way we just fallback to whatever we have in the incoming message. In your case that is a __TypeId__ header with the source type from the producer com.comcast.FileProcess.Pojo.CSVPojo.
If you really would like to the enforce the conversion on the consumer side into the RequestPojo, you need to consider to use an overloaded receiveAndConvert variant:
/**
* Receive a message if there is one from a specific queue and convert it to a Java
* object. Returns immediately, possibly with a null value. Requires a
* {#link org.springframework.amqp.support.converter.SmartMessageConverter}.
*
* #param queueName the name of the queue to poll
* #param type the type to convert to.
* #param <T> the type.
* #return a message or null if there is none waiting
* #throws AmqpException if there is a problem
* #since 2.0
*/
<T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException;

Related

Spring Boot RabbitMQ ConnectException:

I have an application that should work with rabbitmq. I have RabbitMQConfig, which tries to connect with the rabbit, but if it fails to connect, the application does not start at all.
What I want to achieve is that after launching the application, it will try to connect to the rabbit and if it manages to connect, I will start functionality to create a queue and listen to it accordingly. Currently at startup if the rabbit is available and then disappears it starts throwing "java.net.ConnectException: Connection refused". Can I catch this error and how, both at startup and during the operation of the application.
This is config file:
public class RabbitMQConfig implements RabbitListenerConfigurer {
private ConnectionFactory connectionFactory;
#Autowired
public RabbitMQConfig(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}
#Bean
MessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
return messageHandlerMethodFactory;
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory);
}
#Bean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
FixedBackOff recoveryBackOff = new FixedBackOff(10000,FixedBackOff.UNLIMITED_ATTEMPTS);
factory.setRecoveryBackOff(recoveryBackOff);
configurer.configure(factory, connectionFactory);
return factory;
}
Whit this methods i create and start listener:
#Service
public class RabbitMQService {
#RabbitListener(queues = "${queueName}", autoStartup = "false", id = "commandQueue")
public void receive(CommandDataDTO commandDataDTO) {
public void receive(Object rMessage) {
doSomething(rMessage);
}
public void createQueue() {
Queue queue = new Queue("queueName"), true, false, false);
Binding binding = new Binding("queueName"),
Binding.DestinationType.QUEUE, env.getProperty("spring.rabbitmq.exchange"),"rKey",
null);
admin.declareQueue(queue);
admin.declareBinding(binding);
startListener();
}
//When queue is active we start Listener
public void startListener() {
boolean isQueuqReady = false;
while (!isQueuqReady) {
Properties p = admin.getQueueProperties(env.getProperty("management.registry.info.device-type") + "_"
+ env.getProperty("management.registry.info.device-specific-type"));
if (p != null) {
log.info("Rabbit queue is up. Start listener.");
isQueuqReady = true;
registry.getListenerContainer("commandQueue").start();
}
}
}
Problem is that I want, regardless of whether there is a connection or not with the rabbit, the application to be able to work and, accordingly, to intercept when there is a connection and when not to do different actions.

Spring Kafka dead message Queue and retries

I have a configuration:
#Configuration
#EnableKafka
public class ConsumerConfig {
final DlqErrorHandler dlqErrorHandler;
public ConsumerConfig(DlqErrorHandler dlqErrorHandler) {
this.dlqErrorHandler = dlqErrorHandler;
}
#Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
config.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, "group_id_two");
config.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(config);
}
#Bean
public ConcurrentKafkaListenerContainerFactory concurrentKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setBatchListener(false);
factory.getContainerProperties().setAckOnError(false);
factory.setConcurrency(2);
factory.setErrorHandler(dlqErrorHandler);
return factory;
}
}
There is an implementation of the error handler:
#Component
public class DlqErrorHandler implements ContainerAwareErrorHandler {
private final KafkaTemplate kafkaTemplate;
public DlqErrorHandler(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
#Override
public void handle(Exception e, List<ConsumerRecord<?, ?>> list, Consumer<?, ?> consumer, MessageListenerContainer messageListenerContainer) {
ConsumerRecord<?, ?> record = list.get(0);
try {
kafkaTemplate.send("dlqTopic", record.key(), record.value());
consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset() + 1);
} catch (Exception exception) {
consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset());
throw new KafkaException("Seek to current after exception", exception);
}
}
}
There are two listeners:
#Component
public class KafkaConsumer {
#KafkaListener(topics = "batchProcessingWithRetryPolicy", containerFactory = "concurrentKafkaListenerContainerFactory")
public void consume(String message) {
System.out.println(message + " NORMAL");
if (message.equals("TEST ERROR")) {
throw new RuntimeException("EEEEEEEEEEEERRRRRRRRRRRRRRRRRRRRRRROOOOOOOOOOOOOOOOOOORRRRRR");
}
}
#KafkaListener(topics = "dlqTopic", containerFactory = "concurrentKafkaListenerContainerFactory")
public void consumeTwo(String message) {
System.out.println(message + " DQL");
if (message.length() > 0) {
throw new RuntimeException("EEEEEEEEEEEERRRRRRRRRRRRRRRRRRRRRRROOOOOOOOOOOOOOOOOOORRRRRR ");
}
}
}
My question:
1)
factory.getContainerProperties().setAckOnError(false);
Method setAckOnError - deprecated. How can I replace this line of code so that the first listener after an error when processing a message does not make repeated attempts, but sends this message to DQL.
How do I set a limit for DQL (DlqErrorHandler) on repetitions and time intervals between sending messages? That is, after the first error, the message appears in DQL, then I want to make 3 more attempts with an interval of 30 seconds and if it does not work, then go further.
ackOnError is replaced by ErrorHandler.isAckAfterHandle().
Your error handler implementation is incomplete - you need to seek the other partitions in the remaining records list as well.
Why don't you just use the SeekToCurrentErrorHandler and DeadLetterPublishingRecoverer provided by the framework. They support your use case.

How to receive and reply on Spring

I'm trying to deploy a RPC (request/reply pattern) and I'm using RabbitMQ and Spring in the server side because I need dynamic consumers. I can configurate dynamic consumers with SimpleMessageListenerContainer but i don't know how to reply my message.
Here is my class configuration:
#Configuration
public class dynamicConsumerConfig {
private static Properties prop = new Properties();
public static void setPropValues() throws IOException {
File configFile = new File("src/main/resources/config.properties");
InputStream inStream = new FileInputStream(configFile.getAbsolutePath());
prop.load(inStream);
}
#Bean
public Queue slowQueue() {
return new Queue("slowQueue");
}
#Bean
public Queue fastQueue() {
return new Queue("fastQueue");
}
#Bean
public DirectExchange exchange1() {
return new DirectExchange("pdfqueues");
}
#Bean
public Binding slowBind(DirectExchange exchange, Queue slowQueue) {
return BindingBuilder.bind(slowQueue)
.to(exchange)
.with("slow");
}
#Bean
public Binding fastBind(DirectExchange exchange, Queue fastQueue) {
return BindingBuilder.bind(fastQueue)
.to(exchange)
.with("fast");
}
#Bean
public ConnectionFactory connect() throws IOException {
setPropValues();
CachingConnectionFactory connection = new CachingConnectionFactory();
connection.setHost(prop.getProperty("HOST"));
connection.setUsername(prop.getProperty("USER"));
connection.setPassword(prop.getProperty("PASS"));
connection.setPort(Integer.parseInt(prop.getProperty("PORT")));
return connection;
}
#Bean
public SimpleMessageListenerContainer container1(ConnectionFactory connection) throws IOException {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
setPropValues();
container.setConnectionFactory(connection);
container.setQueueNames("slowQueue");
container.setMessageListener(firstListener());
container.setMaxConcurrentConsumers(8);
container.setConcurrentConsumers(1);
container.setConsecutiveActiveTrigger(1);
container.setConsecutiveIdleTrigger(1);
container.setTxSize(1);
container.setPrefetchCount(1);
return container;
}
#Bean
public MessageListener firstListener()
{
return new MessageListener() {
#Override
public void onMessage(Message message) {
PdfBoxService pdfboxservice = new PdfBoxService(prop.getProperty("tmpPath"),prop.getProperty("imagicPath"),prop.getProperty("resources"),
prop.getProperty("tessdata"),prop.getProperty("languages"));
String picture = new String(message.getBody(), StandardCharsets.UTF_8);
List<ImagePair> lip = null;
try {
lip = new ArrayList<ImagePair>();
lip.add(new ImagePair("JPG", picture));
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
ByteArrayOutputStream output= pdfboxservice.ImgToPdf(lip, false, false, false, 1, 1);
} catch (IOException | InterruptedException | TransformerException | BadFieldValueException
| TesseractException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
In the fuction firstListener() i get the message. In this case is a picture. The picture is converted from JPG to PDF. The PDF is stored in outputvariable.
I need to reply this output in other queue but i don't have tools for do it.
I think that my code is a bad pattern but I don't know how to do a RPC pattern with dynamic consumers using SimpleMessageListenerContainer.
Use a MessageListenerAdapter with a POJO method that returns a result instead of implementing MessageListener yourself.
Starting with version 2.0, a convenient FunctionalInterface has been provided:
#FunctionalInterface
public interface ReplyingMessageListener<T, R> {
R handleMessage(T t);
}
This facilitates convenient configuration of the adapter using Java 8 lamdas:
new MessageListenerAdapter((ReplyingMessageListener<String, String>) data -> {
...
return result;
}));

How to build a nonblocking Consumer when using AsyncRabbitTemplate with Request/Reply Pattern

I'm new to rabbitmq and currently trying to implement a nonblocking producer with a nonblocking consumer. I've build some test producer where I played around with typereference:
#Service
public class Producer {
#Autowired
private AsyncRabbitTemplate asyncRabbitTemplate;
public <T extends RequestEvent<S>, S> RabbitConverterFuture<S> asyncSendEventAndReceive(final T event) {
return asyncRabbitTemplate.convertSendAndReceiveAsType(QueueConfig.EXCHANGE_NAME, event.getRoutingKey(), event, event.getResponseTypeReference());
}
}
And in some other place the test function that gets called in a RestController
#Autowired
Producer producer;
public void test() throws InterruptedException, ExecutionException {
TestEvent requestEvent = new TestEvent("SOMEDATA");
RabbitConverterFuture<TestResponse> reply = producer.asyncSendEventAndReceive(requestEvent);
log.info("Hello! The Reply is: {}", reply.get());
}
This so far was pretty straightforward, where I'm stuck now is how to create a consumer which is non-blocking too. My current listener:
#RabbitListener(queues = QueueConfig.QUEUENAME)
public TestResponse onReceive(TestEvent event) {
Future<TestResponse> replyLater = proccessDataLater(event.getSomeData())
return replyLater.get();
}
As far as I'm aware, when using #RabbitListener this listener runs in its own thread. And I could configure the MessageListener to use more then one thread for the active listeners. Because of that, blocking the listener thread with future.get() is not blocking the application itself. Still there might be the case where all threads are blocking now and new events are stuck in the queue, when they maybe dont need to. What I would like to do is to just receive the event without the need to instantly return the result. Which is probably not possible with #RabbitListener. Something like:
#RabbitListener(queues = QueueConfig.QUEUENAME)
public void onReceive(TestEvent event) {
/*
* Some fictional RabbitMQ API call where i get a ReplyContainer which contains
* the CorrelationID for the event. I can call replyContainer.reply(testResponse) later
* in the code without blocking the listener thread
*/
ReplyContainer replyContainer = AsyncRabbitTemplate.getReplyContainer()
// ProcessDataLater calls reply on the container when done with its action
proccessDataLater(event.getSomeData(), replyContainer);
}
What is the best way to implement such behaviour with rabbitmq in spring?
EDIT Config Class:
#Configuration
#EnableRabbit
public class RabbitMQConfig implements RabbitListenerConfigurer {
public static final String topicExchangeName = "exchange";
#Bean
TopicExchange exchange() {
return new TopicExchange(topicExchangeName);
}
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("localhost");
return connectionFactory;
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory());
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
return rabbitTemplate;
}
#Bean
public AsyncRabbitTemplate asyncRabbitTemplate() {
return new AsyncRabbitTemplate(rabbitTemplate());
}
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
Queue queue() {
return new Queue("test", false);
}
#Bean
Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("foo.#");
}
#Bean
public SimpleRabbitListenerContainerFactory myRabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(rabbitConnectionFactory());
factory.setMaxConcurrentConsumers(5);
factory.setMessageConverter(producerJackson2MessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
#Override
public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) {
registrar.setContainerFactory(myRabbitListenerContainerFactory());
}
}
I don't have time to test it right now, but something like this should work; presumably you don't want to lose messages so you need to set the ackMode to MANUAL and do the acks yourself (as shown).
UPDATE
#SpringBootApplication
public class So52173111Application {
private final ExecutorService exec = Executors.newCachedThreadPool();
#Autowired
private RabbitTemplate template;
#Bean
public ApplicationRunner runner(AsyncRabbitTemplate asyncTemplate) {
return args -> {
RabbitConverterFuture<Object> future = asyncTemplate.convertSendAndReceive("foo", "test");
future.addCallback(r -> {
System.out.println("Reply: " + r);
}, t -> {
t.printStackTrace();
});
};
}
#Bean
public AsyncRabbitTemplate asyncTemplate(RabbitTemplate template) {
return new AsyncRabbitTemplate(template);
}
#RabbitListener(queues = "foo")
public void listen(String in, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long tag,
#Header(AmqpHeaders.CORRELATION_ID) String correlationId,
#Header(AmqpHeaders.REPLY_TO) String replyTo) {
ListenableFuture<String> future = handleInput(in);
future.addCallback(result -> {
Address address = new Address(replyTo);
this.template.convertAndSend(address.getExchangeName(), address.getRoutingKey(), result, m -> {
m.getMessageProperties().setCorrelationId(correlationId);
return m;
});
try {
channel.basicAck(tag, false);
}
catch (IOException e) {
e.printStackTrace();
}
}, t -> {
t.printStackTrace();
});
}
private ListenableFuture<String> handleInput(String in) {
SettableListenableFuture<String> future = new SettableListenableFuture<String>();
exec.execute(() -> {
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
future.set(in.toUpperCase());
});
return future;
}
public static void main(String[] args) {
SpringApplication.run(So52173111Application.class, args);
}
}

Spring Rabbit MQ Publish and Acknowledge

In my project i am having 3 Classes App(Publisher), PojoListener(Receiver) , Config(bind both App and PojoListner). But i need to separate out the PojoListener from my project and deploy somewhere else so that my Listener class will continuous listen to the Rabbit Mq and ack the messages back to queue and then to App Class for any messages published by my App class.
As my config file is common for both Publisher and Receiver. Is there any way to seperate out them. I need to deploy my receiver on different server and Publisher on different. Both will listen to common queues.
**App.java** :
public class App {
public static void main(String[] args) throws UnsupportedEncodingException {
ApplicationContext context = new AnnotationConfigApplicationContext(RabbitMQConfig.class);
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
String response = (String) rabbitTemplate.convertSendAndReceive("message from publisher");
System.out.println("message sent");
System.out.println("response from :" + response);
}
}
PojoListener.java :
public class PojoListener {
public String handleMessage(String msg) {
System.out.println("IN POJO RECEIVER!!!");
return msg.toUpperCase();
}
}
RabbitMQConfig.java :
#Configuration
public class RabbitMQConfig {
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
#Bean
public RabbitTemplate fixedReplyQRabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(rabbitConnectionFactory());
template.setExchange(ex().getName());
template.setRoutingKey("test");
template.setReplyQueue(replyQueue());
template.setReplyTimeout(60000);
return template;
}
#Bean
public SimpleMessageListenerContainer replyListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.setQueues(replyQueue());
container.setMessageListener(fixedReplyQRabbitTemplate());
return container;
}
#Bean
public SimpleMessageListenerContainer serviceListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.setQueues(requestQueue());
container.setMessageListener(new MessageListenerAdapter(new PojoListener()));
return container;
}
#Bean
public DirectExchange ex() {
return new DirectExchange("rabbit-exchange", false, true);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(requestQueue()).to(ex()).with("test");
}
#Bean
public Queue requestQueue() {
return new Queue("request-queue-spring");
}
#Bean
public Queue replyQueue() {
return new Queue("response-queue-spring");
}
#Bean
public RabbitAdmin admin() {
return new RabbitAdmin(rabbitConnectionFactory());
}
}

Resources