This course covers the Java Message Service (JMS) API and its main concepts. Finally, we will look through example questions.
- Understand the fundamentals of the JMS API and messaging
- Learn about writing message producers and consumers
- Learn about reliability mechanisms
- Learn how to write message-driven beans
This course is intended for anyone who already has basic knowledge of Java and now wants to learn about Java Enterprise Edition.
Basic knowledge of Java programming.
Hello there. In this lesson, we'll talk about Writing Message-Driven Beans. So, let's start. Until now, this section showed how asynchronous messaging provides loose coupling and increased flexibility between systems using the JMS API. MDBs provide this standard asynchronous messaging model for enterprise applications running in an EJV container. An MDB is an asynchronous consumer that is invoked by the container as a result of the arrival of a message. To a message producer, an MDB is simply a message consumer hidden behind a destination to which it listens. MDBs are part of the EJB specification, and their model is close to stateless session beans as they do not have any state and run inside an EJB container.
The container listens to a destination and delegates the call to the MDB upon message arrival. Like any other EJB, MDBs can access resources managed by the container, other EJPs, JDBC connections, JMS resources, entity manager, etc. Why use MDBs when you can use standalone JMS clients as you've seen previously? Because of the container, which manages multithreading security, and transactions, thereby greatly simplifying the code of your JMS consumer. It also manages incoming messages among multiple instances of MDBs available in a pool that have no special multithreading code themselves. As soon as a new message reaches the destination, an MDB instance is retrieved from the pool to handle the message. A simple consumer MDB is described in this code snippet.
The code in this snippet shows that MDBs relieve the programmer of all mechanical aspects of processing the types of messages explained so far. An MDB implements the message listener interface and the onMessage method, but no other code is needed to connect the provider or start message consumption. MDBs also rely on the configuration by exception mechanism, and only a few annotations are needed to make it work. See the App Message-Driven annotation. MDBs are different from session beans as they do not implement a local or remote business interface, but instead implement the javax.jms.MessageListener interface. Clients cannot invoke methods directly on MDBs.
However, like session beans, MDBs have a rich programming model that includes a lifecycle, callback annotations, interceptors, injections, and transactions. Taking advantage of this model provides applications with a high level of functionality. It's important to be aware that MDBs are not part of the EJB light model, meaning that they cannot be deployed in a simple web profile application server, but still need the full Java EE stack. The requirements to develop an MDB class are: the class must be annotated with @javax.ejb.MessageDriven or its XML equivalent in a deployment descriptor. The class must implement directly or indirectly to the message listener interface. The class must be defined as public. It must not be final or abstract. The class must have a public no-arg constructor that the container will use to create instances of the MDB. The class must not define the finalize() method. The MDB class is allowed to implement other methods, invoke other resources, and so on. MDBs are deployed in a container and can be optionally packaged with an EJB_JAR.xml file. MDBs are one of the simplest kinds of EJBs to develop as they support the smallest number of annotations.
The @MessageDriven annotation or XML equivalent is mandatory, as it's the piece of metadata the container requires to recognize the Java class is actually an MDB. The API of the @MessageDriven annotation is very simple and all elements are optional. The name element specifies the name of the MDB, which by default is the name of the class. messageListenerInterface specifies which message listener the MDB implements. If the MDB implements multiple interfaces, it tells the EJB container, which one is the messageListenerInterface. The mappedName element is the JNDI name of the destination that the MDB should be listening to. Description is just a string used to give a description of the MDB once deployed. The activationConfig element is used to specify configuration properties, and takes an array of @activationConfigProperty annotations. JMS allows configuration of certain properties such as message selectors, acknowledgement mode, durable subscribers, and so on. In an MDB, these properties can be set using the @ActivationConfigProperty annotation. This optional annotation can be provided as one of the parameters for the @MessageDriven annotation and compared to the JMS equivalent. The @ActivationConfigProperty is very basic, consisting of a name value pair. The @ActivationConfigProperty allows you to provide standard and non-standard provider specific configuration. This code sets the acknowledgeMode and the MessageSelector.
Each activation property is a name value pair that the underlying messaging provider understands and uses to set up the MDB This table lists some standard properties you can use. acknowledgeMode. The acknowledgement mode default is auto_acknowledge. messageSelector. The messageSelector string used by the MDB. destinationType. The destinationType which can be topic or queue. destinationLookup. The lookup name of an administratively defined queue or topic. connectionFactoryLookup. The lookup name of an administratively defined connection factory. Destination: the name of the destination. subscriptionDurability. The subscriptionDurability default is Non_durable. subscriptionName. The subscription name of the consumer. shareSubscriptions. Used if the Message-Driven Bean is deployed into a cluster. clientID: the client identifier that will be used when connecting to the JMS provider. MDBs can use dependency injection to acquire references to resources such as JDBC data sources, EJBs, or other objects.
Injection is the means by which the container inserts dependencies automatically after creating the object. These resources have to be available in the container or environment context, so this code is allowed in an MDB. The MDB context can also be injected using the @Resource annotation. The message driven context interface provides access to the runtime context that the container provides for an MDB instance. The container passes the MessageDrivenContext interface to this instance, which remains associated for the lifetime of the MDB. This gives the MDB the possibility to explicitly roll back a transaction, get the user principle, and so on. The MessageDrivenContext interface extends the javax.ejb.EJBContext interface without adding any extra methods. An MDB life cycle is identical to that of the stateless session beans; either the MDB exists and is ready to consume messages or it doesn't exist. Before existing, the container first creates an instance of the MDB, and if applicable, injects the necessary resources as specified by metadata annotations or deployment descriptor.
The container then calls the beans at post construct callback method if any. After this, the MDB is in the ready state and waits to consume any incoming message. The @PreDestroy callback occurs when the MDB is removed from the pool or destroyed. This behavior is identical to that of stateless session beans. And like other EJBs, you can add interceptors with the @javax.ejb.AroundInvoke annotation. As explained in the writing message consumer section earlier in this chapter, consumers can receive a message either synchronously by looping and waiting for a message to arrive or asynchronously by implementing the message listener interface. By nature, MDBs are designed to function as asynchronous message consumers. MDBs implement a message listener interface which is triggered by the container when a message arrives. Can an MDB asynchronous consumer? Yes, but this is not recommended. Synchronous message consumers block and tie up server resources.
MDBs, like stateless session beans, live in a pool of a certain size. When the container needs an instance it takes one out of the pool and uses it. If each instance goes into an infinite loop, the pool will eventually empty, and all the available instances will be busy looping. The EJB container can also start generating more MDB instances, growing the pool and eating up more and more memory. For this reason, Session Beans and MDBs should not be used as synchronous message consumers. MDBs are capable of becoming message producers, something that often occurs when they are involved in a workflow, as they receive a message from one destination, process it, and send it to another destination. To add this capability, the JMS API must be used. A destination and a connection factory can be injected by using the @Resource and @JMSConnectionFactory annotations or via JNDI look up. And then, methods on the Javax.jms.JMSContextObject can be invoked to create and send a message.
The code of the BillingMDB listens to a topic, receives messages and sends a new message to a queue. This MDB uses most of the concepts introduced thus far. First, it uses the app message-driven annotation to define the JDN name of the topic it is listening to, mappedName = "jms/javaee7/Topic." In the same annotation, it defines a set of properties such as the acknowledgeMode and a messageSelector using an array of @ActivationConfigProperty annotations, and it implements MessageListener and it's on messageMethod. This MDB also needs to produce a message. Therefore, it's injected with the two administered objects required. A connection factory using JMSContext and a destination. Finally, the business method that sends messages, the sendPrintingMessage method looks like what you've seen earlier. A JMS producer is created, and used to create and send a text message. For better readability, exception handling has been omitted in the entire class. MDBs are EJBs, MDBs can use BMTs or Container Message Transactions (CMTs).
They can explicitly roll back a transaction by using the MessageDrivenContext.setRollbackOnly method, and so on. However, there are some specifics regarding MDBs that are worth explaining. When we talk about transactions, we always think of relational databases. However, other resources are also transactional, such as, messaging systems. If two or more operations have to succeed or fail together, they form a transaction. With messaging, if two or more messages are sent, they have to succeed, commit, or fail, rollback together. How does this work in practice? The answer is, that messages are not released to consumers until the transaction commits. The container will start a transaction before the onMessage method is invoked, and will commit the transaction when the method returns, unless the transaction was marked for rollback with setRollbackOnly. Even though MDBs are transactional, they cannot execute the client's transaction context, as they don't have a client. Nobody explicitly invokes methods on MDBs. They just listen to a destination and consume messages. There is no context passed from a client to an MDB. And similarly, the client transaction context cannot be passed to the onMessage method.
This table compares CMTs with Session Beans and MDBs. In CMTs, MDBs can use the @JavaX.EJB.TransactionAttributeAnnotations on business methods with the following attributes: Required the default. If the MDB invokes other Enterprise Beans, the container passes the transaction context with the invocation. The container attempts to commit the transaction when the messageListener method has completed. NOT_SUPPORTED. If the MDB invokes other Enterprise Beans, the container passes no transaction context with the invocation. In the snippets of code in this chapter, exception handling has been omitted as the JMS API can be verbose in dealing with exceptions. The classic API defines 12 different exceptions, all inheriting from javax.jms.JMSException. The simplified API defines 10 runtime exceptions, all inheriting from javax.jms.JMSRuntimeException. It's important to note that JMS Exception is a checked exception, and JMS Runtime Exception is unchecked.
The EJB specification outlines two types of exceptions. Application exceptions: Checked exceptions that extend exception, and do not cause the container to roll back. System exceptions: Unchecked exceptions that extend runtime exception, and cause the container to roll back. Throwing a JMS Runtime Exception will cause the container to roll back but throwing a JMS Exception will not. If a roll back is needed, the setRollBackOnly must be explicitly called or a system exception, such as EJB Exception, re-thrown. This chapter showed that integration with messaging is a loosely coupled asynchronous form of communication between components. MOM can be seen as a buffer between systems that need to produce and consume messages at their own pace.
This is different from the RPC Architecture, such as RMI, in which clients need to know the methods of an available service. The first section of this chapter concentrated on the JMS API, classical and simplified, and its vocabulary. The asynchronous model is a very powerful API that can be used in a Java SC or Java EE environment, and is based on P2P and pub sub connection factories, destinations, connections, sessions, messages, header, properties, body of different types, text, object, map, stream, bytes, selectors, and other reliability mechanisms, such as acknowledgement or durability. Java EE has a special enterprise component to consume messages, MDBs. Also in this chapter, showed how MDBs could be used as asynchronous consumers, and how they rely on their container to take care of several services. Lifecycle, inceptors, transactions, security, concurrency, message acknowledgement, etc. So, that's it. Hope to see you in our next lesson. Have a nice day.
OAK Academy is made up of tech experts who have been in the sector for years and years and are deeply rooted in the tech world. They specialize in critical areas like cybersecurity, coding, IT, game development, app monetization, and mobile development.