![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]()
![]()
chat newsreader calendar
|
![]() ![]() ![]() ![]() ![]() |
||
|
||
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 ServiceInterfaceA 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 ServiceDefThis 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 LoadServiceClassesIn 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.
|
||
|
|
|||
|
![]() |
Questions? 27-Oct-97 Copyright © 1996,1997 Sun Microsystems Inc. All Rights Reserved. |
![]() |