A Message -driven Bean (MDB) is a stateless, transaction-aware component (service end-point) that consumes JMS messages asynchronously, and they are managed by a Java EE application server and EJB container. MDBs are similar to EJBs because they are complete enterprise beans, similar to stateless session beans. The EJB container is responsible for managing the lifecycle of the endpoint. MDB live inside the EJB container and the container looks after the environment, the security, and the resources, including transactions and concurrency. Most importantly, the container will also handle message acknowledgement and concurrency.
MDBs process messages concurrently and have a thread safety designed by default. There are no remote or local bean interfaces for MDBs, which means the application does not invoke methods on MDB directly, because they respond only to asynchronous messages. MDBs are scalable to hundreds and thousands of asynchronous JMS messages.
In Java EE 7, a message-driven bean is declared with an annotation: @javax.ejb.MessageDriven
. This annotation accepts an array of annotations for a configuration called @javax.ejb.ActivationConfigProperty
.
package je7hb.jms.essentials; import javax.ejb.*; import javax.jms.*; @MessageDriven( mappedName = "jms/demoQueue", activationConfig = { @ActivationConfigProperty( propertyName="acknowledgeMode", propertyValue = "Auto-acknowledge" ), @ActivationConfigProperty( propertyName="messageSelector", propertyValue = "SpecialCode = 'Barbados'" ), } ) public class PayloadCheckMDB implements MessageListener { @EJB private PayloadCheckReceiverBean receiver; public void onMessage(Message message) { try { TextMessage textMsg = (TextMessage)message; String text = textMsg.getText(); receiver.addMessage(text); } catch (Exception e) { e.printStackTrace(System.err); } } }
The class PayloadCheckMDB
is annotated with @MessageDriven
and it extends the MessageListener
JMS interface. PayloadCheckReceiverBean
, which is an EJB, is injected into the MDB. We do this because MDBs are asynchronous by default, and therefore we want to avoid recording the state in the message bean.
The @MessageDriven
annotation accepts a mappedName
parameter which identifies the connection; the MessageDestination
that this MDB connects to. The activation configuration key-value pairs define the acknowledgment mode for the JMS session. There is also a messageSelector
which filters the JMS messages that this MDB will receive.
Inside the onMessage()
method, we retrieve the text of the Message
instance and save it into the receiver bean.
Here is the code for the receiver bean:
public class PayloadCheckReceiverBean { private CopyOnWriteArrayList<String> messages = new CopyOnWriteArrayList<>(); public void addMessage(String text) { messages.add(text); } public List<String> getMessages() { return messages; } }
The PayloadCheckReceiverBean
is a singleton and a stateless session EJB which uses a concurrent collection to store messages. It is a singleton because we want to preserve its state for the duration of the unit test that follows.
To complete the picture, we need a revised version of the earlier JMS sender EJB. Here is the code snippet for that bean:
package je7hb.jms.essentials; /* ... similar to the previous ... */ @Stateless public class PayloadCheckSenderBean { @Inject @JMSConnectionFactory("jms/demoConnectionFactory") JMSContext context; @Resource(mappedName = "jms/demoQueue") private Queue inboundQueue; List<String> messages = new ArrayList<>(); public void sendPayloadMessage( String payload ) { sendPayloadMessage(payload,null); } public void sendPayloadMessage( String payload, String code ) { JMSProducer producer = context.createProducer(); if (code != null ) { producer.setProperty("SpecialCode", code); } producer.send(inboundQueue, payload); messages.add(payload); } public List<String> getMessages() { return messages; } }
The other EJB, PayloadCheckSenderBean
, now has a refactored overloaded method, sendPayloadMessage()
, which sends a message with a JMS header property or not. Choosing the two argument version of the method adds a property to the outgoing message with the key SpecialCode
. This is the form for the message selector in the MDB.
Here is the full unit to exercise PayloadCheckMDB
:
package je7hb.jms.essentials; /* ... similar to the previous ... */ @RunWith(Arquillian.class) public class PayloadCheckMDBTest { @Deployment public static JavaArchive createDeployment() { JavaArchive jar = ShrinkWrap.create(JavaArchive.class) .addClasses( PayloadCheckMDB.class, PayloadCheckSenderBean.class, PayloadCheckReceiverBean.class) .addAsManifestResource( EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); return jar; } @EJBPayloadCheckSenderBean sender; @EJBPayloadCheckReceiverBean receiver; @Test public void shouldFireMessageAtMDB() throws Exception { sender.sendPayloadMessage("hello"); sender.sendPayloadMessage("Wanda", "Barbados"); sender.sendPayloadMessage("world"); sender.sendPayloadMessage("Fish", "Barbados"); Thread.sleep(1000); List<String> messages = receiver.getMessages(); assertEquals(2, messages.size()); assertTrue(messages.contains("Wanda")); assertTrue(messages.contains("Fish")); } }
In this Arquillian integration test, we inject two EJBs, PayloadCheckSenderBean
and PayloadCheckReceiverBean
, into the class. We do this to get around the fact that it is not possible to inject the MDB directly into a test because the CDI (and EJB) container prohibits this dependency on an asynchronous entity.
Inside the test method shouldFireMessageAtMDB()
,
we send four messages to the MDB. Two messages have the special property and the others have not. We deliberately delay for a bit because we are dealing with asynchronous behaviors, network I/O, and an embedded server. The singleton EJB PayloadCheckReceiverBean
should have two messages in its store. We also use the assertTrue()
call instead of the
assertEquals()
because we can never be sure of order of the messages because of Java concurrency and multiple threads. The list collection could be [Wanda,Fish]
or [Fish,Wanda]
. We only care about the message delivery.
The order of messages
Messaging systems are more flexible and serve better throughput if they can cope with out-of-order messages. The JMS standard does not guarantee the order of the messages and how they will be delivered. The order of messages is completely at the discretion of the JMS provider implementation. Some providers like IBM MQ Series and Oracle WebLogic JMS have a feature that allows administrators to define the order of the messages. The OpenMQ product inside of the GlassFish server delivers messages in FIFO order, but then again, when it is tied to asynchronous MDBs inside a server with Java threads, your results will vary. Typically, the message payload itself will have an order ID or candidate keys of columns that the application can use to re-order the message itself in a group of messages.
JMS 2.0 adds the standard configuration of JMS connections for MDBs. It is now possible to configure the JNDI name of the message destination through annotations. The developer can also configure the connection factory, client ID, and the name of the durable subscription through annotations.
As we have seen, MDBs are asynchronous and are bound to a JMS connection. The connection can be local to the application server, and most implementations are external. In other words, an application can use another message queue implementation, and it will normally be a remote service. MDBs are allowed to be binded to different JMS providers, both local and remote. This is where the activation configuration properties and the corresponding annotation @ActivationConfigProperty
help out.
Here is a table of the important key names for the @ActivationConfigProperty
annotation:
Configuration Name |
Description |
---|---|
New in JMS 2.0, this property specifies the lookup name for an MDB of The setting can also be set in an | |
New in JMS 2.0, this property specifies the lookup name for an MDB of The setting can also be set in an | |
Specifies the session acknowledgement for an MDB. The allowed values are | |
Specifies the conditional expression that allows an MDB to select specific JMS messages. The default is to allow all messages through for reception. | |
Specifies whether the destination is a queue or a topic. The valid values are | |
Specifies if the MDB is part of a durable subscription or not. The valid values are | |
New in JMS 2.0, this optional property sets the client identifier for connecting to the JMS provider. | |
New in JMS 2.0, this optional property sets the name of the durable or non-durable subscription. |
The message selector is an important facility for MDB to receive only messages that are interesting from the JMS queue or topic. Message selectors use javax.jms.Message
header properties in conditional expressions.
The expressions are based on a subset of the SQL-92 conditional expression syntax that is used in the WHERE
clauses of database SQL statements. Hence, they are very familiar to database administrators. Message selectors are allowed to use Boolean expressions, unary operators, and literal values. They can be become quite complex.
Here is an example of a more advanced message selector:
@ActivationConfigProperty( propertyName="messageSelector", propertyValue = "(SpecialCode = 'Barbados' AND VerNum > 2)" + "OR (WorkRole='Admin')" ),
Obviously, a JMS message header would have one or two of these additional properties defined in order for this MDB to receive the data.
18.189.188.238