403 lines
16 KiB
Markdown
403 lines
16 KiB
Markdown
# Using JMS or Jakarta Messaging
|
|
|
|
Although Apache ActiveMQ Artemis provides a JMS agnostic messaging API, many
|
|
users will be more comfortable using JMS.
|
|
|
|
JMS is a very popular API standard for messaging, and most messaging systems
|
|
provide a JMS API. If you are completely new to JMS we suggest you follow the
|
|
[Oracle JMS
|
|
tutorial](https://docs.oracle.com/javaee/7/tutorial/partmessaging.htm) - a full
|
|
JMS tutorial is out of scope for this guide.
|
|
|
|
Apache ActiveMQ Artemis also ships with a wide range of examples, many of which
|
|
demonstrate JMS API usage. A good place to start would be to play around with
|
|
the simple JMS Queue and Topic example, but we also provide examples for many
|
|
other parts of the JMS API. A full description of the examples is available in
|
|
[Examples](examples.md).
|
|
|
|
In this section we'll go through the main steps in configuring the server for
|
|
JMS and creating a simple JMS program. We'll also show how to configure and use
|
|
JNDI, and also how to use JMS with Apache ActiveMQ Artemis without using any
|
|
JNDI.
|
|
|
|
## A simple ordering system
|
|
|
|
For this chapter we're going to use a very simple ordering system as our
|
|
example. It is a somewhat contrived example because of its extreme simplicity,
|
|
but it serves to demonstrate the very basics of setting up and using JMS.
|
|
|
|
We will have a single JMS Queue called `OrderQueue`, and we will have a single
|
|
`MessageProducer` sending an order message to the queue and a single
|
|
`MessageConsumer` consuming the order message from the queue.
|
|
|
|
The queue will be a `durable` queue, i.e. it will survive a server restart or
|
|
crash. We also want to pre-deploy the queue, i.e. specify the queue in the
|
|
server configuration so it is created automatically without us having to
|
|
explicitly create it from the client.
|
|
|
|
## JNDI
|
|
|
|
The JMS specification establishes the convention that *administered objects*
|
|
(i.e. JMS queue, topic and connection factory instances) are made available via
|
|
the JNDI API. Brokers are free to implement JNDI as they see fit assuming the
|
|
implementation fits the API. Apache ActiveMQ Artemis does not have a JNDI
|
|
server. Rather, it uses a client-side JNDI implementation that relies on
|
|
special properties set in the environment to construct the appropriate JMS
|
|
objects. In other words, no objects are stored in JNDI on the Apache ActiveMQ
|
|
Artemis server, instead they are simply instantiated on the client based on the
|
|
provided configuration. Let's look at the different kinds of administered
|
|
objects and how to configure them.
|
|
|
|
> **Note:**
|
|
>
|
|
> The following configuration properties *are strictly required when Apache
|
|
> ActiveMQ Artemis is running in stand-alone mode*. When Apache ActiveMQ
|
|
> Artemis is integrated to an application server (e.g. Wildfly) the application
|
|
> server itself will almost certainly provide a JNDI client with its own
|
|
> properties.
|
|
|
|
### ConnectionFactory JNDI
|
|
|
|
A JMS connection factory is used by the client to make connections to the
|
|
server. It knows the location of the server it is connecting to, as well as
|
|
many other configuration parameters.
|
|
|
|
Here's a simple example of the JNDI context environment for a client looking up
|
|
a connection factory to access an *embedded* instance of Apache ActiveMQ
|
|
Artemis:
|
|
|
|
|
|
```properties
|
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
|
connectionFactory.invmConnectionFactory=vm://0
|
|
```
|
|
|
|
In this instance we have created a connection factory that is bound to
|
|
`invmConnectionFactory`, any entry with prefix `connectionFactory.` will create
|
|
a connection factory.
|
|
|
|
In certain situations there could be multiple server instances running within a
|
|
particular JVM. In that situation each server would typically have an InVM
|
|
acceptor with a unique server-ID. A client using JMS and JNDI can account for
|
|
this by specifying a connction factory for each server, like so:
|
|
|
|
|
|
```properties
|
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
|
connectionFactory.invmConnectionFactory0=vm://0
|
|
connectionFactory.invmConnectionFactory1=vm://1
|
|
connectionFactory.invmConnectionFactory2=vm://2
|
|
```
|
|
|
|
Here is a list of all the supported URL schemes:
|
|
|
|
- `vm`
|
|
- `tcp`
|
|
- `udp`
|
|
- `jgroups`
|
|
|
|
Most clients won't be connecting to an embedded broker. Clients will most
|
|
commonly connect across a network a remote broker. Here's a simple example of a
|
|
client configuring a connection factory to connect to a remote broker running
|
|
on myhost:5445:
|
|
|
|
|
|
```properties
|
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
|
connectionFactory.ConnectionFactory=tcp://myhost:5445
|
|
```
|
|
|
|
In the example above the client is using the `tcp` scheme for the provider URL.
|
|
A client may also specify multiple comma-delimited host:port combinations in
|
|
the URL (e.g. `(tcp://remote-host1:5445,remote-host2:5445)`). Whether there is
|
|
one or many host:port combinations in the URL they are treated as the *initial
|
|
connector(s)* for the underlying connection.
|
|
|
|
The `udp` scheme is also supported which should use a host:port combination
|
|
that matches the `group-address` and `group-port` from the corresponding
|
|
`broadcast-group` configured on the ActiveMQ Artemis server(s).
|
|
|
|
Each scheme has a specific set of properties which can be set using the
|
|
traditional URL query string format (e.g.
|
|
`scheme://host:port?key1=value1&key2=value2`) to customize the underlying
|
|
transport mechanism. For example, if a client wanted to connect to a remote
|
|
server using TCP and SSL it would create a connection factory like so,
|
|
`tcp://remote-host:5445?ssl-enabled=true`.
|
|
|
|
All the properties available for the `tcp` scheme are described in [the
|
|
documentation regarding the Netty
|
|
transport](configuring-transports.md#configuring-the-netty-transport).
|
|
|
|
Note if you are using the `tcp` scheme and multiple addresses then a query can
|
|
be applied to all the url's or just to an individual connector, so where you
|
|
have
|
|
|
|
- `(tcp://remote-host1:5445?httpEnabled=true,remote-host2:5445?httpEnabled=true)?clientID=1234`
|
|
|
|
then the `httpEnabled` property is only set on the individual connectors where
|
|
as the `clientId` is set on the actual connection factory. Any connector
|
|
specific properties set on the whole URI will be applied to all the connectors.
|
|
|
|
|
|
The `udp` scheme supports 4 properties:
|
|
|
|
- `localAddress` - If you are running with multiple network
|
|
interfaces on the same machine, you may want to specify that the
|
|
discovery group listens only on a specific interface. To do this
|
|
you can specify the interface address with this parameter.
|
|
|
|
- `localPort` - If you want to specify a local port to which the
|
|
datagram socket is bound you can specify it here. Normally you would
|
|
just use the default value of -1 which signifies that an anonymous
|
|
port should be used. This parameter is always specified in
|
|
conjunction with `localAddress`.
|
|
|
|
- `refreshTimeout` - This is the period the discovery group waits after
|
|
receiving the last broadcast from a particular server before removing that
|
|
servers connector pair entry from its list. You would normally set this to a
|
|
value significantly higher than the broadcast-period on the broadcast group
|
|
otherwise servers might intermittently disappear from the list even though they
|
|
are still broadcasting due to slight differences in timing. This parameter is
|
|
optional, the default value is 10000 milliseconds (10 seconds).
|
|
|
|
- `discoveryInitialWaitTimeout` - If the connection factory is used immediately
|
|
after creation then it may not have had enough time to received broadcasts
|
|
from all the nodes in the cluster. On first usage, the connection factory will
|
|
make sure it waits this long since creation before creating the first
|
|
connection. The default value for this parameter is 10000 milliseconds.
|
|
|
|
Lastly, the `jgroups` scheme is supported which provides an alternative to the
|
|
`udp` scheme for server discovery. The URL pattern is
|
|
`jgroups://channelName?file=jgroups-xml-conf-filename`
|
|
where`jgroups-xml-conf-filename` refers to an XML file on the classpath that
|
|
contains the JGroups configuration. The `channelName` is the name given to the
|
|
jgroups channel created.
|
|
|
|
The `refreshTimeout` and `discoveryInitialWaitTimeout` properties are supported
|
|
just like with `udp`.
|
|
|
|
The default type for the default connection factory is of type
|
|
`javax.jms.ConnectionFactory`or `jakarta.jms.ConnectionFactory` depending on the
|
|
client you're using. This can be changed by setting the type like so
|
|
|
|
|
|
```properties
|
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
|
java.naming.provider.url=tcp://localhost:5445?type=CF
|
|
```
|
|
|
|
In this example it is still set to the default, below shows a list of types
|
|
that can be set.
|
|
|
|
#### Configuration for Connection Factory Types
|
|
|
|
The interface provided will depend on whether you're using the JMS or Jakarta
|
|
Messaging client implementation.
|
|
|
|
type | interface
|
|
--- |---
|
|
CF (default) | `javax.jms.ConnectionFactory` or `jakarta.jms.ConnectionFactory`
|
|
XA_CF | `javax.jms.XAConnectionFactory`or `jakarta.jms.XAConnectionFactory`
|
|
QUEUE_CF | `javax.jms.QueueConnectionFactory`or `jakarta.jms.QueueConnectionFactory`
|
|
QUEUE_XA_CF | `javax.jms.XAQueueConnectionFactory`or `jakarta.jms.XAQueueConnectionFactory`
|
|
TOPIC_CF | `javax.jms.TopicConnectionFactory`or `jakarta.jms.TopicConnectionFactory`
|
|
TOPIC_XA_CF | `javax.jms.XATopicConnectionFactory`or `jakarta.jms.XATopicConnectionFactory`
|
|
|
|
### Destination JNDI
|
|
|
|
JMS destinations are also typically looked up via JNDI. As with connection
|
|
factories, destinations can be configured using special properties in the JNDI
|
|
context environment. The property *name* should follow the pattern:
|
|
`queue.<jndi-binding>` or `topic.<jndi-binding>`. The property *value* should
|
|
be the name of the queue hosted by the Apache ActiveMQ Artemis server. For
|
|
example, if the server had a JMS queue configured like so:
|
|
|
|
```xml
|
|
<address name="OrderQueue">
|
|
<queue name="OrderQueue"/>
|
|
</address>
|
|
```
|
|
|
|
And if the client wanted to bind this queue to "queues/OrderQueue" then the
|
|
JNDI properties would be configured like so:
|
|
|
|
```properties
|
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
|
java.naming.provider.url=tcp://myhost:5445
|
|
queue.queues/OrderQueue=OrderQueue
|
|
```
|
|
|
|
It is also possible to look-up JMS destinations which haven't been configured
|
|
explicitly in the JNDI context environment. This is possible using
|
|
`dynamicQueues/` or `dynamicTopics/` in the look-up string. For example, if the
|
|
client wanted to look-up the aforementioned "OrderQueue" it could do so simply
|
|
by using the string "dynamicQueues/OrderQueue". Note, the text that follows
|
|
`dynamicQueues/` or `dynamicTopics/` must correspond *exactly* to the name of
|
|
the destination on the server.
|
|
|
|
### The code
|
|
|
|
Here's the code for the example:
|
|
|
|
First we'll create a JNDI initial context from which to lookup our JMS objects.
|
|
If the above properties are set in `jndi.properties` and it is on the classpath
|
|
then any new, empty `InitialContext` will be initialized using those
|
|
properties:
|
|
|
|
```java
|
|
InitialContext ic = new InitialContext();
|
|
|
|
//Now we'll look up the connection factory from which we can create
|
|
//connections to myhost:5445:
|
|
|
|
ConnectionFactory cf = (ConnectionFactory)ic.lookup("ConnectionFactory");
|
|
|
|
//And look up the Queue:
|
|
|
|
Queue orderQueue = (Queue)ic.lookup("queues/OrderQueue");
|
|
|
|
//Next we create a JMS connection using the connection factory:
|
|
|
|
Connection connection = cf.createConnection();
|
|
|
|
//And we create a non transacted JMS Session, with AUTO\_ACKNOWLEDGE
|
|
//acknowledge mode:
|
|
|
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
|
|
|
//We create a MessageProducer that will send orders to the queue:
|
|
|
|
MessageProducer producer = session.createProducer(orderQueue);
|
|
|
|
//And we create a MessageConsumer which will consume orders from the
|
|
//queue:
|
|
|
|
MessageConsumer consumer = session.createConsumer(orderQueue);
|
|
|
|
//We make sure we start the connection, or delivery won't occur on it:
|
|
|
|
connection.start();
|
|
|
|
//We create a simple TextMessage and send it:
|
|
|
|
TextMessage message = session.createTextMessage("This is an order");
|
|
producer.send(message);
|
|
|
|
//And we consume the message:
|
|
|
|
TextMessage receivedMessage = (TextMessage)consumer.receive();
|
|
System.out.println("Got order: " + receivedMessage.getText());
|
|
```
|
|
|
|
It is as simple as that. For a wide range of working JMS examples please
|
|
see the examples directory in the distribution.
|
|
|
|
> **Warning**
|
|
>
|
|
> Please note that JMS connections, sessions, producers and consumers are
|
|
> *designed to be re-used*.
|
|
>
|
|
> It is an anti-pattern to create new connections, sessions, producers and
|
|
> consumers for each message you produce or consume. If you do this, your
|
|
> application will perform very poorly. This is discussed further in the
|
|
> section on performance tuning [Performance Tuning](perf-tuning.md).
|
|
|
|
## Directly instantiating JMS Resources without using JNDI
|
|
|
|
Although it is a very common JMS usage pattern to lookup JMS *Administered
|
|
Objects* (that's JMS Queue, Topic and ConnectionFactory instances) from JNDI,
|
|
in some cases you just think "Why do I need JNDI? Why can't I just instantiate
|
|
these objects directly?"
|
|
|
|
With Apache ActiveMQ Artemis you can do exactly that. Apache ActiveMQ Artemis
|
|
supports the direct instantiation of JMS Queue, Topic and ConnectionFactory
|
|
instances, so you don't have to use JNDI at all.
|
|
|
|
> For a full working example of direct instantiation please look at the
|
|
> [Instantiate JMS Objects
|
|
> Directly](examples.md#instantiate-jms-objects-directly) example under the JMS
|
|
> section of the examples.
|
|
|
|
Here's our simple example, rewritten to not use JNDI at all:
|
|
|
|
We create the JMS ConnectionFactory object via the ActiveMQJMSClient Utility
|
|
class, note we need to provide connection parameters and specify which
|
|
transport we are using, for more information on connectors please see
|
|
[Configuring the Transport](configuring-transports.md).
|
|
|
|
```java
|
|
TransportConfiguration transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName());
|
|
|
|
ConnectionFactory cf = ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF,transportConfiguration);
|
|
|
|
//We also create the JMS Queue object via the ActiveMQJMSClient Utility
|
|
//class:
|
|
|
|
Queue orderQueue = ActiveMQJMSClient.createQueue("OrderQueue");
|
|
|
|
//Next we create a JMS connection using the connection factory:
|
|
|
|
Connection connection = cf.createConnection();
|
|
|
|
//And we create a non transacted JMS Session, with AUTO\_ACKNOWLEDGE
|
|
//acknowledge mode:
|
|
|
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
|
|
|
//We create a MessageProducer that will send orders to the queue:
|
|
|
|
MessageProducer producer = session.createProducer(orderQueue);
|
|
|
|
//And we create a MessageConsumer which will consume orders from the
|
|
//queue:
|
|
|
|
MessageConsumer consumer = session.createConsumer(orderQueue);
|
|
|
|
//We make sure we start the connection, or delivery won't occur on it:
|
|
|
|
connection.start();
|
|
|
|
//We create a simple TextMessage and send it:
|
|
|
|
TextMessage message = session.createTextMessage("This is an order");
|
|
producer.send(message);
|
|
|
|
//And we consume the message:
|
|
|
|
TextMessage receivedMessage = (TextMessage)consumer.receive();
|
|
System.out.println("Got order: " + receivedMessage.getText());
|
|
```
|
|
|
|
## Setting The Client ID
|
|
|
|
This represents the client id for a JMS client and is needed for creating
|
|
durable subscriptions. It is possible to configure this on the connection
|
|
factory and can be set via the `clientId` element. Any connection created by
|
|
this connection factory will have this set as its client id.
|
|
|
|
## Setting The Batch Size for DUPS_OK
|
|
|
|
When the JMS acknowledge mode is set to `DUPS_OK` it is possible to configure
|
|
the consumer so that it sends acknowledgements in batches rather that one at a
|
|
time, saving valuable bandwidth. This can be configured via the connection
|
|
factory via the `dupsOkBatchSize` element and is set in bytes. The default is
|
|
1024 \* 1024 bytes = 1 MiB.
|
|
|
|
## Setting The Transaction Batch Size
|
|
|
|
When receiving messages in a transaction it is possible to configure the
|
|
consumer to send acknowledgements in batches rather than individually saving
|
|
valuable bandwidth. This can be configured on the connection factory via the
|
|
`transactionBatchSize` element and is set in bytes. The default is 1024 \*
|
|
1024.
|
|
|
|
## Setting The Destination Cache
|
|
|
|
Many frameworks such as Spring resolve the destination by name on every
|
|
operation, this can cause a performance issue and extra calls to the broker, in
|
|
a scenario where destinations (addresses) are permanent broker side, such as
|
|
they are managed by a platform or operations team. using `cacheDestinations`
|
|
element, you can toggle on the destination cache to improve the performance and
|
|
reduce the calls to the broker. This should not be used if destinations
|
|
(addresses) are not permanent broker side, as in dynamic creation/deletion.
|