Monday, February 7, 2011

Java Web Services Without Code Generation

With Java 6 it is possible to use annotations to create web services and to create clients to consume those web services without using 3rd party libraries like Axis. If you have control of both the server and the client code you can easily write these pieces without any code generation tools. Here is an example of that.

Server
To create your web service, perform the following steps.
  1. Create a directory for the project.  (mkdir server;  cd server)
  2. Create a package for your code.  (mkdir hello)
  3. Create the interface for the web service.  (hello/HelloPort.java)
      1 package hello; 
      2  
      3 import javax.jws.WebService; 
      4  
      5 @WebService 
      6 public interface HelloPort {
      7   String talk(String msg); 
      8 }
  4. Create the web service.  (hello/Hello.java)
      1 package hello; 
      2  
      3 import javax.jws.WebService; 
      4 import javax.xml.ws.Endpoint; 
      5  
      6 @WebService 
      7 public class Hello implements HelloPort {
      8   public String talk(String msg) { 
      9     return "Hello, " + msg; 
     10   } 
     11  
     12   public static void main(String[] argv) {
     13     Endpoint.publish("http://localhost:8080/Hello", new Hello()); 
     14   } 
     15 }
  5. Compile your code.  (javac hello/Hello.java)
  6. Run the server.  (java hello.Hello)
Now that your server is running, you can view the wsdl by going to http://localhost:8080/Hello?wsdl. If you wanted to, you could've skipped the step where you created the HelloPort interface (and had Hello not implement any interface), but we will use HelloPort in the client.

Client
To create a client that consumes this web service:
  1. Create a directory for the project.  (mkdir client;  cd client)
  2. Create a directory for the Hello interface.  (mkdir hello)
  3. Copy the interface.  (cp ../server/hello/HelloPort.class hello)
  4. Create the client code (Client.java)
      1 import java.net.URL; 
      2 import javax.xml.namespace.QName; 
      3 import javax.xml.ws.Service; 
      4 import hello.HelloPort; 
      5  
      6 public class Client {
      7  
      8   static class HelloService extends Service {
      9     public HelloService() throws Exception {
     10       super(new URL("http://localhost:8080/Hello?wsdl"), 
     11             new QName("http://hello/", "HelloService")); 
     12     } 
     13     public HelloPort getHelloPort() { 
     14       return super.getPort(new QName("http://hello/", "HelloPort"), 
     15                            HelloPort.class); 
     16     } 
     17   } 
     18    
     19   public static void main(String[] args) throws Exception {    
     20     HelloService hs = new HelloService(); 
     21     HelloPort port = hs.getHelloPort(); 
     22     System.out.printf("ws returns [%s]%n", port.talk(args[0])); 
     23   } 
     24 }
  5. Compile the client  (javac Client.java)
  6. Run the program  (java Client <message>)  or
    java -Djava.util.logging.config.file=log.prop Client <message>
And just like that you have written both a client and a server which talk to each other via SOAP. The secondary run line (with the -Djava.util... option) is there in case you don't want to see the logging messages that get printed by default when you run the client.

The URL specified on line 10 is the same URL that you specified on line 13 of the server up above, except with a "?wsdl" parameter. The QName defined on line 11 comes directly from the WSDL. If you look at the wsdl, http://localhost:8080/Hello?wsdl, it'll look something like:
  1 <?xml version="1.0" encoding="UTF-8"?> 
  2 <!--  
  3   Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.6 in JDK 6. 
  4 --> 
  5 <!--  
  6   Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.6 in JDK 6. 
  7 --> 
  8 <definitions  
  9   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
 10   xmlns:tns="http://hello/" 
 11   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 12   xmlns="http://schemas.xmlsoap.org/wsdl/" 
 13   targetNamespace="http://hello/" 
 14   name="HelloService"> 
 15   <!-- service definition here --> 
 16 </definitions
The targetNamespace and the name (lines 13 and 14 above) correspond to the QName used in the client.

Final Thoughts
This example was for a very specific case where you control both the client and server code and want it to be standalone. Of course, your web service needs may not match this model. If you already have a web container (Tomcat, etc), you can always deploy the Hello web service in that, rather than running it as a standalone application. And, of course, you could write your client in any language, as long as it conforms to the WSDL. Or you can just give customers the WSDL and let them write their own client.

If you have to write a client and don't have handy interface classes (hello.HelloPort in the above example), you can do that as well. You'll just have to generate classes using the wsimport command which comes with Java, but that is beyond the scope of this particular blog post.

No comments: