Annotation-Based API for Consuming Messages
The only means to declare message consumers via configuration or annotation is currently via JMS Message-Driven Beans. A new expressive annotation-based approach modeled after JAX-RS is desired to bring JMS into the future.
A follow-up to an older issue: https://github.com/jakartaee/messaging/issues/134.
Current MDB API Example:
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "maxSessions", propertyValue = "3"),
@ActivationConfigProperty(propertyName = "maxMessagesPerSessions", propertyValue = "1"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "TASK.QUEUE")
})
public class BuildTasksMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
if (!(message instanceof ObjectMessage)) {
throw new JMSException("Expected ObjectMessage, received " + message.getJMSType());
}
final ObjectMessage objectMessage = (ObjectMessage) message;
final BuildTask buildTask = (BuildTask) objectMessage.getObject();
doSomethingUseful(buildTask);
} catch (JMSException e) {
// Why can't I throw a JMS Exception
throw new RuntimeException(e);
}
}
// This is the only "useful" code in the class
private void doSomethingUseful(BuildTask buildTask) {
System.out.println(buildTask);
}
}
Issues with this API involve:
- User manual required to know what names can be used in
@ActivationConfigProperty. - Loosely typed. All values are string, but have an implied type which is not compile-time checked.
- Poor targeting. The metadata for the
onMessagemethod is on the class, not the method, making additional methods impossible. - Too Course-grained. The
MessageListener.onMessage(Message msg)method is similar to theHttpServlet.service(ServletRequest req, ServletResponse res)in being too course-grained and requires boilerplate; casting, message property checking, string parsing. - EJB-specific. The above API is only available to EJB Message-Driven beans.
Some form of annotation-based approach styled after JAX-RS could solve all of the above issues. For example:
import io.breezmq.MaxMessagesPerSession;
import io.breezmq.MaxSessions;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.JMSMessageDrivenBean;
import javax.jms.ObjectMessage;
import javax.jms.QueueListener;
import javax.jms.TopicListener;
@MessageDriven
@MaxSessions(3)
@MaxMessagesPerSession(1)
public class BuildTasksMessageListener implements JMSMessageDrivenBean {
@QueueListener("TASK.QUEUE")
public void processBuildTask(final ObjectMessage objectMessage) throws JMSException {
final BuildTask buildTask = (BuildTask) objectMessage.getObject();
doSomethingUseful(buildTask);
}
@TopicListener("BUILD.TOPIC")
public void processBuildNotification(final ObjectMessage objectMessage) throws JMSException {
final BuildNotification notification = (BuildNotification) objectMessage.getObject();
System.out.println("Something happened " + notification);
}
// This is the only "useful" code in the class
private void doSomethingUseful(BuildTask buildTask) {
System.out.println(buildTask);
}
}
Benefits:
- Multiple user-defined message consuming methods allowed.
- Method signature implies message type, avoiding casting.
- Message Properties can be passed as annotated method arguments. (not shown above)
- Strongly-typed annotations replace string properties so names and values are compile-time checked.
- Opens the door for use outside EJB
The above API should be considered a straw-man just to get conversations started.
Proposals
Actual proposals from the community are welcome, but should achieve or not-conflict with the same 5 benefits. Partial proposals are welcome, such as #241 which focuses on one aspect required to make an annotation-based API work.
-
@MessageConsumerfor discovery #241
File your proposals and mention in the comments below and we'll add it to the list, regardless of state.
TODO: find JMS 2.1 proposal and link it
Related
Spring can process jms via simple @JmsListener, how hard we still need the interface?
@ApplicationScoped
@Slf4j
public class Receiver {
@JmsListener(destination = "hello")
public void onMessage(String message) {
log.debug("receving message: {}", message);
}
}
Provides programmatic APIs to set the JSM global properties, and also allow to set properties attribute(a Map or a string of properties joint by ",") on the JmsListener annotation.
I agree with @hantsy, no interface is needed. And I also like that any CDI bean can be turned into a JMS observer. However, I'm not sure whether a single @JmsListener is enough to distinguish between a queue and a topic. An implementation might need to know whether the listener wants to connect to a queue or a topic.
Another idea is to turn this into a native CDI event observer, with a @JmsListener qualifier. Then all the CDI event API could be used, including asynchronous API. Using @JmsListener on the method could be also allowed but would work as a shorthand, e.g.:
@JmsListener(destination = "hello")
public void onMessage(String message) {
log.debug("receving message: {}", message);
}
would be equivalent to:
public void onMessage(@Observes @JmsListener(destination = "hello") String message) {
log.debug("receving message: {}", message);
}
The latter syntax also allows injection per method call, e.g. the following would inject a myProcessor CDI bean:
public void onMessage(@Observes @JmsListener(destination = "hello") String message, MyProcessor myProcessor) {
myProcessor.process(message);
}
We could allow injecting some contextual information, if it's technically possible. E.g. inject a CDI bean of type of Topic/Queue for the current queue/topic. But we would need to explore that separately, I'm not sure if CDI allows this dynamically for each message.
This is a very deep topic with a number of discussions that took place over a long time but never implemented. If the idea is to finally do this work, I suggest starting with a very simple straw man and detailed follow up discussions on the mailing list. I would be delighted to participate and share incrementally all the things from the past. It’s too much for this one issue I think. The interim outcome may be creating several smaller issues based on some initial progress that hopefully can go into Jakarta EE 11.
@m-reza-rahman I remember JBoss Seam2/3(Seam 3 is based CDI) has very simple approach to bridge the JMS to CDI event observer, this JMS handling should be simple as possible.