Spring in Practice

Willie Wheeler's Spring blog

Web Services With Spring 2.5 and Apache CXF 2.0

| Comments

In this tutorial I explain how to get a web service up and running using Spring 2.5 and Apache CXF 2.0, which is the combination of Celtix and XFire and is considered XFire 2.0. (I don’t know what the Celtix team would say about that, but that’s what the XFire site says.) Here I just treat the web service itself; to learn about consuming the web service using Spring and CXF, please see the article Make web services transparent with Spring 2.5 and Apache CXF 2.0.

CXF supports both WSDL-first and Java-first web service development. This article takes the Java-first approach.

Project setup

The first thing you’ll need to do is download CXF from the Apache CXF site. At the time of this writing the project is in incubator status and the latest version is 2.0.4. That’s the version I’m using.

You’ll also find it useful to know about the section of the CXF user documentation that deals with writing a service with Spring, but currently the docs describe how to integrate with Spring 2.0, and since I want to integrate with Spring 2.5, there are some differences worth highlighting along the way.

Also, the docs describe a “Hello, World” web service that just returns a string, and in this tutorial we want to go a little further than that and actually do a class databinding.

Here are the service-side dependencies you’ll need. Inside the distribution there is a file called WHICH_JARS that describes in a little more detail what the JARs are for and which ones you’ll really need, if you’re interested in that. But the following is essentially the list given in the user docs.

CXF itself

  • cxf-2.0.4-incubator.jar

CXF dependencies

Note that for CXF 2.0.4 the documented dependencies are almost but not quite the same as the JARs that are actually included in the distribution, once again, at the time of this writing (February 2008). Specifically the stax, neethi and XMLSchema JARs are not the ones listed. Here’s the corrected list for CXF 2.0.4:

  • commons-logging-1.1.jar
  • geronimo-activation_1.1_spec-1.0-M1.jar (or Sun’s Activation jar)
  • geronimo-annotation_1.0_spec-1.1.jar (JSR 250)
  • geronimo-javamail_1.4_spec-1.0-M1.jar (or Sun’s JavaMail jar)
  • geronimo-servlet_2.5_spec-1.1-M1.jar (or Sun’s Servlet jar)
  • geronimo-stax-api_1.0_spec-1.0.jar
  • geronimo-ws-metadata_2.0_spec-1.1.1.jar (JSR 181)
  • jaxb-api-2.0.jar
  • jaxb-impl-2.0.5.jar
  • jaxws-api-2.0.jar
  • neethi-2.0.2.jar
  • saaj-api-1.3.jar
  • saaj-impl-1.3.jar
  • wsdl4j-1.6.1.jar
  • wstx-asl-3.2.1.jar
  • XmlSchema-1.3.2.jar
  • xml-resolver-1.2.jar

Aegis dependencies

In addition to the above, you will need to add jdom-1.0.jar since Aegis databinding uses it.

Spring dependencies

In this case ignore what’s in the CXF documentation, because we’re integrating with Spring 2.5 instead of Spring 2.0. I’m going to assume that you have Spring 2.5 already set up on your web services project, including Hibernate or anything else that your web services will need on the implementation side.

Just in case you were wondering, you don’t need the Spring MVC module’s DispatcherServlet as CXF comes with its own servlet, org.apache.cxf.transport.servlet.CXFServlet.

OK, that should be good for basic project setup. Let’s write a simple service.

Let’s write a simple web service

Let’s go basic but realistic and implement a “contact us” web service. The idea is that lots of different applications and/or web sites need to have a web-based form to allow end users to contact an admin team (business, tech support, development, whatever) responsible for monitoring such communications. We’ll implement a web service so that multiple applications can share it easily. There will be two operations:

  • View the list of submitted messages: This allows the admin team to see what the end users are saying.
  • Post a message: This allows end users to ask a question, tell us how great we are, demand their money back, etc.

Create the object model

As we want to explore more than the barest “Hello, World” functionality, let’s create a Message class that we can pass to the service and that we can get from the service. This will allow us to play a bit with Java/XML databindings.

Here’s the Message class:

package contactus;

import org.apache.cxf.aegis.type.java5.IgnoreProperty;

public final class Message {
    private String firstName;
    private String lastName;
    private String email;
    private String text;
    
    public Message() { }
    
    public Message(String firstName, String lastName, String email, String text) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.text = text;
    }

    public String getFirstName() { return firstName; }

    public void setFirstName(String firstName) { this.firstName = firstName; }

    public String getLastName() { return lastName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    
    @IgnoreProperty
    public String getLastNameFirstName() { return lastName + ", " + firstName; }
    
    public String getEmail() { return email; }

    public void setEmail(String email) { this.email = email; }

    public String getText() { return text; }

    public void setText(String text) { this.text = text; }
}

In real life I might have a bunch of Hibernate annotations in there (I’m a fan of annotation-based configuration), and I would create a DAO. But as this is a tutorial, we’ll keep it simple.

If you look carefully, however, you can see that I did in fact include an annotation. The @IgnoreProperty annotation is part of the Aegis databinding mechanism, which comes with CXF but is not the default. (JAXB is the default.) I ended up using Aegis instead of JAXB because JAXB was giving me trouble when I tried to return complex data types like Message from a web service. Maybe it works and maybe it doesn’t—I don’t know—but when I plugged Aegis in it worked immediately and now I intend to use Aegis. (More on JAXB/Aegis a little later.) Anyway, @IgnoreProperty tells Aegis that I don’t want getLastNameFirstName() to show up in the auto-generated WSDL. It’s just a read-only convenience method.

Create the service interface

Now we define a service interface.

package contactus;

import java.util.List;

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

@WebService
public interface ContactUsService {
    
    List<Message> getMessages();
    
    void postMessage(@WebParam(name = "message") Message message);
}

The @WebService and @WebParam are JAX-WS annotations. The first indicates that the interface defines a web service interface (we’ll use it to autogenerate a WSDL) and the second allows us to use an HTTP parameter called “message” to reference the operation’s argument instead of having to call it “arg0”, which is the default.

Create the service implementation

Here’s our simple implementation of the ContactUsService interface.

package contactus;

import java.util.ArrayList;
import java.util.List;

import javax.jws.WebService;

@WebService(endpointInterface = "contactus.ContactUsService")
public final class ContactUsServiceImpl implements ContactUsService {

    @Override
    public List<Message> getMessages() {
        List<Message> messages = new ArrayList<Message>();
        messages.add(new Message(
                "Willie", "Wheeler", "willie.wheeler@xyz.com", "Great job"));
        messages.add(new Message(
                "Dardy", "Chen", "dardy.chen@xyz.com", "I want my money back"));
        return messages;
    }

    @Override
    public void postMessage(Message message) {
        System.out.println(message);
    }
}

Here the @WebService annotation is marking this class as a web service implementation, and also specifying that the WSDL should be generated using the ContactUsService interface.

With that, we have the Java backend for the web service. Let’s move on to configuration.

Configure your web service

We need to create web.xml and a Spring application context file.

web.xml configuration

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="services" version="2.5">
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/appContext.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>
    </servlet>
    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

Nothing surprising here. The main thing worth mentioning is that we’re using the CXFServlet to process all requests, which we’re assuming are all requests for web services.

As you might guess from the web.xml file above, we need to create a Spring application context file called appContext.xml.

appContext.xml configuration

<?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"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
    default-autowire="byName">

    <!-- Load CXF modules from cxf.jar -->
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    
    <!-- Enable message logging using the CXF logging feature -->
    <cxf:bus>
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
    
    <!-- The service bean -->
    <bean id="contactUsServiceImpl" class="contactus.ContactUsServiceImpl"/>

    <!-- Aegis data binding -->
    <bean id="aegisBean"
        class="org.apache.cxf.aegis.databinding.AegisDatabinding"
        scope="prototype"/> 
    <bean id="jaxws-and-aegis-service-factory"
        class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean"
        scope="prototype">
        <property name="dataBinding" ref="aegisBean"/>
        <property name="serviceConfigurations">
            <list>
              <bean class="org.apache.cxf.jaxws.support.JaxWsServiceConfiguration"/>
              <bean class="org.apache.cxf.aegis.databinding.AegisServiceConfiguration"/>
              <bean class="org.apache.cxf.service.factory.DefaultServiceConfiguration"/> 
            </list>
        </property>
    </bean>
 
    <!-- Service endpoint -->
    <!-- See http://incubator.apache.org/cxf/faq.html regarding CXF + Spring AOP -->
    <jaxws:endpoint id="contactUsService"
            implementorClass="contactus.ContactUsServiceImpl"
            implementor="#contactUsServiceImpl"
            address="/contactus">
        <jaxws:serviceFactory>
            <ref bean="jaxws-and-aegis-service-factory"/>
        </jaxws:serviceFactory>
    </jaxws:endpoint>
</beans>

Let’s talk about appContext.xml.

The first three imports just import some bean definitions from the CXF JAR file. I don’t know the details and I don’t think we’re supposed to care about the details.

The bus config just puts logging in. That’s optional. If you run this in a production environment you probably want to turn that off because it’s verbose.

The service bean definition is just telling Spring about the service bean we wrote. We’re going to put a web service endpoint in front of it.

The next piece on Aegis specifies the databinding mechanism we want to use for mapping back and forth between Java and XML. I mentioned earlier that I tried using the default JAXB but was having problems getting it to work, so someone recommended to me to try Aegis and that worked like a charm.

Finally we define the web service endpoint. The #contactUsServiceImpl piece connects the endpoint up to the service bean. (Yes, the hash mark is required.) We use the JAX-WS/Aegis service factory that we defined earlier so we can use the Aegis databinding.

I’m not using transactions or persistence in my simple service bean. In a realistic service you are going to have transactions and persistence. To make CXF play nicely with AOP (and hence Spring’s declarative transactions) you are going to need to include the implementorClass attribute as I’ve done. For more details on that see the CXF FAQs at http://incubator.apache.org/cxf/faq.html.

A validation annoyance in Eclipse

Though it in no way reflects a fault with Eclipse itself, if you are using Eclipse 3.3 you may be getting some annoying validation errors in the IDE, such as “cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element ‘jaxws:endpoint’.” This is because the schema locations for the http://cxf.apache.org/core and http://cxf.apache.org/jaxws keys don’t actually point at real XSDs, so the validator won’t recognize elements from the cxf and jaxws namespaces. (That’s why I say it isn’t Eclipse’s fault.) This doesn’t create a problem with the actual build, but the IDE will complain as it tries to validate the Spring app context files against the missing XSDs. I haven’t yet discovered the “right” solution to this problem (I’d be delighted to have somebody tell me) but there are three workarounds I know about:

  • You can map the http://www.springframework.org/schema/beans key to the http://www.springframework.org/schema/beans/spring-beans.xsd location (notice that I’ve removed the -2.5 part). That gets rid of the validation errors though now you’re using the Spring 2.0 beans schema. That may be OK for you.
  • You can turn off XML validation. That seems pretty drastic but it’s an option (and in fact it’s what I’ve myself done).
  • You can just ignore the errors. Can’t… bring… myself… to… do… it… .

There may be something you can do with Window → Preferences → Web and XML → XML Catalog but I didn’t see it: even if you put the core.xsd and jaxws.xsd in the catalog, those XSDs in turn reference other schema locations that don’t resolve to an XSD and it looks like a lot of work to me.

Anyway as I say if somebody knows the right way to handle this please let me know and I’ll gladly update the article and credit you. :–)

Let ‘er rip

At this point you should have a working web service. Try it out. The URLs are:

  • [path to CXFServlet] - List of web services
  • [path to CXFServlet]/contactus?wsdl - WSDL for the Contact Us service
  • [path to CXFServlet]/contactus/getMessages - getMessages operation. Take a look at this and notice that, as promised, the individual messages don’t contain the firstNameLastName property, since we told Aegis to @IgnoreProperty.
  • [path to CXFServlet]/contactus/postMessage?message=... - postMessage operation

So for example if you are running an out-of-the-box Tomcat instance then your URLs would be

  • http://localhost:8080/mywebservices
  • http://localhost:8080/mywebservices/contactus?wsdl
  • http://localhost:8080/mywebservices/contactus/getMessages
  • http://localhost:8080/mywebservices/contactus/postMessage?message=...

replacing mywebservices with whatever you happened to use for your context root.

That’s it! If you want to learn how to consume web services using Spring 2.5 and CXF, please see my follow-up article Make web services transparent with Spring 2.5 and Apache CXF 2.0.

Update (2008-09-03): Sam Brodkin was kind enough to create a Maven project for this article. Thanks Sam!

Post migrated from my Wheeler Software site.

Comments