Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Clean up your wire protocol with SOAP, Part 4

Dynamic proxies make Apache SOAP client development easy

  • Print
  • Feedback

Page 2 of 4

Let's define an interface for our HelloWorld service. The final version of this service in Part 2 had two overloaded versions of the method sayHelloTo(): one version took a Java string, and the other took a Name JavaBean. This is what one possible interface, called Hello, looks like:

package hello;
public interface Hello
{
    public String sayHelloTo(String name);
    public String sayHelloTo(Name name);
}


The service developer decides how many interfaces to create -- and what to call them. For example, you could create two interfaces for the HelloWorld service, with each interface containing one method. In general, you should avoid creating interfaces with more than seven methods. Also consider lumping together only those methods that make sense together. For example, if the HelloWorld service also had a sayByeTo() method that returned a customized good-bye message to the caller, it would make sense to have two separate interfaces: one for the sayHelloTo() methods and one for the sayByeTo() methods.

Now that we have the interface that defines the contract between the HelloWorld service and the client, let's return our attention to the newInstance() method. As I mentioned above, the newInstance() method creates a new instance of the Proxy class. The method can do that because it is part of Proxy and can access the private constructor. newInstance() then calls the initialize() method on the newly created instance. The initialize() method is interesting because the dynamic proxy is created and returned there. initialize() is shown below:

private Object initialize(Class[] interfaces) 
{
             
return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()
,interfaces,this));
}            


Notice the use of the newProxyInstance() method. The only way to create an instance of a dynamic proxy class is to call the static newProxyInstance() method on it -- that is, the java.lang.reflect.Proxy class. The java.lang.reflect.Proxy class provides static methods for creating dynamic proxy classes, and it is also the superclass of all dynamic proxy classes created by those methods. In other words, it's not only a factory for creating dynamic proxy classes; it is a dynamic proxy class itself! So, in our example, the SOAP proxy is not the dynamic proxy; rather, the dynamic proxy is actually an instance of the java.lang.reflect.Proxy class returned by the newProxyInstance static method. As you will see later, the dynamic proxy will actually do all its work through the invoke() method that the SOAP proxy implements. So, how does the dynamic proxy know about the SOAP proxy? Because a reference to the SOAP proxy was passed into the newProxyInstance() method. This may sound convoluted now, but it should all become clear when you examine the invoke() method.

The class java.lang.reflect.Proxy takes a class loader instance as its first parameter; an interface array to dynamically implement (which is the same array that the client passed into newInstance()) as its second parameter; and an instance of a class that implements the java.lang.reflect.InvocationHandler interface as its third parameter. Since the SOAP Proxy class implements the InvocationHandler interface, the third parameter is the proxy instance itself (i.e., this). The InvocationHandler interface has one method: invoke(). The Java runtime calls invoke() when any method on one of the dynamic proxy's dynamically implemented interfaces is called. So, for example, when the client calls the sayHelloTo() method on the dynamic proxy's Hello interface, the Java runtime will call invoke() on the SOAP proxy.

You may have observed that the newInstance() method on the SOAP proxy does not return an instance of the SOAP proxy. Instead, it returns the dynamic proxy that newInstance() just created, which dynamically implements the interface array that the client passed in. The client can type-cast this returned dynamic proxy into any of the interfaces passed into newInstance() and invoke methods on the dynamic proxy as if the dynamic proxy actually implemented the interface. The relevant portion of the client is shown below for convenience:

.
.
.
try
{
    Class[] interfaces = new Class[] {hello.Hello.class};
    Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
    // Invoke the sayHelloTo method that takes a String name.
    System.out.println(hello.sayHelloTo("John"));
    // Invoke the sayHelloTo method that takes a Name 
JavaBean.          
    Name theName = new Name();
    theName.setName("Mala");
    System.out.println(hello.sayHelloTo(theName));
}
.
.
.


Now, let's take a look at the invoke() method, which, according to the above code snippet, will be invoked twice, once for each call to the sayHelloTo() method. In a nutshell, the invoke() method does exactly what each client had to manually do in the examples in Part 2. That includes setting up a Call object with the proper call parameters and any custom mapping that those parameters might require. Since the invoke() method in the SOAP proxy handles all that, the client is released of that burden.

Of the three parameters that the invoke() method receives, only the last two are of interest to us. The second parameter, the Method object, gives the method name that is being invoked. Remember that the invoked method name corresponds to a well-known method that the service exposes. The invoke() method already has the service's object ID, which was passed in as a parameter to the newInstance() method. Using that information, the invoke() method starts setting up the Call object as follows:

Call call = new Call();         
call.setTargetObjectURI(urn);
call.setMethodName(m.getName());
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);


Now it's time to set the parameters for the remote service invocation. For that, we are interested in the third parameter of the invoke() method: an argument array passed into the method invoked on the dynamic proxy. The zeroth index is the leftmost argument, the first index is the second from the left, and so on. As an example, if the client called the sayHelloTo(String name) method, then that array would be an array of one string. The invoke() method processes each element of that array and creates a vector of Parameter objects (just like the client did in Part 2):

java.util.Vector params = new java.util.Vector();       
for( int i=0; i<args.length; i++ )
{
             if( isSimple(args[i]) || isSimpleArray(args[i]) )
             {
                         params.add(new 
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));              
 
             }
             else if( isVector(args[i]) )
             {
                         
addMapping((java.util.Vector)args[i]);            
                         params.add(new 
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));              
 
             }
             // if this is an non-simple array then
             // Assume that this is an array of beans
             else if( isArray(args[i]) )
             {            
                         if( smr == null )
                                    smr = new 
SOAPMappingRegistry();
                         if( beanSer == null )
                                     beanSer = new 
BeanSerializer();
                         //System.out.println("Adding a default 
mapping");
                         ArraySerializer arraySer = new 
ArraySerializer();
                         smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
null, null, beanSer, beanSer);             
                          
smr.mapTypes(Constants.NS_URI_SOAP_ENC,null,args[i].getClass(), arraySer, 
arraySer);                                        
params.add(new 
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));              
 
             }
             // Assume that this is a bean
             else
             {
                         if( smr == null )
                                    smr = new 
SOAPMappingRegistry();
                         if( beanSer == null )
                                     beanSer = new 
BeanSerializer();
                         String qnamePart = 
args[i].getClass().getName();
                         //System.out.println("qnamePart = " + 
qnamePart);
                         smr.mapTypes(Constants.NS_URI_SOAP_ENC,
                                      new QName(urn, 
qnamePart),args[i].getClass(), beanSer, 
beanSer);                                   
                         params.add(new 
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));              
 
             }
}


invoke() uses a number of private helper methods, such as isSimple(), to determine the type of parameter. If the parameter is a JavaBean or an array, then a custom SOAP mapping registry must be set up and set in the Call object via the setSOAPMappingRegistry() method (see Part 2). The SOAP proxy assumes that, in the JavaBean case, any JavaBean used by the SOAP service maps as follows: the NameSpace UR field is set to the object ID and the Local Part is set to the JavaBean's fully qualified class name. Since this is exactly how we've deployed the HelloWorld service, we're fine.

  • Print
  • Feedback

Resources
  • Sign up for the JavaWorld This Week free weekly email newsletter to find out what's new on JavaWorld: http://www.idg.net/jw-subscribe