I know there are at least two other issues on this topic: - #9856 describes the requirement and the context precisely; however it is very old and was closed in a bulk update - #24144 was closed because the request was probably asking for this feature in the wrong way, i.e. by violating the (objectively confusing) JMS API
With this I'm asking if you could reconsider to improve JmsTemplate
so that it's possible to do this, especially in light of the existence now of Spring Boot, which configures a JmsTemplate
for you (so the workaround to create a new JmsTemplate
instance on each message send makes things much more complex than they could be).
Use case: I'm using JmsTemplate
to send notifications; these notifications should have different expiration times depending on their context, which has itself an expiration after which all related notifications become stale and useless.
To workaround this, apart from the option to create new JmsTemplates
on each send operation, there's the possibility to use one of the execute(...)
overloadings that takes a ProducerCallback
parameter, however in this case you're completely left alone with regards to the message creation and actual sending process, which is not trivial (see org.springframework.jms.core.JmsTemplate.doSend(Session, Destination, MessageCreator)
implementation).
Comment From: snicoll
@mauromol I don't understand why you think that what you're asking is any different from #24144 and how Spring Boot is related. What you call a workaround is what the JMS spec mandates, as described in this comment.
there's the possibility to use one of the execute(...) overloadings that takes a ProducerCallback parameter, however in this case you're completely left alone with regards to the message creation and actual sending process, which is not trivial (see org.springframework.jms.core.JmsTemplate.doSend(Session, Destination, MessageCreator) implementation).
I don't really understand what is the problem. doSend
is framework code that accomodates with various use cases depending on what was configured on the template. If you write code for your dedicated use case, you shouldn't have to worry about that.
Can you clarify what improvements you would like us to consider?
Comment From: mauromol
@mauromol I don't understand why you think that what you're asking is any different from #24144 and how Spring Boot is related. What you call a workaround is what the JMS spec mandates, as described in this comment.
24144 was requesting to let the JmsTemplate
consumer to use (for instance) Message.setJMSExpiration(long expiration)
within the MessageCreator
implementation to set a message expiration and then pass that value to the producer in order to properly set message expiration. However, this is a misuse of the JMS API that states that such method is "for use by JMS providers only to set this field when a message is sent".
My request, instead, is to provide a proper API on JmsTemplate
to do this in a proper way. Something like, for instance:
jmsTemplate.send(myMessageCreator, messageExpiration);
I don't really understand what is the problem.
doSend
is framework code that accomodates with various use cases depending on what was configured on the template. If you write code for your dedicated use case, you shouldn't have to worry about that.Can you clarify what improvements you would like us to consider?
With Spring Boot you can easily get a JmsTemplate
instance injected in your code once you add a proper starter and configure JMS properties:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-using-jms-sending
So, let's suppose I've added org.springframework.boot:spring-boot-starter-activemq
to my dependencies and that I've set all my spring.jms.*
and spring.activemq.*
. Then, I have that auto-configured JmsTemplate
injected where I need it, how can I send a message with a custom per-message expiration?
These are the solutions (what I call "workarounds") I see:
- throw away the auto-configured JmsTemplate
and manually set up and use my custom JmsTemplate
extension that exposes such a functionality (doSend
is protected)
- throw away the auto-configured JmsTemplate
and manually set up a factory of JmsTemplate
instances, producing a different JmsTemplate
instance for each message I need to send
- keep the auto-configured JmsTemplate
and call org.springframework.jms.core.JmsTemplate.execute(ProducerCallback<T>)
by passing a ProducerCallback
implementation that does the following:
1. create the message (easy)
2. mimic org.springframework.jms.core.JmsTemplate.doSend(MessageProducer, Message)
to set the delivery delay on the producer and send the message with it by passing in the needed QoS parameters (including the message expiration/TTL) (relatively easy, but verbose)
3. handle the transaction as done by org.springframework.jms.core.JmsTemplate.doSend(Session, Destination, MessageCreator)
(not trivial)
Of course, copy 'n' paste is always a "solution", but quite inelegant and not future-proof, in case JmsTemplate
should evolve and perform other things in its doSend(...)
methods.
IMHO this requirement is enough reasonable and common (at least when I look at the past issues here on GitHub and at some questions on StackOverflow) to deserve support by JmsTemplate
out-of-the-box. This is why I opened this issue.
I hope I've clarified now.
Comment From: jhoeller
In terms of our envisioned scenarios there, it's indeed appropriate to create a per-operation JmsTemplate
instance with the corresponding settings applied before invoking the actual operation on it. This is within the design there and not out of the ordinary at all. JmsTemplate
instances can be seen as a lightweight holder for a ConnectionFactory
plus various settings, allowing operations to be triggered on it.
We could make it more obvious/convenient to build such derived JmsTemplate
instances with the same ConnectionFactory
but different settings, but as you noted, you can easily set up such a factory yourself. All the settings on the JmsTemplate
are publicly accessible, so there are no design constraints as far as I can see (towards your second proposal that you call a "workaround").
With respect to the autoconfigured JmsTemplate
instance in Boot, that's just a convenient JmsOperations
delegate pointing to a ConnectionFactory
with default QoS settings. "Throwing it away" is the wrong way of seeing it IMO; choosing to use different QoS settings through a custom ConnectionFactory+settings holder (a custom JmsTemplate
instance) is a perfectly fine choice in terms of our design.
Comment From: mauromol
In terms of our envisioned scenarios there, it's indeed appropriate to create a per-operation
JmsTemplate
instance with the corresponding settings applied before invoking the actual operation on it. This is within the design there and not out of the ordinary at all.JmsTemplate
instances can be seen as a lightweight holder for aConnectionFactory
plus various settings, allowing operations to be triggered on it.
Still, parameters like the delivery mode, the priority and TTL are strictly bound to a message, not to the connection factory IMHO. We're not talking about a connection/socket timeout here, which are properties of the connection. In other messaging systems, these are just message properties (or "headers"), so while providing default values at template level is fine, having to create multiple templates just to be able to specify per-message properties sounds like a waste to me. We could argue on why the JMS API didn't allow to specify such parameters along with the message itself (and indeed reports like #24144 show that people can get confused), instead of requiring to set them on the producer on send, but this is how things have been done on that side.
We could make it more obvious/convenient to build such derived
JmsTemplate
instances with the sameConnectionFactory
but different settings, but as you noted, you can easily set up such a factory yourself. All the settings on theJmsTemplate
are publicly accessible, so there are no design constraints as far as I can see (towards your second proposal that you call a "workaround").
Well, perhaps having a method on JmsTemplate
like withQos(...)
which returns a new equivalent JmsTemplate
with just different QoS settings will be acceptable for me. In this way I could still use the autoconfigured JmsTemplate
and derive new ones whenever I need different QoS settings. It would still sound a bit odd to me, but at least it would be practical and straight.
With respect to the autoconfigured
JmsTemplate
instance in Boot, that's just a convenientJmsOperations
delegate pointing to aConnectionFactory
with default QoS settings. "Throwing it away" is the wrong way of seeing it IMO; choosing to use different QoS settings through a custom ConnectionFactory+settings holder (a customJmsTemplate
instance) is a perfectly fine choice in terms of our design.
It's just more work. After all, we're talking about 3 QoS settings out of ~20 total properties that can be set/configured on a JmsTemplate
. I know, everything could be done, but we're talking about convenience. After all, this seems to me a so natural requirement that it sounded reasonable to request. And it seems like I'm not the first one with this need.
This said, the last decision is up to you of course.
Comment From: snicoll
It's just more work. After all, we're talking about 3 QoS settings out of ~20 total properties that can be set/configured on a JmsTemplate. I know, everything could be done, but we're talking about convenience. After all, this seems to me a so natural requirement that it sounded reasonable to request. And it seems like I'm not the first one with this need.
Yes, but the same argument could be made for any of the 20 properties. The pattern with template is what it is and introducing a builder-style approach could lead other to legitimately request the same to be applied to other properties. I think the building block has to stay as consistent and as simple as possible.
Comment From: mauromol
Yes, but the same argument could be made for any of the 20 properties.
I do not think so, because I do not think all 20 properties are on the same level, but anyway... I'm just sorry about your final decision.
Comment From: loganmzz
Yes, but the same argument could be made for any of the 20 properties.
I do not think so, because I do not think all 20 properties are on the same level, but anyway... I'm just sorry about your final decision.
Not alone, if it can help in same way :) At least I have some happiness finding other people sharing my mind.
I think we can split JmsTemplate usage/settings over several categories:
- Infrastructure (connection, payload processing, etc.)
- Destination
- Message (Request/Producer/Consumer/...)
I think most people setup their JmsTemplate for primary category (infrastructure) and other ones mostly per call. Having default is nice, but duplicating is error-prone as no facilities are provided (clone, overriding wrapper or copy constructor).
And things get worse when using the messaging wrapper (JmsMessagingTemplate
).
Can you at least provide some copy facilities ?
Comment From: snicoll
Alright, It's great to get some more feedback here. Reopening to consider our options.