Monday, June 27, 2011

Spring Web Service using JAX-WS

Hey ...

Nowadays, most of enterprise applications are linked together to do their jobs. Some of them expose some services, the others use the services which are exposed. Here, the role of web service appears. Here I'm going to explain more about spring-ws using jax-ws and take a simple example.

OK. let's start

1. Spring Web Service

Spring provides full support for standard Java web services APIs:
  • Exposing web services using JAX-RPC
  • Accessing web services using JAX-RPC
  • Exposing web services using JAX-WS
  • Accessing web services using JAX-WS

Why two standard Java web services APIs?
JAX-RPC 1.1 is the standard web service API in J2EE 1.4. As its name indicates, it focuses on on RPC bindings, which became less and less popular in the past couple of years. As a consequence, it has been superseded by JAX-WS 2.0 in Java EE 5, being more flexible in terms of bindings but also being heavily annotation-based. JAX-WS 2.1 is also included in Java 6 (or more specifically, in Sun's JDK 1.6.0_04 and above; previous Sun JDK 1.6.0 releases included JAX-WS 2.0), integrated with the JDK's built-in HTTP server.

Spring can work with both standard Java web services APIs. The choice is effectively dependent on the runtime platform: On JDK 1.4 / J2EE 1.4, the only option is JAX-RPC. On Java EE 5 / Java 6, the obvious choice is JAX-WS. On J2EE 1.4 environments that run on Java 5, you might have the option to plug in a JAX-WS provider; check your J2EE server's documentation.

In addition to stock support for JAX-RPC and JAX-WS in Spring Core, the Spring portfolio also features Spring Web Services, a solution for contract-first, document-driven web services - highly recommended for building modern, future-proof web services. Last but not least, XFire also allows you to export Spring-managed beans as a web service, through built-in Spring support.

As I mentioned, here I'm going to describe more about jax-ws. Here, more details are available about remoting and web services using Spring.

When your application uses Spring for wiring your code, you'll find it more convenient to use the same Spring for configuring JAX-WS. For example, that would allow your service implementations to receive resource injection via Spring, AOP stuff, logging, etc, etc. This RI extension provides this functionality.


2. JAX-WS

This Spring extension also allows you to configure various aspects of your web service through Spring, such as the use of MTOM, handlers, custom transports, encoding, etc., etc.

Another purpose of this extension is to provide a general purpose mechanism that JAX-WS extension authors can use, to let users use their extensions. For example, when you write a custom transport, custom codec, or custom pipeline assembler, you can use Spring to allow users to configure your extensions.

This deployment mechanism supercedes sun-jaxws.xml and JSR-109 deployment.

3. Server Side

Let's consider the most typical case, where you develop a web application and you want to deploy JAX-WS services. First your web.xml needs to have a JAX-WS servlet registered (if servlet could be deployed from Spring without web.xml , we could have gotten rid of this altogether!)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-ws.xml</param-value>
    </context-param>


    <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>*.ws</url-pattern>
    </servlet-mapping>
</web-app>
I used WSSpringServlet class as major web service servlet. Then I mapped any *.ws to this servlet.

spring-ws.xml is spring context configuration also containing web service configuration.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:ws="http://jax-ws.dev.java.net/spring/core"
             xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
             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://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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.ws.service"/>

    <wss:binding url="/hello.ws">
        <wss:service>
            <ws:service bean="#helloService"></ws:service>
        </wss:service>
    </wss:binding>

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

</beans:beans>
The two wss:binding definitions define what services are exposed to which part of the URL space. In the above code, it deploys two services on two URLs(hello, calculator). In "ws:service" tag we should map the specific url to its spring bean. We should determine it on this way: '#'+'bean id'. I have two spring components in spring context including: helloService and calculatorService. component-scan tag would find them.


helloService
package com.ws.service;

import org.springframework.stereotype.Component;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

@Component("helloService")
@WebService(serviceName = "Hello",
        portName = "HelloPort",
        targetNamespace = "http://service.ws.com/")
@SOAPBinding(style = Style.RPC, use = Use.LITERAL)
public class HelloService{

    @WebMethod
    public String sayHello(@WebParam(name = "name") String name) {
        return "Hello " + name;
    }

}

calculatorService
package com.ws.service;

import org.springframework.stereotype.Component;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;

@Component("calculatorService")
@WebService(serviceName = "Calculator",
        portName = "CalculatorPort",
        targetNamespace = "http://service.ws.com/")
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
public class CalculatorService {
    @WebMethod
    public Float add(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2) {
        return input1 + input2;
    }
    @WebMethod
    public Float subtract(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2) {
        return input1 - input2;
    }
    @WebMethod
    public Float multiply(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2) {
        return input1 * input2;
    }
    @WebMethod
    public Float devide(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2) {
        return input1 / input2;
    }
}

We should determine the serviceName, portName, targetNamespace in WebService annotation to use them in client side code.

Now two web services are ready to use. When you run your server side application, you can find these two web services here:
  • http://localhost:8080/hello.ws
  • http://localhost:8080/calculator.ws

and their WSDL are available here:

  • http://localhost:8080/hello.ws?wsdl
  • http://localhost:8080/calculator.ws?wsdl
4. Client Side
In client side we should define these two beans(hello, calculator) in spring context. But there are no implementation of them. We should bind them via SOAPBinding to the client interface of them. So first we should create these two interfaces:
HelloService
package com.ws.service;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
public interface HelloService {
    public String sayHello(@WebParam(name = "name") String name);
}
CalculatorService
package com.ws.service;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)
public interface CalculatorService {
    @WebMethod
    Float add(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2);

    @WebMethod
    Float subtract(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2);

    @WebMethod
    Float multiply(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2);

    @WebMethod
    Float devide(@WebParam(name = "input1") Float input1, @WebParam(name = "input2") Float input2);
}

Then in spring context configuration we should define these two bean on this way:
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.calculator"/>

    <!--Tiles 2-->
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/layout/tiles-config.xml</value>
            </list>
        </property>
    </bean>

    <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
        <property name="order" value="1"/>
    </bean>

    <!--webservice-->
    <bean id="helloService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="com.ws.service.HelloService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8080/hello.ws?WSDL"/>
        <property name="namespaceUri" value="http://service.ws.com/"/>
        <property name="serviceName" value="Hello"/>
        <property name="portName" value="HelloPort"/>
    </bean>

    <bean id="calculatorService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="com.ws.service.CalculatorService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8080/calculator.ws?WSDL"/>
        <property name="namespaceUri" value="http://service.ws.com/"/>
        <property name="serviceName" value="Calculator"/>
        <property name="portName" value="CalculatorPort"/>
    </bean>

</beans>

As you see, we use JaxWsPortProxyFactoryBean. This class will be an instance of the interface which you define in its "serviceInterface" property. "wsdlDocumentUrl", "namespaceUri", "serviceName" and "portName" properties are the values which you determined them in server side. You can also refer to the urls which these web services are exposed.

From now you have two new beans called: "helloService" and "calculatorService". You can autowrie them in your controllers and use them like 'CalculatorController' class
package com.calculator;

import org.springframework.stereotype.Controller;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import com.base.BaseController;
import com.ws.service.CalculatorService;

/**
 * User: mostafa
 * Date: Jun 27, 2011
 * Time: 9:19:40 AM
 */
@Controller
@Scope("prototype")
@RequestMapping("/calculator")
public class CalculatorController extends BaseController {
    @Autowired
    CalculatorService calculatorService;

    Float input1;
    Float input2;
    Float result;

    public Float getInput1() {
        return input1;
    }

    public void setInput1(Float input1) {
        this.input1 = input1;
    }

    public Float getInput2() {
        return input2;
    }

    public void setInput2(Float input2) {
        this.input2 = input2;
    }

    public Float getResult() {
        return result;
    }

    public void setResult(Float result) {
        this.result = result;
    }

    public void init() {
    }

    @RequestMapping("/view")
    public String view() {
        return "calculator.view";
    }

    @RequestMapping("/add")
    public String add() {
        result = calculatorService.add(input1, input2);
        return view();
    }

    @RequestMapping("/subtract")
    public String subtract() {
        result = calculatorService.subtract(input1, input2);
        return view();
    }

    @RequestMapping("/multiply")
    public String multiply() {
        result = calculatorService.multiply(input1, input2);
        return view();
    }

    @RequestMapping("/devide")
    public String devide() {
        result = calculatorService.devide(input1, input2);
        return view();
    }

    @RequestMapping("/clear")
    public String clear() {
        return "redirect:view.html";
    }
}

6. Source Code
Here you can download the source code. When you extract zipped file, you will find two projects. You can run server side project (springWS) on port 8080 and client side project(springWSClient) on port 9090 for example. So the urls will be:
  • Server: http://localhost:8080/calculator.ws
  • Client: http://localhost:9090/calculator/view.html
enjoy yourselves
all rights reserved by Mostafa Rastgar and Programmer Assistant weblog