Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Mastering Enterprise JavaBeans™ and the Java 2 Platform, Enterprise Edition - Roman E

..pdf
Скачиваний:
41
Добавлен:
24.05.2014
Размер:
6.28 Mб
Скачать

Understanding Java Remote Method Invocation (RMI) 533

RMI Example: A Message Queue for Distributed Logging

Traditionally, when software components communicate, they do so in a synchronous manner. That is, when component A invokes a method on component B, component B executes immediately. Component A must block (that is, wait) until component B is done. This is shown in Figure A.10.

In many scenarios (especially server-side development), however, it is nice to have components communicate in an asynchronous manner. This means component B does not execute immediately. It also means component A does not block. How is this accomplished?

One way is to get a third party involved. The third party acts as a storage for deferred invocations. Each invocation component A makes is stored as a message

Component A

 

Component B

 

 

 

invoke()

<<wait>>

<<perform work>>

<<return>>

Figure A.10 Synchronous communication.

Go back to the first page for a quick link to buy this book online!

534 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

in this third party’s internal queue. The queue is used to keep an ordering of the messages. When component B wants to service the invocation, it contacts the third party and reads the message off the queue. Component A is a producer of data for the queue, and component B is a consumer of data. By using this networked queue of data, we can have distributed components service requests at their leisure, in an asynchronous manner.

This technology is called Message Queueing. A Message Queue is the queued storage that stores messages from distributed objects across the enterprise. They are responsible for accepting new messages as they arrive and delivering others as they are requested. Message Queues can also have their messages be transactional in nature, gaining all the benefits of transactions that we listed in Chapter 10. Message Queues are shown in Figure A.11.

Message Queues fall under the general software heading of Message-Oriented Middleware (MOM). There are a number of MOM vendors on the market, including IBM, Microsoft, and BEA Systems. In the Java world, you can accomplish

Component A

 

Message Queue

 

Component B

 

 

 

 

 

<<put message>>

<<return>>

 

<<get message>>

<<return>>

<<perform work>>

Since Component A stores messages on the queue, it is now free to perform other operations. Component B can process the request at its leisure.

Figure A.11 Asynchronous communication using Message Queues.

Go back to the first page for a quick link to buy this book online!

Understanding Java Remote Method Invocation (RMI) 535

MOM tasks by using the Java Messaging Service (JMS). JMS is a high-level API for using different MOM products.

In this example, we’ll be developing our own stripped-down Message Queue using Java RMI. The Message Queue we develop will be useful for illustrating asynchronous networked producer-consumer relationships, as well as giving you hands-on experience with RMI.

The Channel Class

The Message Queue we create will be useful for a variety of domains. It is possible that several different sets of components would want to use the Message Queue at the same time, but for different purposes. For example, one set of components might want to use the Message Queue as a distributed logging facility. One or more logging components could store objects on the queue that represented information to be logged. Other listener components could take messages off the queue, logging the information in some way—say, one listener could represent the logs in a UI, while another could log it to a text file.

Thus, for a generic logging mechanism, we need some concept of differentiating messages on our queue. We provide this functionality with the concept of a channel. A channel represents a particular queue that one or more components are using. This is shown in Figure A.12. If two sets of components are using different channels, they won’t interfere with each other. You send and receive data

 

 

Receive

 

 

message

 

Put message on Channel 1

Message

Component A

 

 

Queue

 

 

 

 

Receive

 

 

message

Component B (Subscribed to Channel 1)

Component C (Subscribed to Channel 1)

Component D (Subscribed to Channel 2, so does not receive message)

Figure A.12 Different channels in our Message Queue.

Go back to the first page for a quick link to buy this book online!

536 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

on only the channel you’re using, much as if it were a TV channel or radio frequency that you were tuning in. The code for the Channel class is shown in Source A.4.

package com.wiley.compBooks.roman.rmi.mq;

import java.util.Random;

/**

*Abstraction representing a channel for sending messages.

*Running Java code can make as many copies of this as necessary,

*so that multiple distributed objects can communicate on the

*same channel.

*/

public class Channel implements java.io.Serializable {

// A single, static randomizer for channel id generation

private static Random rand = new Random(System.currentTimeMillis());

private String id, name;

/**

*Creates a new Channel.

*@param name The name of the channel. */

public Channel(String name) {

this.name = name;

/*

* Create a new, random identifier for this channel */

this.id = String.valueOf(rand.nextInt());

}

/**

*Because we use Channels as elements of a hashtable, we

*hash based on the channel id. We don't hash based on

*the Channel name because that may not be unique.

*/

public int hashCode() { return id.hashCode();

}

public boolean equals(Object obj) { return id.equals(obj.toString());

}

Source A.4 Channel.java (continues).

Go back to the first page for a quick link to buy this book online!

Understanding Java Remote Method Invocation (RMI) 537

public String toString() { return id;

}

/**

* Returns the channel name. */

public String getName() { return name;

}

}

Source A.4 Channel.java (continued).

The IMessageQueue Interface

The Message Queue itself will be structured as follows. The Message Queue class, MessageQueue, is accessed by RMI clients using its remote interface, IMessageQueue. IMessageQueue exposes methods to (for example) put and get messages on internal queues representing specific channels. All Message Queue operations happen through this interface. The code for IMessageQueue is shown in Source A.5.

Notice that each method throws a RemoteException, as required by RMI.

package com.wiley.compBooks.roman.rmi.mq;

import java.rmi.Remote;

import java.rmi.RemoteException; import java.io.InputStream;

/**

*Remote Interface for the Message Queue. Clients should use

*this API for communicating with the Message Queue.

*/

public interface IMessageQueue extends Remote {

/**

*Initializes a channel for a particular client. Our

*MessageQueue will initialize an internal queue for

*storing messages on that channel.

*

* @param name The name of the channel to create.

*

Source A.5 IMessageQueue.java (continues).

Go back to the first page for a quick link to buy this book online!

538 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

* @return The newly created channel */

public Channel createChannel(String name) throws MessageQueueException, RemoteException;

/**

* Call to close channel */

public void destroyChannel(Channel channel) throws MessageQueueException, RemoteException;

/**

*Waits for the next message to occur in this channel,

*and returns that message.

*/

public Object getMessage(Channel channel, long timeout) throws MessageQueueException, RemoteException;

/**

* Puts a message on the queue of a particular channel. */

public void putMessage(Channel channel, Object message) throws MessageQueueException, RemoteException;

}

Source A.5 IMessageQueue.java (continued).

The MessageQueueException Exception

If you noticed in the preceding code, each method on the IMessageQueue interface also threw a MessageQueueException. This is an application-level exception, and it is meant to denote logical problems, such as invalid channels. We’ll reserve the java.rmi.RemoteExceptions for Java RMI to indicate networking problems. This is a good design philosophy that you should follow when writing your RMI programs (in fact, EJB enforces the separation of application-level and system-level exceptions, which we describe in Chapter 4).

Our exception simply extends the normal java.lang.Exception class. The code for this command is detailed in Source A.6.

The MessageQueue Class

Internally, our MessageQueue class uses a hashtable for storing messages. Each hashtable entry represents one channel. Thus, we use channel identifiers as hash values for the hashtable. The value of each hash entry is a queue—to store messages for that particular channel. Thus, we’re using a hashtable of queues. This

Go back to the first page for a quick link to buy this book online!

Understanding Java Remote Method Invocation (RMI) 539

package com.wiley.compBooks.roman.rmi.mq;

/**

* Exceptions thrown by MessageQueue */

public class MessageQueueException extends Exception {

public MessageQueueException() { super();

}

public MessageQueueException(Exception e) { super(e.toString());

}

public MessageQueueException(String s) { super(s);

}

}

Source A.6 MessageQueueException.java.

enables us to have as many channels as we want, with a message queue for each channel. This is illustrated in Figure A.13.

Hashtable

 

 

 

 

 

 

 

Channel 1:

 

Message 1A

 

Message 1B

 

Message 1C

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Channel 2:

 

Message 2A

 

 

 

 

 

 

 

 

 

 

 

Channel 3:

 

Message 3A

 

Message 3B

 

 

 

 

 

 

 

 

 

 

Figure A.13 Our Message Queue architecture.

Go back to the first page for a quick link to buy this book online!

540 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

The actual channel identifiers are abstracted out by the Channel class. You create a new Channel by calling the createChannel method on IMessageQueue. You can pass these Channels around, clone() them, and so on. The Channel object implements java.io.Serializable, so you pass it around by value. By giving each client the same Channel object, you can have many clients all sharing the same channel.

The following procedure shows how the Message Queue can be typically used:

1.Start up the MessageQueue remote object server. MessageQueue registers itself with the local RMI registry.

2.Your client looks up the IMessageQueue object over the network using the RMI lookup facilities.

3.Your client calls IMessageQueue.createChannel() to create a new channel and queue for storing messages. This returns a Channel object.

4.Your client can then give the Channel object to any other party, such as another process that wants to send or receive messages on the same channel.

5.Anyone who owns a Channel object can send messages by calling IMessageQueue.putMessage() with the correct Channel. The messages can be any Java objects. Anyone who wants to receive messages can similarly call IMessageQueue.getMessage().

6.After everyone’s done using the Message Queue, the channel can be destroyed by calling IMessageQueue.destroyChannel().

One interesting thing about our Message Queue is that we don’t dictate what kinds of objects are passed. All we say is that each object has to be a java.lang.Object. This means that both java.io.Serializable messages and java.rmi.Remote messages are possible, for both pass-by-value and pass-by-reference semantics. If the client tries to store non-RMI-safe objects, a java.rmi.RemoteException will automatically be thrown.

The basic idea of a Message Queue brings up a very basic Computer Science problem called the readers/writers problem: What if someone tries to read a message when there are no messages on the queue?

We have two possibilities here. We can either throw an exception if there is no data available, or we can wait for data to become available. The problem with throwing an exception is that client code needs to continuously poll the message queue for any available messages. This is definitely not a scalable solution.

Therefore, we’ll enforce that any clients who request messages are suspended until a message has arrived. To do this, we use Java’s built-in threading support. When a client asks for a message, if the queue is empty we wait() the thread, suspending the client. This means that both the client’s thread and the remote

Go back to the first page for a quick link to buy this book online!

Understanding Java Remote Method Invocation (RMI) 541

Threads and Transactions

Our message queue synchronizes all operations to putMessage() and getMessage() messages on channel queues. This effectively makes each put and get a critical section, and it ensures that every put and get operation is atomic with respect to other threads.

If you’ve read the transactions chapter of this book (Chapter 10), you may recognize how analogous this is to performing atomic transactions. This is no accident—transac- tions and object synchronization are highly related.

calling thread are effectively suspended. When someone puts a new message on the queue, we call notifyAll(), which wakes up any clients that we called wait() on.

Of course, strictly speaking, we should also consider the opposite half of the readers/writers problem: What happens if the queue overflows when there are too many messages on it? We’ll make sure to implement our queue to be theoretically unlimited in length, so we don’t have to worry about that situation. The code for MessageQueue is given in Source A.7.

package com.wiley.compBooks.roman.rmi.mq;

import java.util.*; import java.io.*; import java.rmi.*;

import java.rmi.registry.*; import java.rmi.server.*;

import com.objectspace.jgl.Queue;

/**

*MessageQueue is a generic Buffer for passing information.

*The MessageQueue is a Hashtable, where each hash entry

*represents a "channel" that someone wishes to listen to.

*Each hash entry is a queue of messages for that channel.

*Listening threads are suspended until messages arrive.

*This allows networked applications to take advantage of

*event-driven networked programming but have events occur

*synchronously.

*/

public class MessageQueue extends UnicastRemoteObject implements IMessageQueue {

Source A.7 MessageQueue.java (continues).

Go back to the first page for a quick link to buy this book online!

542 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

/**

* main() creates the Message Queue. */

public static void main(String args[]) { if (args.length != 1) {

System.err.println(

"Usage: MessageQueue <port to bind RMI Registry to>"); System.exit(-1);

}

try {

MessageQueue queue = new MessageQueue(Integer.parseInt(args[0]));

}

catch (Exception e) { System.err.println(e);

}

}

/*

* Hashtable for storing channel queues of messages */

private Hashtable dataHash = new Hashtable();

/**

*Initialization.

*@param port Port to register MessageQueue at.

*/

public MessageQueue(int port) throws Exception, RemoteException { super();

System.out.println("");

System.out.println("MessageQueue starting up...");

/*

* Set the security manager */

try { System.setSecurityManager(

new java.rmi.RMISecurityManager());

}

catch (java.rmi.RMISecurityException e) {

throw new Exception("Security violation " + e.toString ());

}

/*

* Start up an RMI Registry at designated port

Source A.7 MessageQueue.java (continues).

Go back to the first page for a quick link to buy this book online!