Wednesday, May 4, 2011

Spring web services using JAX-WS

This blog illustrates how to configure and develop a JAX-WS server and client using spring framework.

Step 1
Download and Install JDK 1.6, Maven and your favorite web application container(tomcat,jboss,glassfish).

Step 2
Create a maven application with archetype "maven-archetype-webapp". Ask Google if you need help running this archetype.

Step 3
Add maven repository for JBoss and dependencies for junit, spring, jaxb, and jvnet's jaxws-spring. Since spring is already configured exclude spring from jvnet.

pom.xml
    .
    .
    <repositories>
        <repository>
            <id>jboss</id>
            <name>JBoss Maven Repository</name>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
        </repository>
    </repositories>

    <properties>
        <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
           <groupId>com.sun.xml.bind</groupId>
           <artifactId>jaxb-impl</artifactId>
           <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.jvnet.jax-ws-commons.spring</groupId>
            <artifactId>jaxws-spring</artifactId>
            <version>1.8</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    .
    .

Step 4
Develop the webservice class using JAX-WS annotations

CalculatorServiceImpl.java
@WebService
@SOAPBinding(style = Style.RPC, use = Use.LITERAL)
public class CalculatorServiceImpl implements CalculatorService {
    public double addNumbers(double a, double b) {
        return a + b;
    }

    public double subtractNumbers(double a, double b) {
        return a - b;
    }
}

Step 5
Configure spring context loader and jvnet's WSSpringServlet in web.xml.

web.xml
    .
    .
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>jaxws-servlet</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>jaxws-servlet</servlet-name>
    <url-pattern>/calculator</url-pattern>
</servlet-mapping>
    .
    .
Step 6   
Configure calculator service as a bean in spring's context. Next, add this service to the web URL that we configured earlier in web.xml.

applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:ws="http://jax-ws.dev.java.net/spring/core" xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://jax-ws.dev.java.net/spring/core  http://jax-ws.dev.java.net/spring/core.xsd
         http://jax-ws.dev.java.net/spring/servlet http://jax-ws.dev.java.net/spring/servlet.xsd">

    <bean id="calculatorService"
        class="com.ingeniouscamel.springjaxws.service.CalculatorServiceImpl" />

    <wss:binding url="/calculator">
        <wss:service>
            <ws:service bean="#calculatorService" />
        </wss:service>
    </wss:binding>
</beans>

Step 7
Run the application in your webapp container and test it using the following URL

http://localhost:<Port>/<WebAppContext>/calculator?WSDL



Step 8
In order to develop a client, first develop a webservice client interface. You can cheat this step by running wsimport command on the WSDL and using the generated source file.

CalculatorServiceImpl.java
@WebService(name = "CalculatorServiceImpl", targetNamespace = "http://service.springjaxws.ingeniouscamel.com/")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface CalculatorServiceImpl {
    @WebMethod
    @WebResult(partName = "return")
    public double addNumbers(
            @WebParam(name = "arg0", partName = "arg0") double arg0,
            @WebParam(name = "arg1", partName = "arg1") double arg1);

    @WebMethod
    @WebResult(partName = "return")
    public double subtractNumbers(
            @WebParam(name = "arg0", partName = "arg0") double arg0,
            @WebParam(name = "arg1", partName = "arg1") double arg1);
}

Step 9
Configure client's spring context to bind the above interface to the web service using spring's JaxWsPortProxyFactoryBean bean (you will find this file in src/main/resources folder of my project).

applicationClientContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"    
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="calculatorWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="com.ingeniouscamel.springjaxws.client.CalculatorServiceImpl"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8080/SpringJaxWSApp/calculator?WSDL"/>
        <property name="namespaceUri" value="http://service.springjaxws.ingeniouscamel.com/"/>
        <property name="serviceName" value="CalculatorServiceImplService"/>
        <property name="portName" value="CalculatorServiceImplPort"/>
    </bean>    
</beans>

Step 10
You can now load the above client's context (applicationClientContext.xml) and call methods on the web service client directly.

CalculatorWebServiceClientTest.java
public class CalculatorWebServiceClientTest {
    private CalculatorServiceImpl calculatorWebService;

    @Before
    public void setUp() throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationClientContext.xml");
        calculatorWebService = (CalculatorServiceImpl)context.getBean("calculatorWebService");
    }

    @Test
    public void testAddNumbers() {
        Assert.isTrue(calculatorWebService.addNumbers(3, 5) == 8);
    }

    @Test
    public void testSubtractNumbers() {
        Assert.isTrue(calculatorWebService.subtractNumbers(7, 4) == 3);
    }
}

Download Project
You can download my project from google code. SVN URL is http://ingenious-camel.googlecode.com/svn/trunk/SpringJaxWSApp/

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. How would one annotate the CalculatorServiceImpl interface if both the parameter and the return type was a complex type?

    ReplyDelete
  3. Hi - Please see the sample below. I have also checked in this sample to SVN.

    1. My Pojo's
    public class Dept {
    private int deptId;
    private String location;
    ....
    }


    public class Manager {
    private int empId;
    private String name;
    private int deptId;
    private String location;
    ....
    }

    2. Webservice class
    @WebService
    @SOAPBinding(style = Style.RPC, use = Use.LITERAL)
    public class DeptServiceImpl {
    public Manager getDeptManager(Dept dept) {
    // perform some logic to retrieve manager using dept
    return manager;
    }
    }

    3. Webservice client interface
    @WebService(name = "DeptServiceImpl", targetNamespace = "http://service.springjaxws.ingeniouscamel.com/")
    @SOAPBinding(style = SOAPBinding.Style.RPC)
    public interface DeptServiceImpl {

    @WebMethod
    @WebResult(partName = "return")
    public Manager getDeptManager(
    @WebParam(name = "arg0", partName = "arg0") Dept arg0);

    }

    4. Spring client config

    ReplyDelete
  4. Great article!!! Works perfectly

    ReplyDelete