简体   繁体   中英

Spring rabbitmq how to do manually ack on Error handler , On throwing specific required exception, it only ack when Acknowledge is set to AUTO

We are using Spring rabbitmq for out project. And Currently for the listener, we are using Acknowledge mode to manual. In this case, when an exception has occurred, We are unable to send acknowledge to rabbitMQ.

Our Code is: package com.highq.listener;

import java.io.IOException;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpoint;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.util.ErrorHandler;
import com.highq.workflow.helper.RuleEngine;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;

/**
 * The Class RabbitMQListenerConfig.
 */
@Configuration
@Slf4j
@EnableRabbit
public class RabbitMQListenerConfig
{

    /** The rule engine. */
    @Autowired
    RuleEngine ruleEngine;

    private RabbitAdmin currentQueueAdmin = null;

    public RabbitAdmin getRabbitAdmin()
    {
        return currentQueueAdmin;
    }

    /**
     * This method will listen message from rabbitmq.
     *
     * @param message the message
     * @param channel - Channel created for particular listener
     * @param queue - Specified queue to which this listener will listen message
     * @param deliveryTag - Delivery Tag to be used when sending acknowledgement
     * @throws IOException - Exception in case of sending acknowledgement
     */
    @RabbitListener(queues = "${queue.name}")
    public void listen(byte[] msg, Channel channel, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException
    {
        try
        {
            String message = new String(msg, "UTF-8");
            log.info(message);
            ruleEngine.evaluateRule(message);
        }
        catch (Exception e)
        {
            log.error(e.toString());
            log.error(e.getMessage(), e);
        }
        finally
        {
            channel.basicAck(deliveryTag, false);
        }

    }

    /**
     * Error handler.
     *
     * @return the error handler
     */
    @Bean
    public ErrorHandler errorHandler()
    {
        return new RabbitExceptionHandler(new RabbitFatalExceptionStrategy(), this.currentQueueAdmin);
    }

    /**
     * Bean will create from this with given name.
     *
     * @param name - Queue name- will be instance id
     * @return the queue
     */
    @Bean
    public Queue queue(@Value("${queue.name}") String name)
    {
        return new Queue(name);
    }

    /**
     * Rabbit listener container factory.
     *
     * @param connectionFactory the connection factory
     * @return the direct rabbit listener container factory
     */
    @Bean
    public DirectRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory)
    {
        DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
        factory.setPrefetchCount(1);
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        factory.setConnectionFactory(connectionFactory);
        factory.setErrorHandler(errorHandler());
        return factory;
    }

    @Bean
    public RabbitListenerAroundAdvice rabbitListenerAroundAdvice()
    {
        return new RabbitListenerAroundAdvice();
    }

    /**
     * RabbitAdmin Instance will be created which is required to create new Queue.
     *
     * @param cf - Connection factory
     * @return the rabbit admin
     */
    @Bean
    public RabbitAdmin admin(ConnectionFactory cf)
    {
        this.currentQueueAdmin = new RabbitAdmin(cf);
        return this.currentQueueAdmin;
    }

}

@Slf4j public class RabbitExceptionHandler extends ConditionalRejectingErrorHandler { private RabbitAdmin rabbitAdmin;

public RabbitExceptionHandler()
{
    super();
}

/**
 * Create a handler with the supplied {@link FatalExceptionStrategy} implementation.
 * 
 * @param exceptionStrategy The strategy implementation.
 */
public RabbitExceptionHandler(FatalExceptionStrategy exceptionStrategy, RabbitAdmin rabbitAdmin)
{
    super(exceptionStrategy);
    this.rabbitAdmin = rabbitAdmin;
}

@Override
public void handleError(Throwable t)
{
    try
    {
        super.handleError(t);
    }
    catch (Exception e)
    {
        if (e instanceof AmqpRejectAndDontRequeueException)
        {
            Throwable t1 = new Throwable("Some message", e.getCause());
            log.error(e.getMessage(), e);
            ImmediateAcknowledgeAmqpException newAmqp = new ImmediateAcknowledgeAmqpException("Some message", t1);
            throw newAmqp;
        }
    }
}

}

package com.highq.listener;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;

/**
 * The Class RabbitFatalExceptionStrategy.
 */
@Slf4j
public class RabbitFatalExceptionStrategy extends ConditionalRejectingErrorHandler.DefaultExceptionStrategy
{

    /*
     * (non-Javadoc)
     * @see org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler.DefaultExceptionStrategy#isFatal(java.lang.Throwable)
     */
    @Override
    public boolean isFatal(Throwable t)
    {
        boolean finalResult = super.isFatal(t);
        if (t instanceof ListenerExecutionFailedException)
        {
            ListenerExecutionFailedException lefe = (ListenerExecutionFailedException) t;

            lefe.getFailedMessage().getMessageProperties().getDeliveryTag());
            log.error("\n   Error occured while handling message with Queue named : "
                    + 
            lefe.getFailedMessage().getMessageProperties().getConsumerQueue() + "   Message not processed is FATAL or NOT : " + finalResult
                    + ";\n  Failed message: " + lefe.getFailedMessage());
        }
        if (!log.isWarnEnabled() || !finalResult)
        {
            log.error(t.getMessage(), t);
        }
        return true;
    }

}

In above case, we did not get channel anywhere in our exception handler, So How we can manually ack when an exception occurs.

You cannot; when using manual acks, all of the acknowledgment logic needs to be in the listener.

It is quite rare to use manual acks since the container, generally, provides all the flexibility you need with AUTO.

What is the use case that requires the use of MANUAL acks?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM