Post process outgoing messages in JMS clients

Prior to this commit, the `JmsTemplate` would use `MessagePostProcessor`
for mutating JMS messages before they are being sent, but only if the
method takes a post processor as an argument.
The main use case so far is to mutate messages after they've been
created by a `MessageConverter` from a payload.

This commit updates the `JmsClient` to use `MessagePostProcessor` more
broadly, for all outgoing messages (converted or not). This brings an
interception-like mechanism for clients to enrich the message before
being sent.

This change also updates the `JmsClient` static factories and
introduces a Builder, allowing for more configuration options: multiple
message converters and message post processors.

Closes gh-35271
This commit is contained in:
Brian Clozel 2025-08-01 15:40:32 +02:00
parent 149d468ce4
commit ec87d90c9b
11 changed files with 498 additions and 109 deletions

View File

@ -9,39 +9,7 @@ that takes no destination argument uses the default destination.
The following example uses the `MessageCreator` callback to create a text message from the
supplied `Session` object:
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.Queue;
import jakarta.jms.Session;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;
public class JmsQueueSender {
private JmsTemplate jmsTemplate;
private Queue queue;
public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate(cf);
}
public void setQueue(Queue queue) {
this.queue = queue;
}
public void simpleSend() {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}
----
include-code::./JmsQueueSender[]
In the preceding example, the `JmsTemplate` is constructed by passing a reference to a
`ConnectionFactory`. As an alternative, a zero-argument constructor and
@ -84,21 +52,7 @@ gives you access to the message after it has been converted but before it is sen
following example shows how to modify a message header and a property after a
`java.util.Map` is converted to a message:
[source,java,indent=0,subs="verbatim,quotes"]
----
public void sendWithConversion() {
Map<String, String> map = new HashMap<>();
map.put("Name", "Mark");
map.put("Age", new Integer(47));
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}
----
include-code::./JmsSenderWithConversion[]
This results in a message of the following form:
@ -126,32 +80,6 @@ to `jakarta.jms.TextMessage`, `jakarta.jms.BytesMessage`, etc. For a contract su
generic message payloads, use `org.springframework.messaging.converter.MessageConverter`
with `JmsMessagingTemplate` or preferably `JmsClient` as your central delegate instead.
[[jms-sending-jmsclient]]
== Sending a Message with `JmsClient`
[source,java,indent=0,subs="verbatim,quotes"]
----
// Reusable handle, typically created through JmsClient.create(ConnectionFactory)
// For custom conversion, use JmsClient.create(ConnectionFactory, MessageConverter)
private JmsClient jmsClient;
public void sendWithConversion() {
this.jmsClient.destination("myQueue")
.withTimeToLive(1000)
.send("myPayload"); // optionally with a headers Map next to the payload
}
public void sendCustomMessage() {
Message<?> message =
MessageBuilder.withPayload("myPayload").build(); // optionally with headers
this.jmsClient.destination("myQueue")
.withTimeToLive(1000)
.send(message);
}
----
[[jms-sending-callbacks]]
== Using `SessionCallback` and `ProducerCallback` on `JmsTemplate`
@ -160,3 +88,21 @@ want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run
these callback methods.
[[jms-sending-jmsclient]]
== Sending a Message with `JmsClient`
include-code::./JmsClientSample[]
[[jms-sending-postprocessor]]
== Post-processing outgoing messages
Applications often need to intercept messages before they are sent out, for example to add message properties to all outgoing messages.
The `org.springframework.messaging.core.MessagePostProcessor` based on the spring-messaging `Message` can do that,
when configured on the `JmsClient`. It will be used for all outgoing messages sent with the `send` and `sendAndReceive` methods.
Here is an example of an interceptor adding a "tenantId" property to all outgoing messages.
include-code::./JmsClientWithPostProcessor[]

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.integration.jms.jmssending;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.Queue;
import jakarta.jms.Session;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;
public class JmsQueueSender {
private JmsTemplate jmsTemplate;
private Queue queue;
public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate(cf);
}
public void setQueue(Queue queue) {
this.queue = queue;
}
public void simpleSend() {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.integration.jms.jmssendingconversion;
import java.util.HashMap;
import java.util.Map;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessagePostProcessor;
public class JmsSenderWithConversion {
private JmsTemplate jmsTemplate;
public void sendWithConversion() {
Map<String, Object> map = new HashMap<>();
map.put("Name", "Mark");
map.put("Age", 47);
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.integration.jms.jmssendingjmsclient;
import jakarta.jms.ConnectionFactory;
import org.springframework.jms.core.JmsClient;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
public class JmsClientSample {
private final JmsClient jmsClient;
public JmsClientSample(ConnectionFactory connectionFactory) {
// For custom options, use JmsClient.builder(ConnectionFactory)
this.jmsClient = JmsClient.create(connectionFactory);
}
public void sendWithConversion() {
this.jmsClient.destination("myQueue")
.withTimeToLive(1000)
.send("myPayload"); // optionally with a headers Map next to the payload
}
public void sendCustomMessage() {
Message<?> message = MessageBuilder.withPayload("myPayload").build(); // optionally with headers
this.jmsClient.destination("myQueue")
.withTimeToLive(1000)
.send(message);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.integration.jms.jmssendingpostprocessor;
import jakarta.jms.ConnectionFactory;
import org.springframework.jms.core.JmsClient;
import org.springframework.messaging.Message;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.support.MessageBuilder;
public class JmsClientWithPostProcessor {
private final JmsClient jmsClient;
public JmsClientWithPostProcessor(ConnectionFactory connectionFactory) {
this.jmsClient = JmsClient.builder(connectionFactory)
.messagePostProcessor(new TenantIdMessageInterceptor("42"))
.build();
}
public void sendWithPostProcessor() {
this.jmsClient.destination("myQueue")
.withTimeToLive(1000)
.send("myPayload");
}
static class TenantIdMessageInterceptor implements MessagePostProcessor {
private final String tenantId;
public TenantIdMessageInterceptor(String tenantId) {
this.tenantId = tenantId;
}
@Override
public Message<?> postProcessMessage(Message<?> message) {
return MessageBuilder.fromMessage(message)
.setHeader("tenantId", this.tenantId)
.build();
}
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.jms.support.JmsAccessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.util.Assert;
/**
@ -34,28 +35,38 @@ import org.springframework.util.Assert;
* as created by the static factory methods.
*
* @author Juergen Hoeller
* @author Brian Clozel
* @since 7.0
* @see JmsClient#create(ConnectionFactory)
* @see JmsClient#create(ConnectionFactory, MessageConverter)
* @see JmsClient#create(JmsOperations)
* @see JmsClient#create(JmsOperations, MessageConverter)
*/
class DefaultJmsClient implements JmsClient {
private final JmsOperations jmsTemplate;
private final @Nullable MessageConverter messageConverter;
private @Nullable MessageConverter messageConverter;
private @Nullable MessagePostProcessor messagePostProcessor;
public DefaultJmsClient(ConnectionFactory connectionFactory, @Nullable MessageConverter messageConverter) {
public DefaultJmsClient(ConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
this.jmsTemplate = new JmsTemplate(connectionFactory);
}
public DefaultJmsClient(JmsOperations jmsTemplate) {
Assert.notNull(jmsTemplate, "JmsTemplate must not be null");
this.jmsTemplate = jmsTemplate;
}
void setMessageConverter(MessageConverter messageConverter) {
Assert.notNull(messageConverter, "MessageConverter must not be null");
this.messageConverter = messageConverter;
}
public DefaultJmsClient(JmsOperations jmsTemplate, @Nullable MessageConverter messageConverter) {
Assert.notNull(jmsTemplate, "JmsTemplate must not be null");
this.jmsTemplate = jmsTemplate;
this.messageConverter = messageConverter;
void setMessagePostProcessor(MessagePostProcessor messagePostProcessor) {
Assert.notNull(messagePostProcessor, "MessagePostProcessor must not be null");
this.messagePostProcessor = messagePostProcessor;
}
@ -141,17 +152,18 @@ class DefaultJmsClient implements JmsClient {
@Override
public void send(Message<?> message) throws MessagingException {
message = postProcessMessage(message);
this.delegate.send(message);
}
@Override
public void send(Object payload) throws MessagingException {
this.delegate.convertAndSend(payload);
this.delegate.convertAndSend(payload, DefaultJmsClient.this.messagePostProcessor);
}
@Override
public void send(Object payload, Map<String, Object> headers) throws MessagingException {
this.delegate.convertAndSend(payload, headers);
this.delegate.convertAndSend(payload, headers, DefaultJmsClient.this.messagePostProcessor);
}
@Override
@ -176,19 +188,27 @@ class DefaultJmsClient implements JmsClient {
@Override
public Optional<Message<?>> sendAndReceive(Message<?> requestMessage) throws MessagingException {
requestMessage = postProcessMessage(requestMessage);
return Optional.ofNullable(this.delegate.sendAndReceive(requestMessage));
}
@Override
public <T> Optional<T> sendAndReceive(Object request, Class<T> targetClass) throws MessagingException {
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, targetClass));
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, targetClass, DefaultJmsClient.this.messagePostProcessor));
}
@Override
public <T> Optional<T> sendAndReceive(Object request, Map<String, Object> headers, Class<T> targetClass)
throws MessagingException {
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, headers, targetClass));
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, headers, targetClass, DefaultJmsClient.this.messagePostProcessor));
}
private Message<?> postProcessMessage(Message<?> message) {
if (DefaultJmsClient.this.messagePostProcessor != null) {
return DefaultJmsClient.this.messagePostProcessor.postProcessMessage(message);
}
return message;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jms.core;
import java.util.ArrayList;
import java.util.List;
import jakarta.jms.ConnectionFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.core.CompositeMessagePostProcessor;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.util.Assert;
/**
* Default implementation of {@link JmsClient.Builder}.
* @author Brian Clozel
* @since 7.0
* @see JmsClient#builder(ConnectionFactory)
* @see JmsClient#builder(JmsOperations)
*/
class DefaultJmsClientBuilder implements JmsClient.Builder {
private final DefaultJmsClient jmsClient;
private @Nullable List<MessageConverter> messageConverters;
private @Nullable List<MessagePostProcessor> messagePostProcessors;
DefaultJmsClientBuilder(ConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
this.jmsClient = new DefaultJmsClient(connectionFactory);
}
DefaultJmsClientBuilder(JmsOperations jmsTemplate) {
Assert.notNull(jmsTemplate, "JmsOperations must not be null");
this.jmsClient = new DefaultJmsClient(jmsTemplate);
}
@Override
public JmsClient.Builder messageConverter(MessageConverter messageConverter) {
Assert.notNull(messageConverter, "MessageConverter must not be null");
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
}
this.messageConverters.add(messageConverter);
return this;
}
@Override
public JmsClient.Builder messagePostProcessor(MessagePostProcessor messagePostProcessor) {
Assert.notNull(messagePostProcessor, "MessagePostProcessor must not be null");
if (this.messagePostProcessors == null) {
this.messagePostProcessors = new ArrayList<>();
}
this.messagePostProcessors.add(messagePostProcessor);
return this;
}
@Override
public JmsClient build() {
if (this.messageConverters != null) {
this.jmsClient.setMessageConverter(new CompositeMessageConverter(this.messageConverters));
}
if (this.messagePostProcessors != null) {
this.jmsClient.setMessagePostProcessor(new CompositeMessagePostProcessor(this.messagePostProcessors));
}
return this.jmsClient;
}
}

View File

@ -25,6 +25,7 @@ import jakarta.jms.Destination;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.core.MessagePostProcessor;
/**
* A fluent {@code JmsClient} with common send and receive operations against a JMS
@ -73,6 +74,7 @@ import org.springframework.messaging.converter.MessageConverter;
* </pre>
*
* @author Juergen Hoeller
* @author Brian Clozel
* @since 7.0
* @see JmsTemplate
* @see JmsMessagingTemplate
@ -103,16 +105,7 @@ public interface JmsClient {
* @param connectionFactory the factory to obtain JMS connections from
*/
static JmsClient create(ConnectionFactory connectionFactory) {
return new DefaultJmsClient(connectionFactory, null);
}
/**
* Create a new {@code JmsClient} for the given {@link ConnectionFactory}.
* @param connectionFactory the factory to obtain JMS connections from
* @param messageConverter the message converter for payload objects
*/
static JmsClient create(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
return new DefaultJmsClient(connectionFactory, messageConverter);
return new DefaultJmsClient(connectionFactory);
}
/**
@ -121,17 +114,56 @@ public interface JmsClient {
* (can be a custom {@link JmsOperations} implementation as well)
*/
static JmsClient create(JmsOperations jmsTemplate) {
return new DefaultJmsClient(jmsTemplate, null);
return new DefaultJmsClient(jmsTemplate);
}
/**
* Create a new {@code JmsClient} for the given {@link JmsOperations}.
* Obtain a {@code JmsClient} builder that will use the given connection
* factory for JMS connections.
* @param connectionFactory the factory to obtain JMS connections from
* @return a {@code JmsClient} builder that uses the given connection factory.
*/
static Builder builder(ConnectionFactory connectionFactory) {
return new DefaultJmsClientBuilder(connectionFactory);
}
/**
* Obtain a {@code JmsClient} builder based on the configuration of the
* given {@code JmsTemplate}.
* @param jmsTemplate the {@link JmsTemplate} to use for performing operations
* (can be a custom {@link JmsOperations} implementation as well)
* @param messageConverter the message converter for payload objects
* @return a {@code JmsClient} builder that uses the given JMS template.
*/
static JmsClient create(JmsOperations jmsTemplate, MessageConverter messageConverter) {
return new DefaultJmsClient(jmsTemplate, messageConverter);
static Builder builder(JmsOperations jmsTemplate) {
return new DefaultJmsClientBuilder(jmsTemplate);
}
/**
* A mutable builder for creating a {@link JmsClient}.
*/
interface Builder {
/**
* Add a {@code MessageConverter} to use for converting payload objects to/from messages.
* Message converters will be considered in order of registration.
* @param messageConverter the message converter for payload objects
* @return this builder
*/
Builder messageConverter(MessageConverter messageConverter);
/**
* Add a {@link MessagePostProcessor} to use for modifying {@code Message} instances before sending.
* Post-processors will be executed in order of registration.
* @param messagePostProcessor the post-processor to use for outgoing messages
* @return this builder
*/
Builder messagePostProcessor(MessagePostProcessor messagePostProcessor);
/**
* Build the {@code JmsClient} instance.
*/
JmsClient build();
}

View File

@ -20,18 +20,19 @@ import jakarta.jms.JMSException;
import jakarta.jms.Message;
/**
* To be used with JmsTemplate's send method that converts an object to a message.
* Post-processes a {@link Message}. This is the JMS equivalent of the spring-messaging
* {@link org.springframework.messaging.core.MessagePostProcessor}.
*
* <p>This allows for further modification of the message after it has been processed
* by the converter and is useful for setting JMS headers and properties.
*
* <p>Often implemented as a lambda expression or as an anonymous inner class.
* <p>This is involved right before a {@link JmsClient} sends a message over the wire, for setting additional
* JMS properties and headers. With {@link JmsTemplate}, the message post processor is only involved
* in methods accepting it as an argument, to customize the outgoing message produced
* by a {@link org.springframework.jms.support.converter.MessageConverter}.
*
* @author Mark Pollack
* @since 1.1
* @see JmsClient.OperationSpec#send(org.springframework.messaging.Message)
* @see JmsTemplate#convertAndSend(String, Object, MessagePostProcessor)
* @see JmsTemplate#convertAndSend(jakarta.jms.Destination, Object, MessagePostProcessor)
* @see org.springframework.jms.support.converter.MessageConverter
*/
@FunctionalInterface
public interface MessagePostProcessor {

View File

@ -141,6 +141,21 @@ class JmsClientTests {
assertTextMessage(this.messageCreator.getValue()); // see createTextMessage
}
@Test
void convertAndSendPayloadAndHeadersWithPostProcessor() throws JMSException {
Destination destination = new Destination() {};
Map<String, Object> headers = new HashMap<>();
headers.put("foo", "bar");
this.jmsClient = JmsClient.builder(this.jmsTemplate)
.messagePostProcessor(msg -> MessageBuilder.fromMessage(msg).setHeader("spring", "framework").build())
.build();
this.jmsClient.destination(destination).send("Hello", headers);
verify(this.jmsTemplate).send(eq(destination), this.messageCreator.capture());
TextMessage jmsMessage = createTextMessage(this.messageCreator.getValue());
assertThat(jmsMessage.getObjectProperty("spring")).isEqualTo("framework");
}
@Test
void receive() {
Destination destination = new Destination() {};
@ -209,7 +224,7 @@ class JmsClientTests {
jakarta.jms.Message jmsMessage = createJmsTextMessage("123");
given(this.jmsTemplate.receive("myQueue")).willReturn(jmsMessage);
this.jmsClient = JmsClient.create(this.jmsTemplate, new GenericMessageConverter());
this.jmsClient = JmsClient.builder(this.jmsTemplate).messageConverter(new GenericMessageConverter()).build();
Integer payload = this.jmsClient.destination("myQueue").receive(Integer.class).get();
assertThat(payload).isEqualTo(Integer.valueOf(123));
@ -258,7 +273,7 @@ class JmsClientTests {
jakarta.jms.Message jmsMessage = createJmsTextMessage("123");
given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage);
this.jmsClient = JmsClient.create(this.jmsTemplate, new GenericMessageConverter());
this.jmsClient = JmsClient.builder(this.jmsTemplate).messageConverter(new GenericMessageConverter()).build();
Integer payload = this.jmsClient.destination("myQueue").receive("selector", Integer.class).get();
assertThat(payload).isEqualTo(Integer.valueOf(123));
@ -315,6 +330,22 @@ class JmsClientTests {
assertThat(reply).isEqualTo("My reply");
}
@Test
void convertSendAndReceivePayloadWithPostProcessor() throws JMSException {
Destination destination = new Destination() {};
jakarta.jms.Message replyJmsMessage = createJmsTextMessage("My reply");
given(this.jmsTemplate.sendAndReceive(eq(destination), any())).willReturn(replyJmsMessage);
this.jmsClient = JmsClient.builder(this.jmsTemplate)
.messagePostProcessor(msg -> MessageBuilder.fromMessage(msg).setHeader("spring", "framework").build())
.build();
this.jmsClient.destination(destination).sendAndReceive("my Payload", String.class);
verify(this.jmsTemplate).sendAndReceive(eq(destination), this.messageCreator.capture());
TextMessage jmsMessage = createTextMessage(this.messageCreator.getValue());
assertThat(jmsMessage.getObjectProperty("spring")).isEqualTo("framework");
verify(this.jmsTemplate, times(1)).sendAndReceive(eq(destination), any());
}
@Test
void convertSendAndReceivePayloadName() {
jakarta.jms.Message replyJmsMessage = createJmsTextMessage("My reply");
@ -391,6 +422,32 @@ class JmsClientTests {
verify(connection).close();
}
@Test
void sendWithPostProcessor() throws Exception {
ConnectionFactory connectionFactory = mock();
Connection connection = mock();
Session session = mock();
Queue queue = mock();
MessageProducer messageProducer = mock();
TextMessage textMessage = mock();
given(connectionFactory.createConnection()).willReturn(connection);
given(connection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(session);
given(session.createProducer(queue)).willReturn(messageProducer);
given(session.createTextMessage("just testing")).willReturn(textMessage);
JmsClient.builder(connectionFactory)
.messagePostProcessor(msg -> MessageBuilder.fromMessage(msg).setHeader("spring", "framework").build())
.build()
.destination(queue).send("just testing");
verify(textMessage).setObjectProperty("spring", "framework");
verify(messageProducer).send(textMessage);
verify(messageProducer).close();
verify(session).close();
verify(connection).close();
}
@Test
void sendWithCustomSettings() throws Exception {
ConnectionFactory connectionFactory = mock();

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.core;
import java.util.List;
import org.springframework.messaging.Message;
/**
* Composite {@link MessagePostProcessor} implementation that iterates over
* a given collection of delegate {@link MessagePostProcessor} instances.
* @author Brian Clozel
* @since 7.0
*/
public class CompositeMessagePostProcessor implements MessagePostProcessor {
private final List<MessagePostProcessor> messagePostProcessors;
/**
* Construct a CompositeMessagePostProcessor from the given delegate MessagePostProcessors.
* @param messagePostProcessors the MessagePostProcessors to delegate to
*/
public CompositeMessagePostProcessor(List<MessagePostProcessor> messagePostProcessors) {
this.messagePostProcessors = messagePostProcessors;
}
@Override
public Message<?> postProcessMessage(Message<?> message) {
for (MessagePostProcessor messagePostProcessor : this.messagePostProcessors) {
message = messagePostProcessor.postProcessMessage(message);
}
return message;
}
}