设置手动确认SQS消息的Spring Cloud AWS问题

时间:2018-10-17 13:50:57

标签: amazon-sqs spring-cloud-aws

我正在尝试使用spring-cloud-aws-messaging手动删除AWS SQS消息来实现逻辑。此功能已在this ticket from the example in tests

范围内实现
@SqsListener(value = "queueName", deletionPolicy = SqsMessageDeletionPolicy.NEVER)
public void listen(SqsEventDTO message, Acknowledgment acknowledgment) {

    LOGGER.info("Received message {}", message.getFoo());
    try {
        acknowledgment.acknowledge().get();
    } catch (InterruptedException e) {
        LOGGER.error("Opps", e);
    } catch (ExecutionException e) {
        LOGGER.error("Opps", e);
    }
}

但遇到意外的异常

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of org.springframework.cloud.aws.messaging.listener.Acknowledgment (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

使用SqsMessageDeletionPolicy.ON_SUCCESS解决方案有效,但我想避免引发异常。

我在配置中错过了什么?

2 个答案:

答案 0 :(得分:1)

摆弄了一些东西,尝试了与其他SO答案不同的事情。

这是我的代码,我将尽力解释。我包括了我用于SQS消费者的所有内容。

我的配置类如下。下面仅需注意的一点不是在queueMessageHandlerFactory方法中实例化的转换器和解析器对象。 MappingJackson2MessageConverter(如果从如此明显的类名中看不到)将处理来自SQS的有效负载的反序列化。

将严格的内容类型匹配设置为false也很重要。

此外,MappingJackson2MessageConverter允许您设置自己的Jackson ObjectMapper,但是如果这样做,则需要按以下方式进行配置:

objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

您可能不想这样做,因此可以将其保留为空,它将创建自己的ObjectMapper。

我认为代码的其余部分很容易解释...?让我知道是否可以。

我们的用例之间的一个区别是,看起来您正在映射自己的自定义对象(SqsEventDTO),并且我认为这可行吗?在那种情况下,我认为您不需要MappingJackson2MessageConverter,但我可能是错的。

@Configuration
public class AppConfig {

@Bean
@Primary
public QueueMessageHandler queueMessageHandler(@Autowired QueueMessageHandlerFactory queueMessageHandlerFactory) {
    return queueMessageHandlerFactory.createQueueMessageHandler();
}

@Bean
@Primary
public QueueMessageHandlerFactory queueMessageHandlerFactory(@Autowired AmazonSQSAsync sqsClient) {

    QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
    factory.setAmazonSqs(sqsClient);

    MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter();
    messageConverter.setSerializedPayloadClass(String.class);

    //set strict content type match to false
    messageConverter.setStrictContentTypeMatch(false);

    // Uses the MappingJackson2MessageConverter object to resolve/map 
    // the payload against the Message/S3EventNotification argument.
    PayloadArgumentResolver payloadResolver = new PayloadArgumentResolver(messageConverter);

    // Extract the acknowledgment data from the payload's headers, 
    // which then gets deserialized into the Acknowledgment object.  
    AcknowledgmentHandlerMethodArgumentResolver acknowledgmentResolver = new AcknowledgmentHandlerMethodArgumentResolver("Acknowledgment");

    // I don't remember the specifics of WHY, however there is 
    // something important about the order of the argument resolvers 
    // in the list
    factory.setArgumentResolvers(Arrays.asList(acknowledgmentResolver, payloadResolver));

    return factory;
}

@Bean("ConsumerBean")
@Primary
public SimpleMessageListenerContainer simpleMessageListenerContainer(@Autowired AmazonSQSAsync amazonSQSAsync, @Autowired QueueMessageHandler queueMessageHandler,
    @Autowired ThreadPoolTaskExecutor threadPoolExecutor) {

    SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
    smlc.setWaitTimeOut(20);
    smlc.setAmazonSqs(amazonSQSAsync);
    smlc.setMessageHandler(queueMessageHandler);
    smlc.setBeanName("ConsumerBean");
    smlc.setMaxNumberOfMessages(sqsMaxMessages);
    smlc.setTaskExecutor(threadPoolExecutor);

    return smlc;
}

@Bean
@Primary
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    executor.setCorePoolSize(corePoolSize);
    executor.setAllowCoreThreadTimeOut(coreThreadsTimeout);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setMaxPoolSize(maxPoolSize);
    executor.setKeepAliveSeconds(threadTimeoutSeconds);
    executor.setThreadNamePrefix(threadName);
    executor.initialize();

    return executor;
}
}

我的SQS消费者服务类别如下。

@Service
public class RawConsumer {

@SqsListener(deletionPolicy = SqsMessageDeletionPolicy.NEVER, value = "${input.sqs.queuename}")
public void sqsListener(S3EventNotification event, Acknowledgment ack) throws Exception {
    // Handle event here
}

希望对您有帮助。

答案 1 :(得分:1)

问题作者没有提到的是他试图自定义Jackson ObjectMapper。因此,他实例化了MappingJackson2MessageConverter,将其包装在PayloadArgumentResolver中,并将其设置为HandlerMethodArgumentResolver上的单个QueueMessageHandlerFactory.setArgumentResolvers()。这样做会覆盖QueueMessageHandler.initArgumentResolvers()中定义的默认参数解析器的列表(在QueueMessageHandler内创建QueueMessageHandlerFactory的实例时调用该参数)。

例如仅将PayloadArgumentResolver设置为单个参数解析器,Acknowledgement参数将不再受约束。

与覆盖用于自定义Jackson消息转换器的参数解析器列表相比,更好的解决方案是在QueueMessageHandlerFactory上设置消息转换器列表:

    @Bean
    fun queueMessageHandlerFactory(objectMapper: ObjectMapper): QueueMessageHandlerFactory {
        val factory = QueueMessageHandlerFactory()

        val messageConverter = MappingJackson2MessageConverter()
        messageConverter.objectMapper = objectMapper

        factory.setMessageConverters(listOf(messageConverter)) // <-- this is the important line.
        return factory
    }

注册的MessageConverters位于QueueMessageHandler.initArgumentResolvers()内部,用作PayloadArgumentResolvers

因此,这是一个不太麻烦的更改。