activemq-artemis/docs/user-manual/en/using-jms.md

428 lines
17 KiB
Markdown

# Using JMS
Although ActiveMQ 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[Sun JMS
tutorial](http://docs.oracle.com/javaee/1.3/jms/tutorial) - a full JMS
tutorial is out of scope for this guide.
ActiveMQ 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 ActiveMQ 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 JMS configuration so it is created automatically
without us having to explicitly create it from the client.
JNDI Configuration
==================
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. ActiveMQ 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 ActiveMQ server. There 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
> ActiveMQ is running in stand-alone mode*. When ActiveMQ 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.
By default, a `javax.naming.Context` instance created using the
`org.apache.activemq.jndi.ActiveMQInitialContextFactory` will
automatically have the following connection factories available for
lookup:
- `ConnectionFactory`
- `XAConnectionFactory`
- `QueueConnectionFactory`
- `TopicConnectionFactory`
Here's a simple example of the JNDI context environment for a client
looking up a connection factory to access an *embedded* instance of
ActiveMQ:
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
It's really as simple as that. As noted previously, any JNDI context
created with the `ActiveMQInitialContextFactory` will have a set of
default connection factories available. Therefore, only the
`java.naming.factory.initial` property is required to access an embedded
broker.
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
`javax.naming.Context.PROVIDER_URL` (`String` value of
"java.naming.provider.url") in the JNDI environment like `vm://2` where
`2` is the server-ID for acceptor.
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. In that case the
client can use the `javax.naming.Context.PROVIDER_URL` (`String` value
of "java.naming.provider.url") in the JNDI environment to specify where
to connect. Here's a simple example of a client configuring a connection
factory to connect to a remote broker running on myhost:5445:
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=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 an host:port
combination that matches the `group-address` and `group-port` from the
corresponding `broadcast-group` configured on the ActiveMQ 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 use a
`Context.PROVIDER_URL` of `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.netty).
The `udp` scheme supports 4 properties:
- `local-address` - If you are running with multiple network
interfaces on the same machine, you may want to specify that the
discovery group listens only only a specific interface. To do this
you can specify the interface address with this parameter.
- `local-port` - 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 `local-address`.
- `refresh-timeout` - 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).
- `discovery-initial-wait-timeout` - 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 as follows
`jgroups://<jgroups-xml-conf-filename>` where
`<jgroups-xml-conf-filename>` refers to an XML file on the classpath
that contains the JGroups configuration.
The `refresh-timeout` and `discovery-initial-wait-timeout` properties
are supported just like with `udp`.
Although a `javax.naming.Context` instance created using the
`org.apache.activemq.jndi.ActiveMQInitialContextFactory` will
automatically have some connection factories present, it is possible for
a client to specify its own connection factories. This is done using the
`org.apache.activemq.jndi.ActiveMQInitialContextFactory.CONNECTION_FACTORY_NAMES`
property (String value of "connectionFactoryNames"). The value for this
property is a comma delimited String of all the connection factories the
client wishes to create. For example:
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=tcp://localhost:5445
connectionFactoryNames=myConnectionFactory
In this example, the client is creating a connection factory named
"myConnectionFactory." This replaces all the default connection
factories so that only the "myConnectionFactory" connection factory is
available to the client.
Aside from the underlying transport, the underlying connection factory
implementation can also be configured using special properties. To
configure a particular connection factory the client would follow this
pattern for the property name to set in the environment:
`connection.<connection-factory-name>.<property-name>`. For example, if
the client wanted to customize the default connection factory
"ConnectionFactory" to support high-availability then it would do this:
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=tcp://myhost:5445
connection.ConnectionFactory.ha=true
Any property available on the underlying
`org.apache.activemq.jms.client.ActiveMQConnectionFactory` can be set
this way in addition to the `ha` (boolean) and `type` (String)
properties. Here are the different options for the `type`:
#### Configuration for Connection Factory Types
<table>
<tr>
<th>type</th>
<th>interface</th>
</tr>
<tr>
<td>CF (default)</td>
<td>javax.jms.ConnectionFactory</td>
</tr>
<tr>
<td>XA_CF</td>
<td>javax.jms.XAConnectionFactory</td>
</tr>
<tr>
<td>QUEUE_CF</td>
<td>javax.jms.QueueConnectionFactory</td>
</tr>
<tr>
<td>QUEUE_XA_CF</td>
<td>javax.jms.XAQueueConnectionFactory</td>
</tr>
<tr>
<td>TOPIC_CF</td>
<td>javax.jms.TopicConnectionFactory</td>
</tr>
<tr>
<td>TOPIC_XA_CF</td>
<td>javax.jms.XATopicConnectionFactory</td>
</tr>
</table>
### 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
ActiveMQ server. For example, if the server had a JMS queue configured
like so:
<queue name="OrderQueue"/>
And if the client wanted to bind this queue to "queues/OrderQueue" then
the JNDI properties would be configured like so:
java.naming.factory.initial=org.apache.activemq.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 ActiveMQ you can do exactly that. ActiveMQ 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 see the JMS
examples in ?.
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 `client-id` 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 `dups-ok-batch-size`
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 `transaction-batch-size` element and is set in bytes.
The default is 1024 \* 1024.