Technical Support
Discussion Forum
Online Training
Technical Articles
Bug Parade
JDC Resources
DukeDollars
Early Access

Java Cup Logo

JDC Home Page

JDC Applets
chat
newsreader
calendar

Log Out

Technical Articles
shadowSearchFAQFeedback

Read about Java Index | Java In-Depth Index
Writing a Generic Service Loader: How to take advantage of Java's flexibility and power
By Tim Stefanini

When I think about the strengths of Java, I always think of two things: its networking classes, and its ability to instantiate a class from a string. These features get their support through part of the class loader mechanism, which enables a web browser to load your applet's classes. In a large development project, it can be really useful to be able to specify in a .ini file, which support the services you would like to have running. To demonstrate this, I have created two services: CapService, which capitalizes a string, and ReverseService, which reverses a string. While these examples are simple to illustrate, a service can also be a much larger program, even a web server. The following classes demonstrate how to build a dynamic service loader.

Our implementation begins with the definition of an interface--called ServiceInterface (ServiceInterface.class)--for all of our Services:

   public interface ServiceInterface
   {  //++ Public Methods
      public void      StartUp();
      public void      ShutDown();
      public void      Activate();
      public void      Deactivate();
      public boolean   IsActive();
   }//end interface ServiceInterface
A service is a process that provides some functionality to a client. Services are usually multithreaded and communicate via network sockets. This interface lets services be dynamically activated or deactivated. A call to StartUp() initiates the main service thread.

This interface is implemented by the class GenericService, which is defined as abstract, which of course means that you are not allowed to create an instance of this class directly. But you can create a class that is inherited from the abstract base class, which is generally a good technique, because it documents what is generic in your class hierarchy:

   public abstract class GenericService implements Runnable, 
        Cloneable, ServiceInterface
(The opposite of abstract classes are final classes, which can't be extended. Using final classes can increase performance, but a good rule of thumb is that you should only use this if the class is really not reusable.)

GenericService creates a thread, which manages a ServerSocket class. This class is used by calling its Accept() method, which blocks until it receives a connection and returns the socket that is used to communicate with your client:

   final public void run()
   {    
      Socket ns = serverSocket.accept();
      GenericService n = (GenericService)clone();
   }   
At this point, the GenericService clones itself to create a thread to process the request. This is a good technique for using an abstract base class when you don't know how difficult the request is to process. In our example, our service could probably process the request faster than the overhead of spawning a new thread.

Once we have our abstract service class, we need only write some specific services. To do this, all we have to specify is the method that processes a service request. Here our CapService takes the input String and calls .toUpperCase:

   public class CapService extends GenericService implements 
      PortInterface

   public void serviceRequest() throws IOException
   {  
      try
      {    System.out.println("in serviceRequest for server " +
                    getClass().getName() + "\n");
           String foo = null;
           while ( (foo = clientInput.readLine()) != null)
              clientOutput.print("server: " + foo.toUpperCase() + "
                    :server\r\n")
              clientOutput.flush();
      } //end try
      catch (Exception e)
      { 
         e.printStackTrace();
      }//end catch
    } //end serviceRequest()
I've provided a port interface to each service to provide an easy way for our client to get access to it:
   public static int GetPort()
   { 
      return ClassPort;
   }  //end getPort();
Now that I've defined the services and tested to see that they work, I'll start the design of the service loader. The first step is to make a service definition class which will use the ServiceInterface that I've already created:
   class ServiceDef (ServiceDef.class)
   {   
       public ServiceDef (String ClassName, String ClassPath)
       {   
           mstrClassName = ClassName;
           mstrClassPath = ClassPath;
       }//end constructor

       public String ClassPath ()
       {   
           return mstrClassPath;
       }//end ClassPath()

       public String ClassName ()
       {   
           return mstrClassName;
       }//end ClassName()

       public  ServiceInterface ServiceObj ()
       {   
           return mService;
       }//end ServiceObj()

       protected void Load ()
       {   
           try
           {   
               ClassoClass = Class.forName (mstrClassPath);
               mService = (ServiceInterface) oClass.newInstance ();
           }//end try
           catch (Exception e)
           { 
               e.printStackTrace();
           }//end catch
       }//end Load()

    //protected data members
    
       protected String mstrClassName;
       protected String mstrClassPath;
       private ServiceInterface mService;
   }//end class ServiceDef
This class maintains a private object--mService--that uses ServiceInterface as its type. When you call the Load() method, use Class.forName(), which returns the class. Calling oClass.newInstance() is the same as saying "new Class." So, Load() creates a new instance of the class and saves it in its member variable mService.

The last two classes-- ServiceLoader and GenericClient-- show how to use this system. ServiceLoader first constructs a vector of class names and class paths called mServiceList. (It is important to specify the full package path when calling Class.forName(). If CapService were in the package util.serviceloader, you would have needed to specify util.serviceloader.CapService as its classpath name.) The second step is to useLoad() on each object in the vector to create the service. Then make the call to Activate() and StartUp():

   private void LoadServiceClasses()
   {    
       try
       {   
           int iCount = mServiceList.size();
           for (int i = 0; i < iCount; i++)
           {   
               ServiceDef oServiceDef = (ServiceDef)
                            mServiceList.elementAt(i);
               oServiceDef.Load();
               ServiceInterface oServiceObj = 
                            oServiceDef.ServiceObj();
               String strKey = oServiceDef.ClassName ();
               System.out.println("Starting: " + strKey + " ...");
               oServiceObj.Activate ();
               oServiceObj.StartUp ();
           }//end for
       }//end try
       catch (Exception e)
       {   
           e.printStackTrace();
       }//end catch
   }//end LoadServiceClasses
In the client program, you need only create a service loader and then use the services:
   //example use of Dynamic Service Loading
   public class GenericClient 
   {
      public static void main(String[] args)
      {   
          try
          {   
              //Load and Start all Service Classes
              new ServiceLoader(); 
The extra work in creating a PortInterface now becomes very useful because you can now access the service easily and create a socket to it:
   //Create Input and Output Sockets for Service1 - CAPS
   //Get port for Service Type
   int iServerPort = CapService.GetPort(); 
   //Create Socket
   Socket CapSocket = new Socket("localhost", iServerPort );

   //Create Input and Output Sockets for Service2 - Reverse
   int iServerPort2 = ReverseService.GetPort();
   Socket ReverseSocket = new Socket("localhost", iServerPort2 );

   //Use Caps Service
   PushToService("This is a fine class", CapSocket);
   //Use Reverse Service
   PushToService("This is a fine class", ReverseSocket);
Now, using services is easy. PushToService retrieves the input and output streams from the socket, prints the result, and returns it as a string. You can enhance this solution by managing byte[] data from the sockets rather than strings, or adding a more sophisticated scheduling interface to automatically start and stop services over time.

By building up a library of services, you provide a solution that is dynamically configurable at runtime for each user environment. Java's ability to create a class by name makes this easy. And Java applets require sophisticated, dynamic class loading, Java comes with some very powerful capabilities for client/server design built right into it. This program is an interesting example that shows you how to take advantage of these powers.

Download the zipped file which contains the necessary files for Stefanini's generic service loader.

BulletWhat You Can Learn From Tim Stefanini About Threads, Efficiency, and Understanding Java

Bullet Sliding Buffers






Questions?
27-Oct-97
Copyright © 1996,1997 Sun Microsystems Inc.
All Rights Reserved.
Sun Logo