Social Icons

Wednesday 30 April 2014

Creating Java Web Services Using JAX-WS, JAXB and Spring

I've recently been working a lot with Java web services, most of these were greenfield projects where we were able to choose the architecture. I decided to use JAX-WS to create the web services but was unsure initially of the best way to go about this. In general there are two approaches to writing web services (contract first or code first).

Contract first
Contract first requires a wsdl to be written first and then JAX-WS can be used to generate matching code. This approach has its place if you already have a wsdl but they're not the easiest of things to work with and maintenance quickly becomes messy.

Code first
Code first is much easier as you simply write the code using a handful of basic annotations and let JAX-WS generate the wsdl for you at run time. The downside to this approach is that you have to write the code before you have a wsdl or schema available. This may not be an issue but if you need to write a specification first then it's handy to have some kind of schema to define how the inputs and outputs are going to look.

Also, in some cases you may be able to express more in a schema than you can with just Java code. For instance you might want to set a restriction in the schema (like a string max length), or perhaps you want a particular nested structure of elements. You could do this yourself with JAXB annotations but it's easier to write a schema and generate the required classes.

Combined approach using JAXB
I asked this as a question on stackoverflow and one of the answers provided inspiration for a kind of best of both worlds approach. This has now been implemented for several different projects and overall it's been a pleasure to work with. The basic idea is that development follow something like the following process.
  • Write a basic schema that defines the request and response types (this can be included in specifications and is easier to maintain than a full wsdl)
  • Use JAXB/XJC to generate the request and response types
  • Write a JAX-WS endpoint using the generated types as inputs and outputs
  • Let JAX-WS generate the full wsdl at runtime

Spring integration
In addition to this I wanted to use spring for managing services, dependency injection and loading properties files. This requires setting up JAX-WS slightly differently so that spring can load the endpoints and inject dependencies. If you don't do this then you'll end up with a JAX-WS version of the endpoint with none of its dependencies injected while spring will have its own instance complete with injected dependencies but not handling any requests.

Maven build and dependencies
I'm using maven to manage the build and dependencies. You don't have to use maven but it really does make life much easier. These are the required dependencies you'll need in your pom.xml file.

<dependency>
         <groupId>com.sun.xml.ws</groupId>
         <artifactId>jaxws-rt</artifactId>
         <version>2.2.7</version>
       </dependency>

       <!-- Spring DI -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
            <exclusions>
            <exclusion>
             <groupId>commons-logging</groupId>
             <artifactId>commons-logging</artifactId>
            </exclusion>
         </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-version}</version>
        </dependency>

        <!-- JAX-WS/Spring integration -->
       <dependency>
   <groupId>org.jvnet.jax-ws-commons.spring</groupId>
   <artifactId>jaxws-spring</artifactId>
   <version>1.8</version>
            <exclusions>
            <exclusion>
             <groupId>org.springframework</groupId>
             <artifactId>spring</artifactId>
            </exclusion>
         </exclusions>
  </dependency>

In the build section of the pom you'll also need to configure the JAXB plugin to auto-generate code from your schemas. The way it's setup here it will look for any schema in the directory /src/main/resources/xsd and then generate code and put it in a source folder called /target/generated-sources/src/main/java. If you're using eclipse you'll want to right click this folder and take the option Build path &gt; Use as source folder. If you don't do this you'll still be able to run a build using maven but you'll probably see compile errors in eclipse.

<build>
        <plugins>
            <!-- Generate JAXB Java source files from an XSD file -->
            <plugin>
             <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>xjc</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <!-- Don't set package name here as we want different packages for each schema.
                         Instead we set in each schema separately. -->
                    <!-- <packageName></packageName>  -->
                    <outputDirectory>${basedir}/target/generated-sources/src/main/java</outputDirectory>
                    <schemaDirectory>${basedir}/src/main/resources/xsd</schemaDirectory>
                </configuration>
            </plugin>
        </plugins>
  </build>

Web app config files
Next we need to add some configuration files to setup our web application. First is the web.xml deployment descriptor. Here we define the standard spring listener to load our spring configuration and we also setup a servlet to listen for our web service requests. Rather than use the JAX-WS servlet we've used a spring wrapper which will later allow us to use dependency injection in our endpoint classes.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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">

  <display-name>TestServices</display-name>

  <!-- Load spring configuration -->
  <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/application-context.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- Servlet to handle all jax-ws requests -->
  <servlet>
    <servlet-name>jaxws-servlet</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>jaxws-servlet</servlet-name>
    <url-pattern>/service/*</url-pattern>
  </servlet-mapping>

  <!-- There didn't ought to be any sessions created but it's good practice to define a
  timeout as it varies for different containers. -->
  <session-config>
    <session-timeout>40</session-timeout>
  </session-config>

</web-app>

The spring config is minimal in this basic example. It's just turning on classpath scanning for the package in our test project and enabling auto-wiring of scanned dependencies. We also need to setup which urls map to which endpoint classes but this has been moved to a separate file which we import at the bottom.

<?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-3.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.2.xsd
       " >

    <context:component-scan base-package="com.testservices"/>
    <context:annotation-config/>

  <!-- Define the jaxws endpoint -->
  <import resource="jaxws-context.xml"/>

</beans>

Usually with JAX-WS you need a config file called sun-jaxws.xml where you define which urls map to which endpoints. In this case we're using a spring JAX-WS servlet so instead we add our mappings here. This simple mapping is saying all web service requests to /service/test1 will go to the spring bean with an ID of test1Services. We shall see this bean shortly.

<?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: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.2.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">  

  <!-- Define our jaxws endpoint (replaces sun-jaxws.xml) -->
    <wss:binding url="/service/test1">
        <wss:service>
            <ws:service bean="#test1Services" />
        </wss:service>
    </wss:binding>   

</beans>

Schema to define request/response types
In this simple example we could get away with one simple schema but in a real world example you'll probably end up with many. The way I've been organizing this is to have a base shared schema which defines common types. For example you might want all requests to include a username, password and environment and maybe the response should always have a boolean element to indicate success. Then you can write a schema for each endpoint defining the request and response types for all operations on that wsdl.

Here is the example base schema shared.xsd.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema  xmlns:xs="http://www.w3.org/2001/XMLSchema"
   xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
   jaxb:version="2.0"
   targetNamespace="http://shared.testservices.com/"
   xmlns:tns="http://shared.testservices.com/"
   elementFormDefault="qualified">

  <!-- Settings for the JAXB code generation -->
  <xs:annotation>
    <xs:appinfo>
      <!-- Set the package name for the generated classes -->
      <jaxb:schemaBindings>
        <jaxb:package name="com.testservices.generated.shared" />
      </jaxb:schemaBindings>
    </xs:appinfo>
  </xs:annotation>   

  <!-- Begin Types/Classes to be generated -->
  <xs:group name="baseRequest">
    <xs:sequence>
      <xs:element name="user" type="xs:string"/>
      <xs:element name="apikey" type="xs:string"/>
    </xs:sequence>
  </xs:group>    

  <xs:group name="baseResponse">
    <xs:sequence>
      <xs:element name="success" type="xs:boolean"/>
    </xs:sequence>
  </xs:group>   

</xs:schema>

This schema then imports the shared types and defines a simple request and response type for our example web service. Note that we can define which package the generated code belongs to by using the jaxb namespace extensions. There are a number of other customizations you can do like mapping xml types to Java types.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema  xmlns:xs="http://www.w3.org/2001/XMLSchema"
   xmlns:sh="http://shared.testservices.com/"
   xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
   jaxb:version="2.0"
   targetNamespace="http://test1.testservices.com/"
   xmlns:tns="http://test1.testservices.com/"
   elementFormDefault="qualified">

  <!-- Settings for the JAXB code generation -->
  <xs:annotation>
    <xs:appinfo>
      <!-- Set the package name for the generated classes -->
      <jaxb:schemaBindings>
        <jaxb:package name="com.testservices.generated.test1" />
      </jaxb:schemaBindings>
    </xs:appinfo>
  </xs:annotation>

  <xs:import namespace="http://shared.testservices.com/" schemaLocation="shared.xsd" />

  <xs:complexType name="test1Request">
    <xs:sequence>
      <xs:group ref="sh:baseRequest"/>
      <xs:element name="id" type="xs:int" />
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="test1Response">
    <xs:sequence>
      <xs:group ref="sh:baseResponse"/>
      <xs:element name="field1" type="xs:string" />
      <xs:element name="field2" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

</xs:schema>

Finally add the endpoint interface/class
Having saved those schemas there should now be a generated class called Test1Request and another called Test1Response. We're now ready to start piecing things together. The final stage is to define the JAX-WS handler and add a web method that uses these classes as inputs and outputs.

@WebService
public interface Test1
{
 Test1Response test1(Test1Request request);
}

So we have a very simple interface which we annotate with the JAX-WS @WebService annotation. There is one method which uses our generated classes. When this code is deployed JAX-WS will do the legwork and generate the WSDL with this one method on it. We just need to implement this interface now.

@Component("test1Services")
@WebService(endpointInterface = "com.testservices.endpoint.Test1")
public class Test1Impl implements Test1
{

 ObjectFactory fact = new ObjectFactory();

 @Override
 public Test1Response test1(Test1Request request)
 {
  System.out.println("User: " + request.getUser());
  System.out.println("ID: " + request.getId());

  Test1Response response = fact.createTest1Response();

  response.setSuccess(true);
  response.setField1("Value 1");
  response.setField2("Value 2");

  return response;
 }

}

The implementation uses the JAXB object factory, which was generated for us, to create a response object. We then hardcode some values just to see if it works. In practice you'll be able to inject a spring service here and go off and do whatever business logic is required. The class has been annotated as a spring component called test1Services. It's important that this matches the bean name given in the spring config file for JAX-WS. The @WebService annotation names the interface that we've implemented.

You should now be able to fire up your test server and see this running. If you go to http://localhost:PORT/ProjectName/service/test1 you should see a page confirming the web service has been deployed with a link to the wsdl (just append ?wsdl).

Ben Thurley,
Senior Software Engineer at CoralTree Systems Ltd 

You can view Ben's wordpress blog, with more articles, here

Ben has worked for CoralTree for over 5 years. In that time, his expertise and knowledge in new coding techniques and methods have helped us develop even better solutions for our customers.

The views expressed in this article are not necessarily the views of CoralTree Systems Ltd.

0 comments :

Post a Comment