繁体   English   中英

基于 Spring Boot AMQP 的 JmsListener 在 TextMessage 上失败

[英]Spring Boot AMQP based JmsListener fails on TextMessage

我有一个 Spring Boot 应用程序,它在从 ActiveMQ 代理检索TextMessage类型的 JMS 消息时遇到问题。

如果消费者尝试从代理检索消息,它无法自动将消息转换为 TextMessage,而是将其视为 ByteMessage。 有一个 JmsListener 应该从队列中读取消息作为 TextMessage:

...
@JmsListener(destination = "foo")
public void jmsConsumer(TextMessage message) {
...

JmsListener产生如下警告,并丢弃消息:

org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method could not be invoked with incoming message
Endpoint handler details:
Method [public void net.aschemann.demo.springboot.jmsconsumer.JmsConsumer.jmsConsumer(javax.jms.TextMessage)]
Bean [net.aschemann.demo.springboot.jmsconsumer.JmsConsumer@4715f07]; nested exception is org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [javax.jms.TextMessage] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage@7c49d298, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage@7c49d298
    at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:118) ~[spring-jms-5.1.4.RELEASE.jar:5.1.4.RELEASE]

我提取了一个小示例应用程序来调试问题: https : //github.com/ascheman/springboot-camel-jms

现实生活中的生产者是一个使用 Apache Camel 的商业应用程序。 因此,我几乎无法更改/自定义制作人。 我试图构建一个显示相同行为的示例生产者。

我可以以某种方式调整消费者以将消息视为TextMessage吗?

此外:有没有办法直接在 Spring 中以编程方式从消息中检索附加的 AMQP 属性? 当然,我仍然可以将消息读取为ByteMessage并尝试解析属性。 但我正在寻找一种由任何 Spring API 支持的更简洁的方法。 Spring @Headers注释到目前为止没有帮助。

我曾经遇到过与问题所有者相同的问题,在我遵循@AndyWilkinson 的评论后,在activemq.xml 中的 transportConnector 上添加了transport.transformer选项,如下所示,问题解决了。

<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600&amp;transport.transformer=jms"/>

我有同样的错误,这是因为LazyResolutionMessage是从MessagingMessageConverter调用的,它是MessageConverter的默认实现, 它转换你的消息(实际上它没有,因为它是默认的):

return ((org.springframework.messaging.Message) payload).getPayload();

我已经完成了你想要的,最后我的消费者是这样工作的:

@JmsListener(destination = "${someName}")
public void consumeSomeMessages(MyCustomEvent e) {
  ....
}

我必须做的是:

@Bean(name = "jmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory whateverNameYouWant(final ConnectionFactory genericCF) {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setErrorHandler(t -> log.error("bad consumer, bad", t));
    factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
    factory.setConnectionFactory(genericCF);
    factory.setMessageConverter(
            new MessageConverter() {
                @Override
                public Message toMessage(Object object, Session session) {
                    throw new UnsupportedOperationException("since it's only for consuming!");
                }

                @Override
                public MyCustomEvent fromMessage(Message m) {
                    try {
                      // whatever transformation you want here...
                      // here you could print the message, try casting,
                      // building new objects with message's attributes, so on...
                      // example:
                      return (new ObjectMapper()).readValue(((TextMessage) m).getText(), MyCustomEvent.class);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
    );
    return factory;
}

几个关键点:

  1. 如果您的DefaultJmsListenerContainerFactory方法也称为jmsListenerContainerFactory ,则在Bean注释中不需要name属性

  2. 请注意,在尝试转换/转换消息类型时,您还可以实现ErrorHandler来处理异常!

  3. ConnectionFactory是带有 Amazon SQSConnectionFactory的 Spring 托管 bean,因为我想从 SQS 队列中使用。 请正确提供您的等价物。 我的是:

     @Bean("connectionFactory") public SQSConnectionFactory someOtherNome() { return new SQSConnectionFactory( new ProviderConfiguration(), AmazonSQSClientBuilder.standard() .withRegion(Regions.US_EAST_1) .withCredentials( new AWSStaticCredentialsProvider( new BasicAWSCredentials( "keyAccess", "keySecret" ) ) ) .build() ); }

如果从 byte[] 转换为 String 有问题,请使用:

.convertBodyTo(String.class)

路线示例:

from(QUEUE_URL)
                .routeId("consumer")
                .convertBodyTo(String.class)
                .log("${body}")
                .to("mock:mockRoute");

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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