Wednesday, September 17, 2008

Building JAX-WS web service

Last year I wrote a small step by step guide to build JAX-RPC web services. Now JAX-RPC has been replaced by new standard JAX-WS, so I thought it is good time to write an entry for JAX-WS as well. Building web services with JAX-WS is pretty straight forward though it might look cumbersome to a newbie. In this entry I am going explain the basic steps for building a Java first web service, which I developed and tested with JAX-WS 2.1.4, Java 6 update 6 and Tomcat 6.0.16.

Step #1 - Write an interface

@WebService(targetNamespace = "http://vinodsingh.com", name = "MyService")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL)
public interface MyService {

    String sayHello(@WebParam(name = "name") String name);
}

Step #2 - Implement the interface
@WebService(endpointInterface = "pkg.MyService")
public class MyServiceImpl implements MyService {

    @Override
    public String sayHello(String name) {
        return "Hello " + name + "!";
    }
}

Step #3 - Configure web.xml

The JAX-WS context listener and servlet are required to be configured in deployment descriptor (web.xml). The WSServletContextListener initializes and configures the web service endpoint and WSServlet serves the service requests using implementing class.
<listener>
    <listener-class>
        com.sun.xml.ws.transport.http.servlet.WSServletContextListener
    </listener-class>
</listener>
<servlet>
    <servlet-name>jaxws</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>jaxws</servlet-name>
    <url-pattern>/myService</url-pattern>
</servlet-mapping>

Step #4 - sun-jaxws.xml

The JAX-WS RI uses information available in this file while initializing and configuring a web service endpoint. This file should be present in WEB-INF directory.
<endpoints
    xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'
    version='2.0'>
    <endpoint
        name='myService'
        implementation='pkg.MyServiceImpl'
        url-pattern='/myService' />
</endpoints>

Step #5 - Build and deploy

Here is an Ant script to prepare the server side stuff-
<!-- Define classpath. -->
<path id="jaxws.classpath">
    <pathelement location="${java.home}/../lib/tools.jar" />
    <fileset dir="${jaxws.lib.dir}">
        <include name="*.jar" />
    </fileset>
</path>

<!-- Declare ant tasks required to generate service and client. -->
<taskdef name="apt" classname="com.sun.tools.ws.ant.Apt" classpathref="jaxws.classpath" />
<taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen" classpathref="jaxws.classpath" />
<taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport" classpathref="jaxws.classpath" />

<!-- Run the 'apt' tool on annotated code to generate server side stuff. -->
<target name="buildService">
    <apt fork="true" destdir="${classes.dir}" 
         sourcepath="${src.main.dir}" 
         sourcedestdir="${gensrc.dir}" 
         debug="${debug}" verbose="${verbose}" classpathref="jaxws.classpath">
        <source dir="${src.main.dir}">
            <include name="**/MyServiceImpl.java" />
        </source>
    </apt>

    <jar destfile="${build.home}/server.jar" basedir="${classes.dir}" compress="true" />
</target>

Now package all this in a WAR file and deploy on the server. The web service can be accessed at http://localhost:8080/testws/myService (URL may change according to the server configuration).

Update Feb 13, 2009: With latest versions of Java 6 and JAX-WS, apt task is optional. See this comment below.

Step #6 - Generate client

And this script will generate client side stubs. Here first we are generating WSDL and then using that to generate client side stubs. Though we can avoid the WSDL generation and use the live URL by deploying the web service on a server but usually that is not feasible in a project where build process is automated.
<!-- Generate the client, for that first WSDL needs to be generated. -->
<target name="genClient" depends="buildServer">
    <wsgen sei="pkg.MyServiceImpl" 
        resourcedestdir="${gen.wsdl.dir}" 
        sourcedestdir="${gensrc.dir}/client" 
        destdir="${classes.dir}/client" 
        genwsdl="true" verbose="${verbose}" keep="true">
        <classpath>
            <pathelement location="${classes.dir}/server" />
        </classpath>
    </wsgen>

    <wsimport destdir="${classes.dir}/client" 
        sourcedestdir="${gensrc.dir}/client" 
        package="pkg" debug="${debug}" 
        wsdl="${gen.wsdl.dir}\MyServiceImplService.wsdl" 
        xendorsed="true" keep="true" />

    <jar jarfile="${build.home}/ws-client.jar" basedir="${classes.dir}/client" compress="true" />
</target>

Step #7 - Invoke the service

Here is the sample code to invoke the web service, which we just built and deployed.
// Get a handle to web service client interface
WebServiceClient ann = MyServiceImplService.class.getAnnotation(WebServiceClient.class);
MyServiceImplService service = new MyServiceImplService(new URL(
    "http://localhost:8080/testws/myService"), 
    new QName(ann.targetNamespace(), ann.name()));
MyService myService = service.getMyServiceImplPort();

// Invoke methods
String hello = myService.sayHello("Vinod");
System.out.println(hello);

Thats it. We are done. Source code of this example is available here.

Update Apr 12, 2010 - A new post JAX-WS Web service with Maven shows usage of Maven as build script for building JAX-WS web services.

73 Comments: