Skip to content

Commit ec87d90

Browse files
committed
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
1 parent 149d468 commit ec87d90

File tree

11 files changed

+495
-106
lines changed

11 files changed

+495
-106
lines changed

framework-docs/modules/ROOT/pages/integration/jms/sending.adoc

Lines changed: 20 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,7 @@ that takes no destination argument uses the default destination.
99
The following example uses the `MessageCreator` callback to create a text message from the
1010
supplied `Session` object:
1111

12-
[source,java,indent=0,subs="verbatim,quotes"]
13-
----
14-
import jakarta.jms.ConnectionFactory;
15-
import jakarta.jms.JMSException;
16-
import jakarta.jms.Message;
17-
import jakarta.jms.Queue;
18-
import jakarta.jms.Session;
19-
20-
import org.springframework.jms.core.MessageCreator;
21-
import org.springframework.jms.core.JmsTemplate;
22-
23-
public class JmsQueueSender {
24-
25-
private JmsTemplate jmsTemplate;
26-
private Queue queue;
27-
28-
public void setConnectionFactory(ConnectionFactory cf) {
29-
this.jmsTemplate = new JmsTemplate(cf);
30-
}
31-
32-
public void setQueue(Queue queue) {
33-
this.queue = queue;
34-
}
35-
36-
public void simpleSend() {
37-
this.jmsTemplate.send(this.queue, new MessageCreator() {
38-
public Message createMessage(Session session) throws JMSException {
39-
return session.createTextMessage("hello queue world");
40-
}
41-
});
42-
}
43-
}
44-
----
12+
include-code::./JmsQueueSender[]
4513

4614
In the preceding example, the `JmsTemplate` is constructed by passing a reference to a
4715
`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
8452
following example shows how to modify a message header and a property after a
8553
`java.util.Map` is converted to a message:
8654

87-
[source,java,indent=0,subs="verbatim,quotes"]
88-
----
89-
public void sendWithConversion() {
90-
Map<String, String> map = new HashMap<>();
91-
map.put("Name", "Mark");
92-
map.put("Age", new Integer(47));
93-
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
94-
public Message postProcessMessage(Message message) throws JMSException {
95-
message.setIntProperty("AccountID", 1234);
96-
message.setJMSCorrelationID("123-00001");
97-
return message;
98-
}
99-
});
100-
}
101-
----
55+
include-code::./JmsSenderWithConversion[]
10256

10357
This results in a message of the following form:
10458

@@ -126,32 +80,6 @@ to `jakarta.jms.TextMessage`, `jakarta.jms.BytesMessage`, etc. For a contract su
12680
generic message payloads, use `org.springframework.messaging.converter.MessageConverter`
12781
with `JmsMessagingTemplate` or preferably `JmsClient` as your central delegate instead.
12882

129-
130-
[[jms-sending-jmsclient]]
131-
== Sending a Message with `JmsClient`
132-
133-
[source,java,indent=0,subs="verbatim,quotes"]
134-
----
135-
// Reusable handle, typically created through JmsClient.create(ConnectionFactory)
136-
// For custom conversion, use JmsClient.create(ConnectionFactory, MessageConverter)
137-
private JmsClient jmsClient;
138-
139-
public void sendWithConversion() {
140-
this.jmsClient.destination("myQueue")
141-
.withTimeToLive(1000)
142-
.send("myPayload"); // optionally with a headers Map next to the payload
143-
}
144-
145-
public void sendCustomMessage() {
146-
Message<?> message =
147-
MessageBuilder.withPayload("myPayload").build(); // optionally with headers
148-
this.jmsClient.destination("myQueue")
149-
.withTimeToLive(1000)
150-
.send(message);
151-
}
152-
----
153-
154-
15583
[[jms-sending-callbacks]]
15684
== Using `SessionCallback` and `ProducerCallback` on `JmsTemplate`
15785

@@ -160,3 +88,21 @@ want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
16088
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
16189
`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run
16290
these callback methods.
91+
92+
93+
[[jms-sending-jmsclient]]
94+
== Sending a Message with `JmsClient`
95+
96+
include-code::./JmsClientSample[]
97+
98+
99+
[[jms-sending-postprocessor]]
100+
== Post-processing outgoing messages
101+
102+
Applications often need to intercept messages before they are sent out, for example to add message properties to all outgoing messages.
103+
The `org.springframework.messaging.core.MessagePostProcessor` based on the spring-messaging `Message` can do that,
104+
when configured on the `JmsClient`. It will be used for all outgoing messages sent with the `send` and `sendAndReceive` methods.
105+
106+
Here is an example of an interceptor adding a "tenantId" property to all outgoing messages.
107+
108+
include-code::./JmsClientWithPostProcessor[]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.jms.jmssending;
18+
19+
import jakarta.jms.ConnectionFactory;
20+
import jakarta.jms.JMSException;
21+
import jakarta.jms.Message;
22+
import jakarta.jms.Queue;
23+
import jakarta.jms.Session;
24+
25+
import org.springframework.jms.core.MessageCreator;
26+
import org.springframework.jms.core.JmsTemplate;
27+
28+
public class JmsQueueSender {
29+
30+
private JmsTemplate jmsTemplate;
31+
private Queue queue;
32+
33+
public void setConnectionFactory(ConnectionFactory cf) {
34+
this.jmsTemplate = new JmsTemplate(cf);
35+
}
36+
37+
public void setQueue(Queue queue) {
38+
this.queue = queue;
39+
}
40+
41+
public void simpleSend() {
42+
this.jmsTemplate.send(this.queue, new MessageCreator() {
43+
public Message createMessage(Session session) throws JMSException {
44+
return session.createTextMessage("hello queue world");
45+
}
46+
});
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.jms.jmssendingconversion;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import jakarta.jms.JMSException;
23+
import jakarta.jms.Message;
24+
25+
import org.springframework.jms.core.JmsTemplate;
26+
import org.springframework.jms.core.MessagePostProcessor;
27+
28+
public class JmsSenderWithConversion {
29+
30+
private JmsTemplate jmsTemplate;
31+
32+
public void sendWithConversion() {
33+
Map<String, Object> map = new HashMap<>();
34+
map.put("Name", "Mark");
35+
map.put("Age", 47);
36+
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
37+
public Message postProcessMessage(Message message) throws JMSException {
38+
message.setIntProperty("AccountID", 1234);
39+
message.setJMSCorrelationID("123-00001");
40+
return message;
41+
}
42+
});
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.jms.jmssendingjmsclient;
18+
19+
import jakarta.jms.ConnectionFactory;
20+
21+
import org.springframework.jms.core.JmsClient;
22+
import org.springframework.messaging.Message;
23+
import org.springframework.messaging.support.MessageBuilder;
24+
25+
public class JmsClientSample {
26+
27+
private final JmsClient jmsClient;
28+
29+
public JmsClientSample(ConnectionFactory connectionFactory) {
30+
// For custom options, use JmsClient.builder(ConnectionFactory)
31+
this.jmsClient = JmsClient.create(connectionFactory);
32+
}
33+
34+
public void sendWithConversion() {
35+
this.jmsClient.destination("myQueue")
36+
.withTimeToLive(1000)
37+
.send("myPayload"); // optionally with a headers Map next to the payload
38+
}
39+
40+
public void sendCustomMessage() {
41+
Message<?> message = MessageBuilder.withPayload("myPayload").build(); // optionally with headers
42+
this.jmsClient.destination("myQueue")
43+
.withTimeToLive(1000)
44+
.send(message);
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.integration.jms.jmssendingpostprocessor;
18+
19+
20+
import jakarta.jms.ConnectionFactory;
21+
22+
import org.springframework.jms.core.JmsClient;
23+
import org.springframework.messaging.Message;
24+
import org.springframework.messaging.core.MessagePostProcessor;
25+
import org.springframework.messaging.support.MessageBuilder;
26+
27+
public class JmsClientWithPostProcessor {
28+
29+
private final JmsClient jmsClient;
30+
31+
public JmsClientWithPostProcessor(ConnectionFactory connectionFactory) {
32+
this.jmsClient = JmsClient.builder(connectionFactory)
33+
.messagePostProcessor(new TenantIdMessageInterceptor("42"))
34+
.build();
35+
}
36+
37+
public void sendWithPostProcessor() {
38+
this.jmsClient.destination("myQueue")
39+
.withTimeToLive(1000)
40+
.send("myPayload");
41+
}
42+
43+
static class TenantIdMessageInterceptor implements MessagePostProcessor {
44+
45+
private final String tenantId;
46+
47+
public TenantIdMessageInterceptor(String tenantId) {
48+
this.tenantId = tenantId;
49+
}
50+
51+
@Override
52+
public Message<?> postProcessMessage(Message<?> message) {
53+
return MessageBuilder.fromMessage(message)
54+
.setHeader("tenantId", this.tenantId)
55+
.build();
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)