If you want to do inter-applet communications, you can take advantage of the fact that because all the applets run in the same instance of the virtual machine, all static class variables are shared between applets. This means you can
implement singleton classes that are shared by all the applets. As you recall from Chapter 9, a singleton is a class that has only one unique instance. Its constructor is hidden so classes cannot create new instances. You access the one instance of the class through a static method in the class such as instance(). You can create a singleton class that acts as a registry for all the applets in the runtime environment. If the registry is implemented as an observable, you can watch for new applets to become available. This allows two applets to sync up without having to constantly poll the getApplet method to see when the applet becomes available.
Listing 10.6 shows an implementation of the AppletRegistry class. The applet registry uses a hash table to store applets by name. Whenever you want to find an applet, you call findApplet with the name of the applet you're looking for:
Applet findit = AppletRegistry.instance().findApplet("FindMeApplet"); When an applet starts up, it calls addApplet:
AppletRegistry.instance().addApplet("FindMeApplet", this);
The AppletRegistry is observable. Whenever an applet is added or removed, it sends an update to its observers. This allows any object implementing the Observer interface to find out whenever an applet is added to the registry. This is a nice alternative to polling the registry for particular applets.
Since the registry sends a notification when applets are added or removed, it uses a simple event mechanism when it sends the notification. The object passed to the update method in the observers is an instance of
AppletRegistryEvent. This object, in turn, contains the information specific to the event (the name of the applet, the applet itself, and whether it is being added or removed).
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm Listing 10.6 Source Code for AppletRegistry.java
import java.applet.Applet; import java.util.*;
// This class implements an applet registry where applets // can locate each other. It is an observable, so if you want // to wait for a particular class, you can be an observer. This // is better than the polling you have to do with getApplet. //
// This class is implemented as a singleton, which means there // is only one. The single instance is kept in a protected // static variable and returned by the instance() method. public class AppletRegistry extends Observable
{
// The single copy of the registry
protected static AppletRegistry registry; // The table of applets
protected Hashtable applets;
// Used for generating unique applet names protected int nextUnique;
protected AppletRegistry() {
applets = new Hashtable(); nextUnique = 0;
}
// Returns the long instance of the registry. If there isn't a registry // yet, it creates one.
public synchronized static AppletRegistry instance() {
if (registry == null) {
registry = new AppletRegistry(); }
return registry; }
// Adds a new applet to the registry - stores it in the table and // sends a notification to its observers.
public synchronized void addApplet(String name, Applet newApplet) {
applets.put(name, newApplet); setChanged();
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
AppletRegistryEvent.ADD_APPLET, name, newApplet));
}
// Adds a new applet to the registry - stores it in the table and // sends a notification to its observers. If uniqueName is false, the // applet's name is non-unique. Store the applet in a table with a // unique version of the name (appends <#> to the name where # is // a constantly increasing number).
public synchronized void addApplet(String name, Applet newApplet, boolean uniqueName)
{
if (!uniqueName && (applets.get(name) != null)) { name = name + "<"+nextUnique+">";
nextUnique++; } applets.put(name, newApplet); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.ADD_APPLET, name, newApplet)); }
// removes an applet from the table and notifies the observers public synchronized void removeApplet(Applet applet)
{
Enumeration e = applets.keys(); while (e.hasMoreElements()) {
Object key = e.nextElement(); if (applets.get(key) == applet) { applets.remove(key); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.REMOVE_APPLET, (String)key, applet)); return; } } }
// removes an applet from the table and notifies the observers public synchronized void removeApplet(String name)
{
Applet applet = (Applet) applets.get(name); if (applet == null) return;
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm applets.remove(name); setChanged(); notifyObservers(new AppletRegistryEvent( AppletRegistryEvent.REMOVE_APPLET, name, applet)); }
// finds an applet by name, or returns null if not found public Applet findApplet(String name)
{
return (Applet) applets.get(name); }
// lets you see all the applets in the registry public Enumeration getApplets()
{
return applets.elements(); }
}
Listing 10.7 shows the implementation of the AppletRegistryEvent object used by the AppletRegistry. This is an example of how to set up an event that is passed to the update method in an observable's observers.
Listing 10.7 Source Code for AppletRegistryEvent.java
import java.applet.Applet;
public class AppletRegistryEvent extends Object {
public final static int ADD_APPLET = 1; public final static int REMOVE_APPLET = 2; public int id;
public String appletName; public Applet applet;
public AppletRegistryEvent() {
}
public AppletRegistryEvent(int id, String appletName, Applet applet) {
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
this.appletName = appletName; this.applet = applet;
} }
Figure 10.3 shows the relationship between applets, the applet registry, and the observers of the registry.
Figure 10.3 : Applets add themselves to the registry, which tells its observers about the new applets.
To take advantage of the AppletRegistry, a new applet must register itself by calling the addApplet method. Listing 10.8 shows a new version of the SenderApplet example from earlier in this chapter. The corresponding ReaderApplet only needs to call addApplet("reader", this) in its init method to support the registry. Because the new sender applet uses the applet registry, it doesn't have to contin-ually check to see when the reader is loaded. Instead, it waits until it receives an AppletRegistryEvent that tells it that the reader applet has been added. Once it learns that the reader has been added, it passes one end of the pipe stream to the reader and starts a thread that writes data to the pipe.
Listing 10.8 Source Code for SenderApplet2.java
import java.applet.*; import java.io.*; import java.util.*;
// This applet creates a stream pipe and uses it to pass // data to another applet. It waits for the other applet to // be loaded, then invokes a method on that applet to pass it // the input side of the pipe.
// Rather than using the standard getApplet method to check
// for the other applet being loaded, it uses the AppletRegistry. // Also, it doesn't start its thread until the applet has been // started and an update has been received stating that the // other applet has been added.
public class SenderApplet2 extends Applet implements Runnable, Observer {
protected PipedInputStream inStream; protected PipedOutputStream outStream; protected boolean started = false; Thread appletThread = null;
public synchronized void init()
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
{
// Create the pipe. It doesn't matter which end you create first, you just // pass the first end to the constructor of the other end.
try {
inStream = new PipedInputStream();
outStream = new PipedOutputStream(inStream); } catch (Exception e) {
e.printStackTrace(); }
// Add this applet to the registry
AppletRegistry.instance().addApplet("sender", this); // Start watching the registry
AppletRegistry.instance().addObserver(this);
// If the reader applet is already in the registry, set it up
Applet applet = AppletRegistry.instance().findApplet("reader"); if (applet != null) {
initReader(applet); }
}
// update is called by the registry whenever an applet is added or removed. public synchronized void update(Observable obs, Object ob)
{
if (!(ob instanceof AppletRegistryEvent)) return; AppletRegistryEvent evt = (AppletRegistryEvent) ob; if (evt.appletName.equals("reader")) {
initReader(evt.applet); }
}
public void initReader(Applet applet) {
// Now that we found the applet, cast it to a ReaderApplet so // we can invoke setInputStream
ReaderApplet2 reader = (ReaderApplet2) applet; // Give the ReaderApplet the input end of the stream reader.setInputStream(inStream);
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
if (started) {
appletThread.start(); }
}
public void run() {
while (true) {
// Write byte values of 0-9 to the stream pipe over and over for (int i=0; i < 10; i++) {
try {
outStream.write(i); Thread.sleep(1000); } catch (Exception ignore) { }
} }
}
public synchronized void start() {
started = true;
// If the applet thread has already been created, start it. This is // done to synchronize with the update method. We don't know if start // or update will be called first. This method sets the started flag to // true, but only starts the thread if it has been created.
// The update method creates the thread, but only starts it if the // started flag is true.
if (appletThread != null) { appletThread.start(); }
}
public void stop() {
appletThread.stop(); appletThread = null; }
}
The reader applet that corresponds to SenderApplet2 is not too different from the original reader applet. The main difference is that it now adds itself to the applet registry. It still uses a polling mechanism to wait for its input stream. Again, you could use the wait/notify mechanism to keep from polling. Listing 10.9 shows the ReaderApplet2 applet.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
Listing 10.9 Source Code for ReaderApplet2.java
import java.applet.*; import java.awt.*; import java.io.*;
// This applet is the companion to the SenderApplet. It receives // an input stream from the sender and begins reading one byte at
// a time, changing a label on the screen to the string representation // of each byte read so you can see it in action.
public class ReaderApplet2 extends Applet implements Runnable {
protected InputStream inStream; protected Thread appletThread; protected Label label;
public void init() {
label = new Label("X"); add(label);
AppletRegistry.instance().addApplet("reader", this); }
// This method will be called by the SenderApplet when it locates this // applet.
public void setInputStream(InputStream inStream) {
this.inStream = inStream; }
public void run() {
// Wait for the input stream
while(inStream == null) {
try {
Thread.sleep(1000); } catch (Exception insomnia) { }
}
// Start reading bytes
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm
try {
int ch = inStream.read(); // If ch < 0, we hit EOF, indicating some type of shutdown if (ch < 0) return;
// Update the label with the byte we just read
label.setText(""+ch); } catch (Exception e) { return; } } }
public void start() {
appletThread = new Thread(this); appletThread.start();
}
public void stop() { appletThread.stop(); appletThread = null; } } file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm (19 of 19) [8/14/02 10:53:26 PM]
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
Chapter 16
Creating 3-Tier Distributed Applications with
RMI
by Mark Wutka
CONTENTS
● Creating 3-Tier Applications ● RMI Features
● Creating an RMI Server
❍ Defining a Remote Interface
❍ Creating the Server Implementation ❍ Creating the Stub Class
● Creating an RMI Client
● Creating Peer-to-Peer RMI Applications
● Garbage Collection, Remote Objects, and Peer-to-Peer
There are many ways that objects can communicate with one another over a network. Traditionally, objects would communicate with one another using sockets and a custom protocol. Remote procedure calls have also been a popular communication mechanism for the past few years. Java provides two additional mechanisms for remote object-to- object communication. Java provides an interface into the CORBA-distributed object architecture, which is discussed in Chapter 17, "Creating CORBA Clients." Remote Method Invocation (RMI) provides a very simple method for one Java object to invoke a method in another Java object across a network with very little extra work. Unlike many remote communication systems that require you to describe the remote methods in a separate file, RMI works right off existing objects, providing seamless integration.
Creating 3-Tier Applications
The 2-tier model for applications is the most common model in use today. Many application designers think only in terms of the database and the application.
The availability of 2-tier application builders has helped perpetuate this philosophy. The 2-tier model is not a "bad thing," but there are cases in which the 3-tier model would be a better choice.
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
layer of business logic, and a database. Once you break out of the 2-tier mold, you often start adding multiple tiers. Figure 16.1 illustrates the difference between a 2-tier and 3-tier application design.
Figure 16.1 : A 3-tier design adds an extra layer of abstraction to improve reuse.
You can also divide your application into an application logic tier and a presentation tier (the user interface). In a 2- tier model, the business logic is part of the application. In smaller applications, this is not a problem because there may be only one application implementing a particular business process.
In larger systems, however, many applications use the same areas of business logic. In a 2-tier environment, this means that the business logic is replicated across every application. If you change the business logic, you must change every application.
Durable software systems are designed from the ground up with change in mind. A good designer creates modular components with well-defined interfaces so that any single component can be changed without affecting the rest of the system. The 3-tier and multi-tier models are simply the results of modular design.
Before you go off thinking that creating 3-tier designs is simple, think again. Identifying business processes in a large company and reducing them to a set of methods is not a task for the fainthearted.
Many companies do not have their business logic documented as a series of processes. Instead, it is only implied in the code of the applications. The identification of business processes and business logic is a subject for another book, however.
In practice, the line between 2-tier and 3-tier is often rather fuzzy. You may have what is essentially a 2-tier application whose user interface is broken out into a separate module.
The application logic and the business logic are still intermixed, but a portion of the application is distributed. Just because you can't get a handle on the actual business logic doesn't mean you can't still work at making your software more modular and add the benefits of distributed computing.
RMI is very useful for separating an application from its user interface. You define the methods that comprise the interactions between the user interface and the client, and then make these interactions through remote method invocation (RMI). This allows an applet to implement the user interface for an application running on a server somewhere, without developing a custom communications system.