Java Quick Start – JAX-WS code-first web services with CXF and Spring

In this quick start we work through creating and deploying a minimal JAX-WS code-first web service to a Java webapp container using Apache CXF and Spring.

I’ve got a very, very short memory – something of a handicap for a generalist code-monkey. Distracted from a set of technologies for a few hours it seems I have to sprint to the Google Cave to get my head back into them again.

If you’re like me (or are me) and need to get your head back into creating a JAX-WS code-first web service deployed with Spring and CXF, then the following might help you get going. It’s the bare minimum (well, ish) you need to get a web-service working with these tools.

Step One: Assemble your ingredients

We’re using the following for this quick start example:-

This example works fine with older versions (e.g. Java 6, CXF 2.7 and Spring 3) with minor tweaks to the XSD paths in beans.xml and web.xml.

Our Maven pom.xml brings in the minimum bits of Spring and CXF to meet our needs. For CXF we need both the JAX-WS front-end and the HTTP transport. For Spring we need the beans component to support the beans we’ll define, plus the web and context components to support deployment via a web application.

If you’re not using Maven you can just download the full packages for CXF and Spring and drop the JAR files into your build. If you are using Maven, the pom.xml for this example is as follows:-

<project xmlns="http://maven.apache.org/POM/4.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="
                  http://maven.apache.org/POM/4.0.0
                  http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.devguerrilla.quickstart.cxf</groupId>
    <artifactId>country-lookup-service</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>CountryLookupService</name>

    <properties>
        <spring.version>4.0.6.RELEASE</spring.version>
        <cxf.version>3.0.1</cxf.version>
        <java.version>1.8</java.version>
        <servlet.version>3.1.0</servlet.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

Step Two: Define your web service interface

We’re going to create about the simplest possible web service, one that exposes a single method taking a single string parameter and returning a single string response. It returns the full name of a country when given its ISO country code.

The API of our JAX-WS web-service is defined by an annotated Java interface:-

package com.devguerrilla.quickstart.cxf.countrylookup;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(targetNamespace =
        "https://www.devguerrilla.com/countryLookupService")
public interface CountryLookupService {
    @WebResult(name="countryName") String getCountryName(
            @WebParam(name="countryCode") String countryCode);
}

The key thing here is the @WebService annotation which marks it as a web service interface.

Yes, okay, I said this was a “bare minimum” example. I fibbed. You don’t actually need the targetNamespace as JAX-WS will create a default one for you based on the package name. However it’s fairly minimal good practice to define your own instead, as it will be used in the generated WSDL to provide a unique container for its XML definitions. It doesn’t need to be a real URL but it’s good practice to base it on a real domain you own so you can ensure it won’t clash with other XML definitions your application, or your consumers applications use.

The @WebResult and @WebParam annotations are also not required, but your WSDL will end up with pretty unhelpful parameter names and response names (arg0 and return) if you don’t provide names yourself. 

Step Three: Create your web service implementation

Here’s our implementation class, which for this example supports a hard-coded single lookup:-

package com.devguerrilla.quickstart.cxf.countrylookup;

import javax.jws.WebService;

@WebService(targetNamespace =
        "https://www.devguerrilla.com/countryLookupService")
public class CountryLookupServiceImpl implements CountryLookupService {

    @Override
    public String getCountryName(String countryCode) {
        if("GB".equals(countryCode)) {
            return "Great Britain";
        }
        return null;
    }
}

The @WebService annotation isn’t required but if you don’t supply it and give it the same namespace as your interface you’ll end up with part of your XML definitions in a default namespace based on this class’s package.

Step Four: Configure your web application

We now need a web.xml to hook CXF into our web application and a Spring beans.xml to define our services. For my Maven build I’m putting both of these files under src/main/webapp/WEB-INF.

First the beans.xml file:-

<?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:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://cxf.apache.org/jaxws
        http://cxf.apache.org/schemas/jaxws.xsd">

    <jaxws:endpoint id="countryLookupService"
        implementor="com.devguerrilla.quickstart.cxf.countrylookup.CountryLookupServiceImpl"
        address="/countryLookupService">
    </jaxws:endpoint>
</beans>

Most of this is boilerplate except for the jaxws:endpoint bit, to which we’re giving a unique ID, our fully qualified implementation class name and the web application relative URL of its endpoint.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    version="3.1"
    xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/beans.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>
            org.apache.cxf.transport.servlet.CXFServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

There are four things our web.xml needs to do. First it defines the location of our beans.xml file relative to our web application. Secondly it adds the Spring ContextLoaderListener class as a listener; this will get Spring to load the beans.xml for us when the application starts. Thirdly it defines the CXFServlet that will handle web service requests and finally it maps that servlet to all request URLs for this application. 

Step Five: Deploy and enjoy

Build this application and deploy the WAR file to Tomcat 8 and start it up.

Your WSDL will be sitting on http://<hostname>:<port>/<context>/<address>?wsdl. For me, that’s http://localhost:8080/country-lookup-service/countryLookupService?wsdl.

Testing it via SoapUI, firing in the following request:-

<soapenv:Envelope 
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
        xmlns:coun="https://www.devguerrilla.com/countryLookupService">
    <soapenv:Header/>
    <soapenv:Body>
        <coun:getCountryName>
            <countryCode>GB</countryCode>
        </coun:getCountryName>
    </soapenv:Body>
</soapenv:Envelope>

Successfully yields the following response:-

<soap:Envelope 
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:getCountryNameResponse 
                xmlns:ns2="https://www.devguerrilla.com/countryLookupService">
            <countryName>Great Britain</countryName>
        </ns2:getCountryNameResponse>
    </soap:Body>
</soap:Envelope>

Simples.

 

Leave a Reply

Your email address will not be published. Required fields are marked *