http://www.developer.com/

Back to article

Web Services-Not Always the Best Solution


May 28, 2009

From the moment XML was thrown at the IT world, there's been a flurry of activity to port nearly every single communications interface to an XML-based one. In most cases, vendors have provided a SOAP-based interface, but sometimes they also replace the original proprietary protocol with an XML-based one. Exposing a SOAP interface made sense in a lot of cases—especially for middleware components that needed to be "consumed" by a variety of clients implemented in various programming languages. However, in other cases, web services were used to interconnect Java-based components, replacing the older, more "traditional" methods (such as RMI). I've even seen experienced a lot of cases where a proprietary socket-based protocol was replaced by an XML-based one, even though the only way clients would use this protocol was through an in-house-written API supplied to clients.

There's little doubt that some of the interface-replacement work was due to the "hype" factor around XML, and also due to a misunderstanding of what happens under the hood when dealing with XML.

The remainder of this article is a simple comparison among a few differing implementations of a simple service (an address book), along with a discussion of the implications of choosing each solution.

AddressBook Service Entities

First, you define the data entities that the service will store and retrieve from the address book. A simple Java Bean class sufficed, ingeniously named AddressBean:

public class AddressBean implements Serializable
{
   public AddressBean( String name, String street, String city, 
      String postCode )
   {
      this.name = name;
      this.street = street;
      this.city = city;
      this.postCode = postCode;
   }
   
   public AddressBean()
   {
      this( null, null, null, null );
   }
   
   private String       name;
   public String getName() 
   {
      return name;
   }
   public void setName(String name) 
   {
      this.name = name;
   }
   
   private String      street;
   public String getStreet() 
   {
      return street;
   }
   public void setStreet(String street) 
   {
      this.street = street;
   }
   
   private String      city;
   public String getCity() 
   {
      return city;
   }
   public void setCity(String city) 
   {
      this.city = city;
   }
   private String      postCode;
   public String getPostCode() 
   {
      return postCode;
   }
   public void setPostCode(String postCode) 
   {
      this.postCode = postCode;
   }
   public String toString()
   {
      return "" + name + " : " + street + " , " + city + 
         " , " + postCode;
   }
}

The preceding AddressBean implementation has just four properties: name, street address, city and post code You'll find the implementation of this class in the bl (for "business logic") package in the downloadable code. The implementation overrides the toString function to provide a way to "inspect" the object by simply printing it out to the console when needed. Also note that the class is Serializable, so you can use it directly when dealing with communications schemes such as RMI or serialized objects over sockets.

The address book service will manipulate a list of AddressBean references. To keep things simple, the address book will have only one simple function: to search for an address by name. Granted, this approach (and the AddressBean structure) limits the functionality of an address book drastically, however, bear in mind that the point is to investigate ways to connect two systems so they can pass data back and forth, so there's no need to complicate the data structure or the interface.

In addition, a real-world address book would use a persistent data store to hold entries, but again, because data manipulation isn't pertinent to the article, the example uses in-memory storage the address book entries—and not even a structured one—just an array of AddressBean instances. Therefore, the implementation of finding an address based on a name will simply traverse the array and return the first item that has a matching name.

The AddressBook class provides this simple service implementation:

public class AddressBook
{
   private AddressBean[]   friends = { 
      new AddressBean("Liv", "Oxford Street", "London", "SW1"),
      new AddressBean("The Queen", "Buckingham Palace", "London", "WC1")
   };
   
   public AddressBean findFriend( String name )
   {
      if( null == name ) return null;
      for( int i = 0; i < friends.length; i++ )
         if( name.equals(friends[i].getName()) )
            return friends[i];
      return null;
   }
}

This class occasionally gets wrapped by other classes to offer the address book's functionality over different protocols. For each protocol, the example will measure the elapsed time on the client from the moment it places the call until it receives (and unpackages) the result. To do that, the clients need some rudimentary time measurement capabilities. Those of you who read my previous article on recursion versus iteration, are familiar with the SimpleMeasurement interface. Here, however, because the examples need only timing data, the interface exposes only one method, getNanos():

public interface SimpleMeasurement 
{
   public long getNanos();
}

Just as before, here's a SimpleMeasurementImpl class that all the clients will extend to provide simple timing measurements:

public class SimpleMeasurementImpl implements SimpleMeasurement 
{
   protected long timeStart = 0;
   protected long timeEnd    = 0;
   
   public long getNanos() 
   {
      return (timeEnd - timeStart);
   }
}

Each client will implement this class and set the timeStart and timeEnd data member at the beginning and end of each call they make to the service to System.nanoTime(); the rest is automatic. The main client is a simple console-based app that instantiates one of the "specialized client" implementations, invokes it, searches for a particular name, and then displays the results and the time required to retrieve the result ("specialized client" here refers to a class that can invoke the address book service using SOAP, RMI, etc.) Therefore, you need a common behavior for all the specialized clients—an interface that defines the address book service functionality:

public interface AddressBookContract extends SimpleMeasurement
{
   public AddressBean findFriend( String name );
}

Note that the interface also forces the implementation of the SimpleMeasurement interface, to ensure that all the specialized clients provide time measurements.

With that interface in place, you need a way to switch between the various specialized client implementations. A simple factory function in the client class suffices, so the console-based client looks like this:

public class AddressBookClient 
{
   private static final String      NAME   = "Liv";
   
   
   public static AddressBookContract getClient()
   {
      // factory function that will return various specialized client 
      // implementations
      // ...
   }
   
   public static void main( String args[] )
   {
      AddressBookContract client = getClient();      
      AddressBean result = client.findFriend( NAME );
      System.out.println( client.getClass() + " found "  + 
         result + " in " + client.getNanos() );
   }
}

The console client will create a client using the factory function, find a specific contact, and then print its complete class name, the results and the elapsed time.

With the groundwork in place, you can begin creating the specialized implementations for both the service and the client using various approaches.

Communicating via Web Services

The web service-based implementation of the address book service uses Apache Axis2, because it lets you deploy simple Java classes as web services using a manifest-like file (services.xml) that defines the web service.

Author's Note: I won't go into the structural details of the services.xml file or how to deploy the web service itself, as that's outside the scope of this article. You can find detailed information on the Apache Axis2 website.

The services.xml file should look like this:

<service name="addressbook">
    <description>
        This is a sample AddressBook Web Service 
        with one operation: findFriend
    </description>
   <messageReceivers>
     <messageReceiver mep=
       "http://www.w3.org/2004/08/wsdl/in-only" 
       class="org.apache.axis2.rpc.receivers.
          RPCInOnlyMessageReceiver"/>
     <messageReceiver mep=
       "http://www.w3.org/2004/08/wsdl/in-out" 
       class="org.apache.axis2.rpc.receivers.
          RPCMessageReceiver"/>
    </messageReceivers>
    
    <parameter name="ServiceClass" 
      locked="false">bl.AddressBook</parameter>
 </service>

The "specialized" client for this service is based on the Axis2 packages. You can find it in the AddressBookWSClient class (you may need to change the URL depending on your Axis2 and/or Tomcat configuration):

public class AddressBookWSClient 
   extends SimpleMeasurementImpl 
   implements AddressBookContract
{
   @Override
   public AddressBean findFriend(String name) 
   {
      timeStart = System.nanoTime();
      try
      {
         RPCServiceClient client = new RPCServiceClient();
         Options options = client.getOptions();
         EndpointReference targetEPR = new EndpointReference(
            "http://localhost/axis2/services/addressbook");
         options.setTo(targetEPR);
         QName op = new QName("http://bl", "findFriend");
         Object[] opFindArgs = new Object[] { name };
         Class[] returnTypes = new Class[] { AddressBean.class };
         Object[] response = client.invokeBlocking(op, 
            opFindArgs, returnTypes);
         AddressBean result = (AddressBean) response[0];
         return result;
      }
      catch( Exception e )
      {
         e.printStackTrace();
         return null;
      }
      finally
      {
         timeEnd = System.nanoTime();
      }
   }
}

Note that this is a synchronous rather than an asynchronous client, which means that when the client.invokeBlocking call finishes, communication with the web service is complete, and all the data has been sent, received, and processed.

Assuming that you deployed the web service to Tomcat and everything is ready, you can now run the web service client look at the output:

class webservice.client.AddressBookWSClient found 
   Liv : Oxford Street , London , SW1 in 1,257,869,940

Remember that the time at the end of the output is in nanoseconds, so the above number translates to about 1.25 seconds.

Author's Note: It's worth mentioning that I ran both Tomcat and the client on the same machine. While it's possible that having both processes running on the same machine might slow each other down, running them on the same machine also eliminates all other network traffic, because all the communication happens via localhost, so one should compensate for the other. Also, as discussed in my previous article, there are faster PC's out there than mine that will produce sub-second results; however, the speed of the test machine is not relevant, because the execution times are compared only against each other. I believe that the comparisons will show similar relative results on both faster (and slower) PC's.

Using an XML-Based Protocol

As mentioned earlier, another approach is to replace a proprietary TCP/IP-based protocol with an XML-based one. In this approach, server components listen for incoming connections. When a client connects, communication between the client and the server still takes place over sockets; but clients send requests as XML, the server returns XML, and the client parses the reply. To test this approach, here's the "protocol" the client and the server will follow:

Clients send requests using this format:

<request>
   <type>findFriend</type>
   <param>name of the person</param>
</request>

The server will reply with the following format:

<response>
   <results>
      <result>
         <name>person name</name>
         <street>street address</street>
         <city>city address</city>
         <postcode>post code</postcode>
      </result>
   </results>
</response>

If the query doesn't find a match, the <results> element will be empty.

In most cases, an XML Schema or DTD defines the communications protocol, and serves to validate data exchanged on both ends. While validation will find any corrupt messages being passed between the client and the server up-front, it also adds to the execution time. In this case, because you have complete control over both the protocol and the communications API, the code example doesn't validate the messages against a schema or a DTD, it simply assumes that they will always be correct.

Just as before, the system will have two components: a server that listens for incoming connections on TCP port 8899 (you can change this constant in the source code to any other value as long as it doesn't conflict with another service running on that port), and a client that simply connects, sends the request, and then waits for the response, which it parses and returns.

When a client establishes a connection, the server spawns a thread to deal with that connection. The server aggregates the AddressBook service and uses it to search the address book when prompted to do so by the client—in other words, it's just a wrapper for the AddressBook service. The server and client use the XML message formats shown earlier.

Author's Note: The sample code builds the XML manually. It's unlikely you would do that in the real world; in most cases you'd probably want to build an in-memory DOM and then serialize it. However, the concern in this article is performance, so I've taken the liberty of eliminating this extra layer in our code.

protected void sendRequest( String name, OutputStream o ) 
   throws IOException
   {
      PrintWriter w = new PrintWriter( o );
      w.println("<?xml version="1.0" " + 
         "encoding="ISO-8859-1"?><request>" + 
         "<type>findFriend</type><param>" + name + 
         "</param></request>" );
      w.println();
      w.flush();
   }

The server assembles the response in a similar fashion:

protected void sendResponse( AddressBean a, OutputStream s ) 
   throws IOException
   {
      PrintWriter w = new PrintWriter( s );
      w.println( "<?xml version="1.0" " + 
         "encoding="ISO-8859-1"?><response>" );
      if( a == null )
         w.println( "<results/>" );
      else
      {
         w.println( "<results>" );
         w.println( "<result>" );
         w.println( "<name>" + a.getName() + "</name>" );
         w.println( "<street>" + a.getStreet() + "</street>" );
         w.println( "<city>" + a.getCity() + "</city>" );
         w.println( "<postcode>" + a.getPostCode() + "</postcode>" );
         w.println( "</result>" );
         w.println( "</results>" );
      }
      w.println( "</response>" );
      w.println();
      w.flush();
   }

Both client and the server apply the same pattern to process incoming messages: They read all the lines from the incoming stream until they encounter an empty line. They pass the read block through a SAX parser. (The reason for doing that instead of passing the socket InputStream directly to the SAX parser is that the SAX parser reads from the stream until it reaches EOF—which in sockets language means closing the socket—and that ends communication between the client and server, preventing the server from replying to the request.) Instead, you just need to make sure that each request and response is followed by a blank line, which functions as the "end of message" marker. The Utility class implements this little hack:

public static Reader readXML( InputStream s ) throws IOException
{
   BufferedReader br = new BufferedReader( new InputStreamReader(s) );
   StringBuilder sb = new StringBuilder();
   String temp = null;
   boolean stop = false;
   while( !stop )
   {
      temp = br.readLine();
      if( temp != null )
         sb.append( temp );
      if( temp.equals("") )   //read until the first empty line
         stop = true;
   }
   StringReader sr = new StringReader( sb.toString() );
   return sr;
}

You need two SAX parsers: one for the request format (RequestParser) and one for the reply (ResponseParser). Both parsers simply look for certain elements and store the text inside those tags in a buffer used to accumulate the results. While this might not be the most elegant solution, it certainly is one of the fastest as you can find out now by changing AddressBook client to instantiate XMLSerAddressBookClient and use that. Here's the output:

class xmlser.XMLSerAddressBookClient found 
   Liv : Oxford Street , London , SW1 in 584,239,804

The output shows that taking this approach reduced our client execution time to about 0.58 seconds (about half the time required to execute the SOAP client). That shouldn't be surprising, because the XML-based messaging approach simplified the structure of the exchanged messages (the SOAP message are far more complex), which reduced both the message size and also simplified the parsing process. In addition, this approach avoids validating the XML messages, which again reduces the execution time. Finally, the server assembles the reply message assembly by simple text concatenation, rather than operating against a DOM which then gets serialized.

So, eliminating SOAP from the communication loop while still using XML cuts the system response time roughly in half. SOAP partisans will argue that SOAP is a W3C standard, so it's supported "out-of-the-box" by the majority of frameworks, libraries and languages. They'll also probably point out that SOAP offers firewall penetration by using the standard HTTP port (rather than port 8899). However, from an implementation viewpoint, the difference between using a library that supports SOAP out of the box and using a custom XML format is pretty much limited to writing a SAX parser (which is not difficult). As for firewall penetration, there's nothing that prevents you from runnign this service on port 80. Even better, rather than having a TCP/IP server deal with sockets, you could wrap the AddressBook service in a servlet that takes its parameters from the query string. Using that scheme, the execution times should be similar to this example. Because this uses an XML-based protocol, there are parsers available for all the commonly used programming languages—connectivity isn't limited to Java-based implementations for either the client or the server.

The RMI Implementation

The RMI approach assumes that both client and server are Java-based; it's a "pure Java" solution. When RMI first appeared, it triggered the same hype in the Java world as SOAP did a few years ago. Nowadays though, it seems that everyone has dropped RMI in favor of using web services for interconnecting applications.

Author's Note: If you're not familiar with writing RMI applications, see this short Sun tutorial. I won't go into detail about how RMI works here; instead, I'll concentrate on actually implementing the example over RMI.

First, you need to define the RMI interface. An RMI interface must extend the Remote interface, so you can't use the AddressBook contract interface—at least not without altering the interface hierarchy which might be a bit confusing. Instead, here's a new interface for the RMIAddressBook application that offers the same functionality as the AddressBookContract, but is based on the Remote interface:

public interface RMIAddressBook extends Remote 
{
   public AddressBean findAddress( String name ) 
      throws RemoteException;
}

Next, you need to implement the RMI server, which involves aggregating an instance of the AddressBook class in the RMI server, and delegating the findAddress call to the AddressBook instance. That's what the RMIAddressBookServer class does. Its main method instantiates the RMI server and binds it to the RMI registry.

Author's Note: Before starting the server you must start the RMI registry.

public class RMIAddressBookServer implements RMIAddressBook 
{
   private static final String SERVICE_NAME = "addressbook";
   private AddressBook book;
   
   public RMIAddressBookServer()
   {
      super();
      book = new AddressBook();
   }
   @Override
   public AddressBean findAddress(String name) 
      throws RemoteException 
   {
      return book.findFriend( name );
   }
   public static void main( String args[] )
   {
      if( System.getSecurityManager() == null ) 
      {
         System.setSecurityManager(
            new RMISecurityManager() );
      }
      try 
      {
         RMIAddressBookServer engine = new RMIAddressBookServer();
         RMIAddressBook stub = (RMIAddressBook) 
            UnicastRemoteObject.exportObject(engine, 0);
         Registry registry = LocateRegistry.getRegistry();
         registry.rebind( SERVICE_NAME, stub );
      } 
      catch (Exception e) 
      {
         e.printStackTrace();
      }
   }
}
Author's Note: When running the RMI server you might have to change your java.policy file to grant sufficient rights to the RMI server. You can find out how to do that in the RMI tutorial.

The client implementation is easy: All you need to do is look up the RMI service in the registry, get a reference to it, and then call it remotely and return the result immediately. Because the example uses the same RMI server throughout the entire client lifecycle, you can look it up in the constructor and store the remote handle. The code simply delegates the findFriend call to the remote RMI server:

public class RMIAddressBookClient 
   extends SimpleMeasurementImpl 
   implements AddressBookContract
{
   RMIAddressBook  book;
   public RMIAddressBookClient()
   {
      try
      {
         Registry registry = LocateRegistry.getRegistry();
         book = (RMIAddressBook) registry.lookup( 
            RMIAddressBookServer.SERVICE_NAME );
      }
      catch( Exception e )
      {
         book = null;
      }
   }
   
   @Override
   public AddressBean findFriend(String name) 
   {
      timeStart = System.nanoTime();
      try
      {
         return book.findAddress( name );
      }
      catch( Exception re )
      {
         re.printStackTrace();
         return null;
      }
      finally
      {
         timeEnd = System.nanoTime();
      }
   }
}

Starting the RMI server and running the client render the following on my PC:

class rmi.RMIAddressBookClient found 
   Liv : Oxford Street , London , SW1 in 3,789,190

The result required about 0.003 seconds to run the client—more than 100 times faster than the XML-based approach—154.1859 times faster, to be precise.

At this point you might be tempted to say that that's enough optimization (300 times faster than the web service approach sounds very good!) and opt for the RMI approach immediately. But is that the only alternative that is faster than XML? Consider another possibility, in which the server is TCP/IP-based and communicates with the client over sockets but they exchange Java objects directly rather than XML. This scheme eliminates building and parsing XML messages, and relies instead on Java's built-in serialization mechanisms—which might turn out to be faster.

Communicating via Java Objects

So based on the same pattern of spawning a thread each time a client connects to our server (as used in XMLSerAddressBookServer), this section implements a JavaSerAddressBookServer. The only differences lie in reading requests and writing responses; this approach uses ObjectInput and OutputStream to read and write the requests and responses. Because the server supports only one request type (findAddress), the client must send only the name to look up, which can be done by sending a serialized String object. The server returns an AddressBean instance (which if you recall was declared as Serializable), so all the server does to reply to a request is write the AddressBean to the socket using an ObjectOutputStream. Therefore, the server implementation will look like this:

public class JavaSerAddressBookServer 
{
   public static final int SERVER_PORT = 8899;
   private AddressBook book;
   private ServerSocket srvSock;
      
   public JavaSerAddressBookServer() throws IOException
   {
      book = new AddressBook();
      srvSock = new ServerSocket( SERVER_PORT );
   }
   
   public void startListening() 
   {
      Socket s = null;
      while( true )
      {
         try
         {
            s = srvSock.accept();
            Thread t = new Thread( new WorkerThread(s) );
            t.start();
         }
         catch( IOException e )
         {
            e.printStackTrace();
         }
      }
   }
   
   public static void main(String[] args) 
   {
      try
      {
         JavaSerAddressBookServer server = new 
            JavaSerAddressBookServer();
         server.startListening();
      }
      catch( IOException e )
      {
         e.printStackTrace();
      }
   }
   class WorkerThread implements Runnable
   {
      private Socket s;
      private String name;
      
      public WorkerThread( Socket s )
      {
         this.s = s;
      }
      
      public void run()
      {
         try
         {
            processRequest( s.getInputStream() );
            
            AddressBean a = book.findFriend( name );
            sendResponse( a, s.getOutputStream() );
         }
         catch( Exception e )
         {
            e.printStackTrace();
         }
         finally
         {
            try
            {
               s.close();
            }
            catch( IOException ioe )
            {
            }
         }
      }
      
      protected void processRequest( InputStream s ) 
         throws IOException, ClassNotFoundException
      {
         ObjectInputStream ois = new ObjectInputStream( s );
         name = (String)ois.readObject();
      }
      
      protected void sendResponse(AddressBean a, OutputStream s ) 
         throws IOException
      {
         ObjectOutputStream os = new ObjectOutputStream( s );
         os.writeObject( a );
      }
   }
   
}

The client is based on a pattern similar to the one used previously that sent XML messages over TCP/IP:

public class JavaSerAddressBookClient extends SimpleMeasurementImpl implements AddressBookContract
{
   @Override
   public AddressBean findFriend(String name) 
   {
      timeStart = System.nanoTime();
      Socket s = null;
      try
      {
         s = new Socket( "localhost", 
            JavaSerAddressBookServer.SERVER_PORT );
         sendRequest( name, s.getOutputStream() );
         AddressBean a = parseResponse( s.getInputStream() );
         return a;
      }
      catch( Exception e )
      {
         e.printStackTrace();
         return null;
      }
      finally
      {
         try
         {
            s.close();
         }
         catch(Exception e){}
         timeEnd = System.nanoTime();
      }
   }
   
   protected void sendRequest(String name, OutputStream o) 
      throws IOException
   {
      ObjectOutputStream os = new ObjectOutputStream( o );
      os.writeObject( name );
   }
   
   protected AddressBean parseResponse( InputStream s ) 
      throws IOException, ClassNotFoundException
   {
      ObjectInputStream os = new ObjectInputStream( s );
      return (AddressBean)os.readObject();
   }
}

Running the server and the client renders this output:

class javaser.JavaSerAddressBookClient found 
   Liv : Oxford Street , London , SW1 in 73,613,300

You may be surprised to find out that while this is definitely faster than the XML + TCP/IP solution (about 8 times faster), it is still considerably slower than the RMI-based solution. The reason lies in a combination of the fact that this solution must spend some time preparing the socket connection (the RMI example does that in the constructor, so everything is ready when it makes the call to the RMI server), and the way object streams deserialize objects received over the wire.

Nevertheless, note that the solution doesn't require writing any SAX parsing or XML-assembling code—and the JVM does input validation automatically. Ultimately, this solution involves writing less code and still provides a faster result.

Exchanging Text Messages

Last, but not least, here's a humanly readable TCP/IP-based protocol where the server and client exchange plain text messages. The client writes text characters to the server and gets back some characters that follow this format:

name : street , city , postcode.

You may notice that the preceding format is the same as the output of the AddressBean.toString method. In this example, when the server finds the AddressBean, it simply writes the characters resulting from the toString call back to the client. The client parses the string and re-assembles an AddressBean based on the data.

Using this simple approach, the server will look like this:

public class TextSerAddressBookServer 
{
   public static final int SERVER_PORT = 8899;
   private AddressBook book;
   private ServerSocket srvSock;
   
   
   public TextSerAddressBookServer() throws IOException
   {
      book = new AddressBook();
      srvSock = new ServerSocket( SERVER_PORT );
   }
   
   public void startListening() 
   {
      Socket s = null;
      while( true )
      {
         try
         {
            s = srvSock.accept();
            Thread t = new Thread( new WorkerThread(s) );
            t.start();
         }
         catch( IOException e )
         {
            e.printStackTrace();
         }
      }
   }
   
   public static void main(String[] args) 
   {
      try
      {
         TextSerAddressBookServer server = new 
            TextSerAddressBookServer();
         server.startListening();
      }
      catch( IOException e )
      {
         e.printStackTrace();
      }
   }
   class WorkerThread implements Runnable
   {
      private Socket s;
      private String name;
      
      public WorkerThread( Socket s )
      {
         this.s = s;
      }
      
      public void run()
      {
         try
         {
            processRequest( s.getInputStream() );
            
            AddressBean a = book.findFriend( name );
            sendResponse( a, s.getOutputStream() );
         }
         catch( Exception e )
         {
            e.printStackTrace();
         }
         finally
         {
            try
            {
               s.close();
            }
            catch( IOException ioe )
            {
            }
         }
      }
      
      protected void processRequest( InputStream s ) throws IOException
      {
         BufferedReader br = new BufferedReader(
            new InputStreamReader(s) );
         name = br.readLine();
      }
      
      protected void sendResponse(AddressBean a, OutputStream s) 
         throws IOException
      {
         PrintWriter pw = new PrintWriter( s );
         pw.println( a.toString() );
         pw.flush();
      }
   }
}

The client will have to parse the response from the server, as discussed before. The easiest way to do that is by using a regular expression like this:

(w+)s*:s*([0-9a-zA-Zs]+)s*,s*(w+)s*,s*(.*)

To do that, use the java.util.regex package, and precompile the regex in a static member in the client class:

private static final Pattern p = Pattern.compile(
   "(\w+)\s*\:\s*([0-9a-zA-Z\s]+)\s*\,\s*(\w+)\s*\,\s*(.*)" 
);

With that in place, when the client receives a reply from the server, it just matches and retrieves the four matched groups, which it then assembles back into an AddressBean. The TextSerAddressBookClient's parseResponse method looks like this:

protected AddressBean parseResponse( InputStream s ) 
   throws IOException
   {
      BufferedReader br = new BufferedReader( new InputStreamReader(s) );
      String line = br.readLine();
      Matcher m = p.matcher( line );
      if( m.matches() )
         return new AddressBean( m.group(1), 
            m.group(2), m.group(3), m.group(4) );
      else 
         return null;
   }

Running this implementation renders the following:

class text.TextSerAddressBookClient found 
   Liv : Oxford Street  , London , SW1 in 39,210,032

That is approximately about half the time taken by the approach using the in-build serialization mechanism, and you can imagine that the time saved is due to the fact that messages sent in between client and server are shorter and the structure of the messages is not that complex, therefore the parsing of these messages is much lighter. The time it takes is still significantly higher than the RMI version, but that is because we spend a lot of time in opening the socket to the server -- if you move the statement that intializes timeStart after the line that opens the socket:

   @Override
   public AddressBean findFriend(String name) 
   {
      Socket s = null;
      try
      {
         s = new Socket( "localhost", TextSerAddressBookServer.SERVER_PORT );
         timeStart = System.nanoTime();
         sendRequest( name, s.getOutputStream() );
         AddressBean a = parseResponse( s.getInputStream() );
         return a;
      }
      catch( Exception e )
      {
         e.printStackTrace();
         return null;
      }
      finally
      {
         try
         {
            s.close();
         }
         catch(Exception e){}
         timeEnd = System.nanoTime();
      }
   }

you will find out that the timing decreases dramatically:

class text.TextSerAddressBookClient found Liv : Oxford Street  , London , SW1 in 4,373,359

In other words it takes about 0.004 seconds -- compared to 0.003 in the case of RMI -- and that is due to the fact that we offloaded a lot more preparation work into the RMI client constructor.

Comparing All Solutions

As a recap, Table 1 shows the timings for all the solutions discussed here.

Table 1. Solutions Compared: The table shows the timings for all the solutions discussed in this article.
Technology Duration (in ns) Duration (in seconds)
Web services (SOAP) 1,257,869,940 1.2
XML + TCP/IP 584,239,804 0.5
RMI 3,789,190 0.003
Serialized objects 73,613,300 0.07
Text over TCP/IP 4,373,359 0.004

So while web services, SOAP, and XML certainly have advantages for interconnecting components or applications regardless of platform or programming language used, they are not even close to the fastest method for communicating. As the timings show clearly, when you are purely interconnecting Java components, you might want to think twice before choosing SOAP as a communication mechanism.

About the Author

Liviu Tudor is a Java consultant living in the UK who has worked extensively with high-availability systems, mostly in the online media sector. Recently, he's come to realize that for performance, it's the "low level" core Java that makes an application deliver rather than a bloated middleware framework. While healing from injuries acquired playing rugby with his London team The Phantoms (http://www.phantomsrfc.com), he writes articles on Java technology for developer.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date