This course covers the Java Message Service (JMS) API and its main concepts. Finally, we will look through example questions.
Learning Objectives
- 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
Intended Audience
This course is intended for anyone who already has basic knowledge of Java and now wants to learn about Java Enterprise Edition.
Prerequisites
Basic knowledge of Java programming.
Hello there. In this lesson, we'll try to understand messaging in Java. So, let's start. MOM, Message-Oriented Middleware, which has been around for a while uses a special vocabulary. When a message is sent, the software that stores the message and dispatches it is called a provider or sometimes a broker. The message sender is called a producer, and the location where the message is stored is called a destination. The component receiving the message is called a consumer. Any component interested in the message at that particular destination can consume it. In Java EE, the API that deals with these concepts is called Java Message Service, JMS. It has a set of interfaces and classes that allow you to connect to a provider, create a message, send it, and receive it. JMS doesn't physically carry messages, it's just an API. It requires a provider that's in charge of handling messages.
When running in an EJB container, Message-Driven Beans, MDBs, can be used to receive messages in a container managed way. At a high-level, messaging architecture consists of these components: A provider. JMS is only an API, so it needs an underlying implementation to route messages, that is the provider, aka the message broker. The provider handles the buffering and delivery of messages. Client. A client is any Java application or component that produces or consumes a message to or from the provider. Client is the generic term for producer, sender, publisher, consumer, receiver or subscriber. Messages. These are the objects that clients send or receive from the provider. Administered objects. A message broker must provide administered objects to the client, either through JNDI lookups or injection. The messaging provider enables asynchronous communication by providing a destination, where messages can be held until they can be delivered to a client.
There are two different types of destination, each applying to a specific messaging model. The point-to-point (P2P) model. In this model, the destination used to hold messages is called a queue. When using point-to-point messaging, one client puts a message on a queue and another client receives the message. Once the message is acknowledged, the message provider removes the message from the queue. The publish-subscribe (pub-sub) model. The destination is called a topic. When using publish or subscribe messaging, a client publishes a message to a topic and all subscribers to that topic received the message. In the P2P model, a message travels from a single producer to a single consumer. The model is built around the concept of message queues, senders, and receivers.
A queue retains the messages sent by the sender until they are consumed. And the sender and the receiver do not have timing dependencies. This means that the sender can produce messages and send them in a queue whenever he likes, and the receiver can consume them whenever he likes. Once the receiver is created, it'll get all the messages that were sent to the queue even though sent before its creation. Each message is sent to a specific queue, and the receiver extracts the messages from the queue. Queues retain all messages until they are consumed or until they expire. The P2P model is used if there is only one receiver for each message. Note that a queue can have multiple consumers, but once a receiver consumes a message from the queue, it's taken out of the queue and no other consumer can receive it. In this figure, you can see one sender producing three messages.
Note that P2P doesn't guarantee messages are delivered in any particular order. The order is not defined. A provider might pick them in arrival order or randomly or some other way. In the publish-subscribe model, a single message is sent by a single producer to potentially several consumers. The model is built around the concept of topics, publishers, and subscribers. Consumers are called subscribers because they first need to subscribe to a topic. The provider manages the subscribing and unsubscribing mechanism as it occurs dynamically. The topic retain messages until they are distributed to all subscribers. Unlike the P2P model, there is a timing dependency between publishers and subscribers. Subscribers do not receive messages sent prior to their subscription and if the subscriber is inactive for a specified period, it will not receive past messages when it becomes active again.
Note that this can be avoided because the JMS API supports the concept of a durable subscriber. We'll talk about these in our next lessons. Multiple subscribers can consume the same message. The pub-sub model can be used for broadcast type applications in which a single message is delivered to several consumers. In this figure, the publisher sends three messages that each subscriber will receive in an undefined order. Administered objects are objects that are configured administratively as opposed to programmatically. The message provider allows these objects to be configured. It makes them available in the JNDI namespace. Like JDBC data sources, administered objects are created only once. The two types of administered objects: Connection factories. Used by clients to create a connection to a destination. Destinations. Message distribution points that receive, hold, and distribute messages. Destinations can be queues, P2P or topics pub-sub. Clients access these objects through portable interfaces by looking them up in the JNDI namespace or through injection. In GlassFish, there are several ways to create these objects as you'll later see by using the administration console, the asadmin command line or the REST interface.
Since JMS 2.0, you can even use the @JMSConnectionFactoryDefinition and @JMSDestinationDefinition annotations to define programmatically these objects. Java is a standard Java API that allows applications to create, send, receive, and read messages asynchronously. It defines a common set of interfaces and classes that allow programs to communicate with other message providers. JMS is analogous to JDBC. The latter connects to several databases: Derby, MySQL, Oracle, DB2, etc. And JMS connects to several providers: Open MQ, MQSeries, SonicMQ, etc. The JMS API has evolved ever since its creation. For historical reasons, JMS offers three alternative sets of interfaces for producing and consuming messages. These are very different interfaces evolved in JMS 1.0, 1.1, and 2.0 and are referred to as Legacy API, Classic API, and Simplified API. JMS 1.0 made a clear difference between the point-to-point and publish-subscribe model. It defined two domain specific APIs. One for point-to-point, queues and one for pub-sub, topics.
That's why you can find QueueConnectionFactory and TopicConnectionFactory API instead of the generic ConnectionFactory, for example. The JMS 1.1 API, referred to as the Classic API, provided a unified set of interfaces that can be used with both P2P and pub-sub messaging. Here, this table shows the generic name of an interface, and the Legacy names for each model Queue session, Topic session. But JMS 1.1 was still a verbose and low-level API compared to JPA or EJB. JMS 2.0 introduces a Simplified API that offers all the features of the Classic API, but requires fewer interfaces and is simpler to use. The JMS Classic API provides classes and interfaces for applications that require a messaging system. This API enables asynchronous communication between clients by providing a connection to the provider, and a session where messages can be created or sent or received. These messages can contain text or other different kinds of objects. Connection factories are administered objects that allow an application to connect to a provider by creating a connection object programmatically.
A javax.jms.ConnectionFactory is an interface that encapsulates the configuration parameters that have been defined by an administrator. To use an administered object such as a ConnectionFactory, the client needs to perform a JNDI lookup or use injection. For example, Context ctx = new InitialContext. ConnectionFactory = ConnectionFactory. ctx.lookupConnectionFactory. This code fragment obtains the JNDI initial context object and uses it to look up a connection factory by its JNDI name. The methods available in this interface are createConnection methods that return a Connection object and new JMS 2.0 createContext methods that return a JMSContext. You can create a Connection or a JMSContext either with the default user identity or by specifying a username and password.
ConnectionFactory. 'Connection createConnection() throws JMSException;'. '(String userName, String password) throws JMSException;'. 'JMSContext createContext();'. 'JMSContext createContext(String userName, String password);'. 'JMSContext createContext(String userName, String password, int sessionMode);'. 'JMSContext createContext(int sessionMode);'. A destination is an administered object that contains provider-specific configuration information such as the destination address. But, this configuration is hidden from the JMS client by using the standard javax.jms.Destination interface. Like the ConnectionFactory, a JNDI look up is needed to return such objects. Context ctx = new InitialContext();. Destination queue = (Destination) ctx.lookup. The javax.jms.Connection object, which you create using the createConnection method of the ConnectionFactory, encapsulates a connection to the JMS provider. Connections are thread-safe and designed to be shareable as opening a new connection is resource intensive. However, a session, javax.jms.Session provides a single-threaded context for sending and receiving messages using a connection to create one or more sessions.
Once you have a ConnectionFactory, you can use it to create a connection like this. 'Connection connection = connectionFactory.createConnection();'. Before a receiver can consume messages, it must call the start method. If you need to stop receiving messages temporarily without closing the connection, you can call the stop method. 'connection.start();'. 'connection.stop();'. When the application completes, you need to close any connections created. Closing a connection also closes its session and its producers or consumers. 'connection.close();'. You can create a session from the connection using the createSession() method. A session provides a transactional context in which a set of messages to be sent or received are grouped in an atomic unit of work. Meaning that, if you send several messages during the same session, JMS will ensure that either they all are sent or none.
This behavior is set at the creation of the session. The first parameter of the method specifies whether or not the session is transactional. In the code, the parameter is set to true. Meaning that, the request for sending messages won't be realized until either the sessions commit method is called or the session is closed. If the parameter was set to false, the session would not be transactional and messages would be sent as soon as the send method is invoked. The second parameter means that the session automatically acknowledges messages when they have been received successfully. A session is single-threaded and is used to create messages, producers, and consumers. To communicate, clients exchange messages. One producer will send a message to a destination and a consumer will receive it. Messages are objects that encapsulate information and are divided into three parts.
A header contains standard information for identifying and routing the message. Properties are name-value pairs that the application can send or read. Properties also allow destinations to filter messages based on property values. A body contains the actual message and can take several formats, text, bytes, object, et cetera. The header has predefined name-value pairs common to all messages that both clients and providers use to identify and route messages. They can be seen as message metadata and they give information about the message. Each field has associated getter and setter methods defined in the javax.jms.Message interface. Some header fields are intended to be set by a client, but many are set automatically by the send or the published method. Here, this table describes each JMS message header field. JMSDestination. This indicates the destination to which the message is being sent. JMSDeliveryMode. JMS supports two modes of message delivery.
Persistent mode instructs the provider to ensure the message is not lost in transit due to a failure. Non_persistent mode is the lowest overhead delivery mode because it does not require the message to be logged to a persistent storage. JMSMessageID. This provides a value that uniquely identifies each message sent by a provider. JMSTimestamp. This contains the time a message was handed off to a provider to be sent. JMSCorrelationID. A client can use this field to link one message with another, such as linking a response message with its request message. JMSReplyTo. This contains the destination where a reply to the message should be sent. JMSRedelivered. This Boolean value is set by the provider to indicate whether a message has been redelivered. JMSType. This serves as a message type identifier. JMSExpiration. When a message is sent, its expiration time is calculated and set based on the time-to-live value specified on the send method. JMSPriority.
JMS defines a 10-level priority value with zero as the lowest priority and nine as the highest. In addition to the header fields, the javax.jms.Message interface supports property values which are just like headers, but explicitly created by the application instead of being standard across messages. This provides a mechanism for adding optional header fields to a message that a client will choose to receive or not via selectors. Property values can be Boolean, byte, short, int, long, float, double, and string. The code to set and get properties looks like this. 'message.setFloatProperty("orderAmount", 1245.5f);'. 'message.getFloatProperty("orderAmount");'. The body of a message is optional and it contains the data to send or receive. Depending on the interface that you use, it can contain different formats of data as listed like this. StreamMessage. A message whose body contains a stream of Java primitive values. It's filled and read sequentially. MapMessage.
A message whose body contains a set of name-value pairs where names are strings and values are Java primitive types. TextMessage. A message whose body contains a string. For example, it can contain XML. ObjectMessage. A message that contains a serializable object or a collection of serializable objects. BytesMessage. A message that contains a stream of bytes. It's possible to create your own message format if you extend the javax.jms.Message interface. Note that when a message is received, its body is read-only. Depending on the message type, you have different methods to access its content. A TextMessage will have a getText and setText method. An ObjectMessage will have a getObject and setObject and so forth. 'textMessage.setText("This is a textMessage");'. 'textMessage.getText();'. 'bytesMessage.readByte();'. 'objectMessage.getObject();'. 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.