我正在尝试使用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
解决方案有效,但我想避免引发异常。
我在配置中错过了什么?
答案 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
。
因此,这是一个不太麻烦的更改。