Chapter 17. EJB, Spring Remoting, and Web Services

In this chapter, you will learn about Spring's support for various remoting technologies, such as EJB, RMI, Hessian, Burlap, HTTP Invoker, and web services. Remoting is a key technology in developing distributed applications, especially multitier enterprise applications. It allows different applications or components, running in different JVMs or on different machines, to communicate with each other using a specific protocol.

Spring's remoting support is consistent across different remoting technologies. On the server side, Spring allows you to expose an arbitrary bean as a remote service through a service exporter. On the client side, Spring provides various proxy factory beans for you to create a local proxy for a remote service so that you can use the remote service as if it were a local bean.

Nowadays, there are two main approaches to developing web services: contract-first and contract-last. Automatically exposing a bean from the IoC container as a web service means that the service is contract-last because the service contract is generated from an existing bean. The Spring team has created a subproject called Spring Web Services (Spring-WS), which focuses on the development of contract-first web services. In this approach, a service contract is defined first, and code is then written to fulfill this contract.

Exposing and Invoking Services Through RMI

Problem

You want to expose a service from your Java application for other Java-based clients to invoke remotely. Because both parties are running on the Java platform, you can choose a pure Java-based solution without considering cross-platform portability.

Solution

Remote Method Invocation (RMI) is a Java-based remoting technology that allows two Java applications running in different JVMs to communicate with each other. With RMI, an object can invoke the methods of a remote object. RMI relies on object serialization to marshall and unmarshall method arguments and return values.

Considering the typical RMI usage scenario, to expose a service through RMI, you have to create the service interface that extends java.rmi.Remote and whose methods declare throwing java.rmi.RemoteException. Then, you create the service implementation for this interface. After that, you start an RMI registry and register your service to it. As you can see, there are quite a lot of steps required for exposing a simple service.

To invoke a service through RMI, you first look up the remote service reference in an RMI registry, and then, you can call the methods on it. However, to call the methods on a remote service, you must handle java.rmi.RemoteException in case any exception is thrown by the remote service.

Fortunately, Spring's remoting facilities can significantly simplify the RMI usage on both the server and client sides. On the server side, you can use RmiServiceExporter to export a Spring bean as an RMI service whose methods can be invoked remotely. It's just several lines of bean configuration without any programming. Beans exported in this way don't need to implement java.rmi.Remote or throw java.rmi.RemoteException. On the client side, you can simply use RmiProxyFactoryBean to create a proxy for the remote service. It allows you to use the remote service as if it were a local bean. Again, it requires no additional programming at all.

How It Works

Suppose you are going to build a weather web service for clients running on different platforms to invoke. This service includes an operation for querying a city's temperatures on multiple dates. First, you create the TemperatureInfo class representing the minimum, maximum, and average temperatures of a particular city and date.

package com.apress.springrecipes.weather;
...
public class TemperatureInfo implements Serializable {

    private String city;
    private Date date;
    private double min;
    private double max;
    private double average;

    // Constructors, Getters and Setters
    ...
}

Next, you define the service interface that includes the getTemperatures() operation, which returns a city's temperatures on multiple dates as requested.

package com.apress.springrecipes.weather;
...
public interface WeatherService {

    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates);
}

You have to provide an implementation for this interface. In a production application, you probably want to implement this service interface by querying the database. Here, you may hard-code the temperatures for testing purposes.

package com.apress.springrecipes.weather;
...
public class WeatherServiceImpl implements WeatherService {

    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates) {
        List<TemperatureInfo> temperatures = new ArrayList<TemperatureInfo>();
        for (Date date : dates) {
            temperatures.add(new TemperatureInfo(city, date, 5.0, 10.0, 8.0));
        }
        return temperatures;
    }
}

Exposing an RMI Service

Suppose you want to expose the weather service as an RMI service. To use Spring's remoting facilities for this purpose, create a bean configuration file such as rmi-server.xml in the classpath root to define the service. In this file, you declare a bean for the weather service implementation and export it as an RMI service by using RmiServiceExporter.

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

    <bean id="weatherService"
        class="com.apress.springrecipes.weather.WeatherServiceImpl" />

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="WeatherService" />
        <property name="serviceInterface"
            value="com.apress.springrecipes.weather.WeatherService" />
        <property name="service" ref="weatherService" />
    </bean>
</beans>

There are several properties you must configure for an RmiServiceExporter instance, including the service name, the service interface, and the service object to export. You can export any bean configured in the IoC container as an RMI service. RmiServiceExporter will create an RMI proxy to wrap this bean and bind it to the RMI registry. When the proxy receives an invocation request from the RMI registry, it will invoke the corresponding method on the bean.

By default, RmiServiceExporter attempts to look up an RMI registry at localhost port 1099. If it can't find the RMI registry, it will start a new one. However, if you want to bind your service to another running RMI registry, you can specify the host and port of that registry in the registryHost and registryPort properties. Note that once you specify the registry host, RmiServiceExporter will not start a new registry, even if the specified registry doesn't exist.

To start a server that provides the RMI weather service, run the following class to create an application context for the preceding bean configuration file:

package com.apress.springrecipes.weather;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RmiServer {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("rmi-server.xml");
    }
}

In this configuration, the server will launch; among the output, you should see a message indicating that an existing RMI registry could not be found.

Invoking an RMI Service

By using Spring's remoting facilities, you can invoke a remote service just like a local bean. For example, you can create a client that refers to the weather service by its interface.

package com.apress.springrecipes.weather;
...
public class WeatherServiceClient {

    private WeatherService weatherService;

    public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    public TemperatureInfo getTodayTemperature(String city) {
        List<Date> dates = Arrays.asList(new Date[] { new Date() });
        List<TemperatureInfo> temperatures =
            weatherService.getTemperatures(city, dates);
        return temperatures.get(0);
    }
}

In a client bean configuration file, such as client.xml located in the classpath root, you can use RmiProxyFactoryBean to create a proxy for the remote service. Then, you can use this service as if it were a local bean (e.g., inject it into the weather service client).

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="client"
        class="com.apress.springrecipes.weather.WeatherServiceClient">
        <property name="weatherService" ref="weatherService" />
    </bean>

 <bean id="weatherService"
        class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl"
            value="rmi://localhost:1099/WeatherService" />
        <property name="serviceInterface"
            value="com.apress.springrecipes.weather.WeatherService" />
    </bean>
</beans>

There are two properties you must configure for an RmiProxyFactoryBean instance. The service URL property specifies the host and port of the RMI registry, as well as the service name. The service interface allows this factory bean to create a proxy for the remote service against a known, shared Java interface. The proxy will transfer the invocation requests to the remote service transparently. You can test this service with the following Client main class:

package com.apress.springrecipes.weather;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("client.xml");
        WeatherServiceClient client =
            (WeatherServiceClient) context.getBean("client");

        TemperatureInfo temperature = client.getTodayTemperature("Houston");
        System.out.println("Min temperature : " + temperature.getMin());
        System.out.println("Max temperature : " + temperature.getMax());
        System.out.println("Average temperature : " + temperature.getAverage());
    }
}

Creating EJB 2.x Components with Spring

Problem

In EJB 2.x, as opposed to the current EJB 3.1 and what you may know from Spring, each EJB component requires a remote/local interface, a remote/local home interface, and a bean implementation class, in which you must implement all EJB life cycle callback methods even if you don't need them.

Solution

Sometimes, the proxy approach isn't 100 percent tenable for both clients and servers, but Spring still tries to lighten the load wherever possible. Spring even supports legacy component models like EJB 2.x, in addition to its support for the current EJB 3.x series. Examples in this chapter will also cover EJB 2.x integration, but it should be forewarned that you should avoid at all costs any new development on EJB 2.x. Newer iterations of the EJB specification are already deprecating large swathes of the older specification. Spring's remoting support can't completely remove the burden of all these requirements, but it does provide powerful support for building legacy EJB2.x components with Spring. The Spring support classes facilitate building session beans—stateful session beans (SFSBs) and stateless session beans (SLSBs)—and message-driven beans (MDBs) with Spring. Classic entity beans have no direct support in Spring, presumably because they map more usefully to something like Hibernate or JDO. These classes provide empty implementation for all EJB life cycle callback methods.

Your EJB classes can extend these classes to inherit the methods. Table 17-1 shows Spring's EJB support classes for different types of EJB.

Table 17.1. Spring's EJB Support Classes for Different Types of EJB

EJB Support Class

EJB Type

AbstractStatelessSessionBean

Stateless session bean

AbstractStatefulSessionBean

Stateful session bean

AbstractMessageDrivenBean

General message-driven bean that may not use JMS

AbstractJmsMessageDrivenBean

Message-driven bean that uses JMS

Moreover, the EJB support classes provide access to the Spring IoC container for you to implement your business logic in POJOs and wrap them with EJB components. Because POJOs are easier to develop and test, implementing business logic in POJOs can accelerate your EJB development.

How It Works

Suppose you are going to develop a system for a post office. You are asked to develop a stateless session bean for calculating postage based on the destination country and the weight. The target runtime environment is an application server that supports EJB 2.x only, so you have to develop the EJB component that will work with this version. Obviously, this scenario is not ideal, but there are still quite a few shops that are stuck on EJB 2.x!

Compared with lightweight POJOs, EJB 2.x components are more difficult to build, deploy, and test. A good practice for developing EJB 2.x components is to implement business logic in POJOs and then wrap them with EJB components. First, you define the following business interface for postage calculation:

package com.apress.springrecipes.post;

public interface PostageService {

    public double calculatePostage(String country, double weight);
}

Next, you have to implement this interface. Typically, it should query the database for the postage and perform some calculation. Here, you may hard-code the result for testing purposes.

package com.apress.springrecipes.post;

public class PostageServiceImpl implements PostageService {

    public double calculatePostage(String country, double weight) {
        return 1.0;
    }
}

Before you start creating your EJB component, you might like to have a simple EJB container for testing purposes. For simplicity's sake, we have chosen Apache OpenEJB (http://openejb.apache.org/) as the EJB container, which is very easy to install, configure, and deploy. OpenEJB is an open source EJB container. OpenEJB was designed for the Apache Geronimo server project (http://geronimo.apache.org/), but you don't need Apache Geronimo to run OpenEJB.

Note

You can download OpenEJB Standalone Server (e.g., v3.1.2) from the OpenEJB web site and extract it to a directory of your choice to complete the installation.

Creating EJB 2.x Components Without Spring's Support

First, let's create the EJB component without Spring's support. To allow remote access to this EJB component, you expose the following remote interface to clients.

Note

To compile and build your EJB component, you have to include a library that contains standard EJB classes and interfaces in your classpath. OpenEJB 3.1.1 supports both legacy EJB 2.x components as well as newer EJB 3.0 and EJB 3.1 components, so we'll use its implementation library. If you are using Maven, add the following dependency to your classpath.

<dependency>
  <groupId>org.apache.openejb</groupId>
  <artifactId>openejb-client</artifactId>
<version>3.1</version>
 </dependency>

 <dependency>
  <groupId>org.apache.openejb</groupId>
  <artifactId>openejb-jee</artifactId>
  <version>3.1</version>
 </dependency>
package com.apress.springrecipes.post;

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

public interface PostageServiceRemote extends EJBObject {

    public double calculatePostage(String country, double weight)
        throws RemoteException;
}

This calculatePostage() method has a signature similar to that in the business interface, except it declares throwing RemoteException.

Also, you need a remote home interface for clients to retrieve a remote reference to this EJB component, whose methods must declare throwing RemoteException and CreateException.

package com.apress.springrecipes.post;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface PostageServiceHome extends EJBHome {

    public PostageServiceRemote create() throws RemoteException, CreateException;
}

If you want to expose this EJB component for local access within an enterprise application, the preceding two interfaces should extend EJBLocalObject and EJBLocalHome instead, whose methods don't need to throw RemoteException. For simplicity's sake, we're omitting the local and local home interfaces here.

Note that the following EJB implementation class also implements the PostageService business interface so that you can delegate requests to the POJO service implementation.

package com.apress.springrecipes.post;

import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class PostageServiceBean implements SessionBean, PostageService {

    private PostageService postageService;
    private SessionContext sessionContext;
  // this isn't part of the interface, but is required
    public void ejbCreate() {
        postageService = new PostageServiceImpl();
    }

    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void ejbRemove() {}

    public void setSessionContext(SessionContext sessionContext) {
        this.sessionContext = sessionContext;
    }

    public double calculatePostage(String country, double weight) {
        return postageService.calculatePostage(country, weight);
    }
}

In the ejbCreate() life cycle method, you instantiate the POJO service implementation class. It's up to this object to perform the actual postage calculation. The EJB component just delegates requests to this object.

The astute reader will note that the ejbCreate() method is nowhere to be found on the SessionBean interface. Instead, it's a convention. Stateless session beans can contain one version of ejbCreate(). If the method has any arguments, the corresponding create method on the EJBHome bean must have the same arguments. The ejbCreate() method is the EJB hook for initialization of state, much as a JSR-250 annotated @PostConstruct() method or afterPropertiesSet() method work in Java EE 5 and Spring. If you have a stateful session bean, then there may be multiple overloaded ejbCreate() methods. Similarly, for each overloaded form of ejbCreate() on the SessionBean, there must be a create method with the same arguments on the EJBHome. We mention all this (which, if we're honest, doesn't even begin to cover the nuances involved) to put in stark relief the Spring container's lightweight approach and to show that even the Spring abstractions for EJB 2.x are incredible improvements.

Finally, you require an EJB deployment descriptor for your EJB component. You create the file ejb-jar.xml in the META-INF directory of your classpath and add the following contents to describe your EJB component:

<ejb-jar>
    <enterprise-beans>
        <session>
            <display-name>PostageService</display-name>
            <ejb-name>PostageService</ejb-name>
       <home>com.apress.springrecipes.post.PostageServiceHome</home>
<remote>com.apress.springrecipes.post.PostageServiceRemote</remote>
            <ejb-class>
                com.apress.springrecipes.post.PostageServiceBean
            </ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Bean</transaction-type>
        </session>
    </enterprise-beans>
</ejb-jar>

Now, your EJB component is finished, and you should pack your interfaces, classes, and deployment descriptors in a JAR file. Then start up your EJB container, and deploy this EJB component to it.

Note

To start the OpenEJB container, you first set the OPENEJB_HOME environment variable to point to your OpenEJB installation directory. Then execute the OpenEJB startup script (located in the bin directory) with the parameter start (e.g., openejb start). In another shell, to deploy an EJB component, you also execute the OpenEJB startup script, but this time, you pass deploy and the location of your EJB JAR file as parameters. (e.g., openejb deploy ./PostService.jar).

For OpenEJB, the default JNDI name for a remote home interface of an EJB 2.x component is the EJB name with RemoteHome as its suffix (PostageServiceRemoteHome in this case). If the deployment is successful, you should see the following output:

Application deployed successfully at "c:PostageService.jar"

App(id=C:openejb-3.1.2appsPostageService.jar)

    EjbJar(id=PostageService.jar, path=C:openejb-3.1.2appsPostageService.jar)

        Ejb(ejb-name=PostageService, id=PostageService)

            Jndi(name=PostageServiceRemoteHome)

            Jndi(name=PostageServiceLocal)

Creating EJB 2.x Components with Spring's Support

As you can see, your EJB implementation class needs to implement all EJB life cycle methods even if you don't need them. It should extend Spring's EJB support class to get the life cycle methods implemented by default. The support class for stateless session beans is AbstractStatelessSessionBean.

Note

To use Spring's EJB support for your EJB implementation classes, you have to include a few Spring framework JARs, including spring-beans, spring-core, spring-context, spring-asm, and spring-expression in the classpath of your EJB container. For OpenEJB, you can copy these JAR files to the lib directory of the OpenEJB installation directory. If your OpenEJB container is running, you will have to restart it.

package com.apress.springrecipes.post;

import javax.ejb.CreateException;

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class PostageServiceBean extends AbstractStatelessSessionBean
        implements PostageService {

    private PostageService postageService;

    protected void onEjbCreate() throws CreateException {
        postageService = (PostageService)
                getBeanFactory().getBean("postageService");
    }

    public double calculatePostage(String country, double weight) {
        return postageService.calculatePostage(country, weight);
    }
}

When you extend the AbstractStatelessSessionBean class, your EJB class no longer needs to implement any EJB life cycle methods, but you can still override them if necessary. Note that this class has an onEjbCreate() method that you must implement to perform initialization tasks. Here, you just retrieve the postageService bean from the Spring IoC container for this EJB component to use. Of course, you must define it in a bean configuration file. This file can have an arbitrary name but must be located in the classpath. For example, you can create it as beans-ejb.xml in the root of the classpath.

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

    <bean id="postageService"
        class="com.apress.springrecipes.post.PostageServiceImpl" />
</beans>

The final step is to tell the EJB support class where your bean configuration is. By default, it looks at the JNDI environment variable java:comp/env/ejb/BeanFactoryPath for the file location. So, you add an environment entry to your EJB deployment descriptor for this location.

<ejb-jar>
    <enterprise-beans>
        <session>
            <display-name>PostageService</display-name>
            <ejb-name>PostageService</ejb-name>
            <home>com.apress.springrecipes.post.PostageServiceHome</home>
            <remote>com.apress.springrecipes.post.PostageServiceRemote
</remote>
            <ejb-class>
                com.apress.springrecipes.post.PostageServiceBean
            </ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Bean</transaction-type>
            <env-entry>
                <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
                <env-entry-type>java.lang.String</env-entry-type>
                <env-entry-value>beans-ejb.xml</env-entry-value>
            </env-entry>
        </session>
    </enterprise-beans>
</ejb-jar>

The EJB support classes instantiate the Spring IoC container using BeanFactoryLocator. The default BeanFactoryLocator they use is ContextJndiBeanFactoryLocator, which instantiates the IoC (a regular BeanFactory implementation such as ApplicationContext) container using a bean configuration file specified by the JNDI environment variable java:comp/env/ejb/BeanFactoryPath. You can override this variable name by calling the setBeanFactoryLocatorKey() method in a constructor or in the setSessionContext() method.

Now, you can repack your EJB JAR file to include the preceding bean configuration file and redeploy it to your EJB container. In OpenEJB, this is a simple undeploy and redeploy sequence. It will vary from container to container.

Accessing Legacy EJB 2.x Components in Spring

Problem

In EJB 2.x, you have to perform the following tasks to invoke a method on a remote EJB component. Invoking a method on a local EJB component is very similar, except that you have no need to handle RemoteException.

  • Initialize the JNDI lookup context, which may throw a NamingException.

  • Look up the home interface from JNDI, which may throw a NamingException.

  • Retrieve a remote EJB reference from the home interface, which may throw a CreateException or a RemoteException.

  • Invoke the method on the remote interface, which may throw a RemoteException.

As you can see, invoking a method on an EJB component requires a lot of coding. The exceptions NamingException, CreateException, and RemoteException are all checked exceptions that you must handle. Moreover, your client is bound to EJB and would require a lot of changes if you ever switched the service implementation from EJB to another technology.

Solution

Spring offers two factory beans, SimpleRemoteStatelessSessionProxyFactoryBean and LocalStatelessSessionProxyFactoryBean, for creating a proxy for a remote and local stateless session bean respectively. They allow EJB clients to invoke an EJB component by the business interface as if it were a simple local object. The proxy handles the JNDI context initialization, home interface lookup, and invocation of local/remote EJB methods behind the scenes.

The EJB proxy also converts exceptions such as NamingException, CreateException, and RemoteException into runtime exceptions, so the client code is not required to handle them. For example, if a RemoteException is thrown when accessing a remote EJB component, the EJB proxy will convert it into Spring's runtime exception RemoteAccessException.

How It Works

Suppose that there's a front desk subsystem in your post office system that requires postage calculation. First, let's define the FrontDesk interface as follows:

package com.apress.springrecipes.post;

public interface FrontDesk {

    public double calculatePostage(String country, double weight);
}

Because there's an EJB 2.x remote stateless session bean for calculating postage, you only have to access it in your front desk subsystem. To talk to the remote service, you interface in terms of the EJBHome and the EJB Remote (PostageServiceRemote) interface. It is against these interfaces that a client-side proxy will be created. You are given the following remote interface and home interface for this EJB component:

package com.apress.springrecipes.post;

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

public interface PostageServiceRemote extends EJBObject {

    public double calculatePostage(String country, double weight)
        throws RemoteException;
}
package com.apress.springrecipes.post;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface PostageServiceHome extends EJBHome {

    public PostageServiceRemote create() throws RemoteException, CreateException;
}

Suppose this EJB component has already been deployed in an EJB container (e.g., an OpenEJB container started up on localhost). The JNDI name of this EJB component is PostageServiceRemoteHome.

Note

To access an EJB component deployed in an EJB container, you have to include the EJB container's client library in your classpath. If you are using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.apache.openejb</groupId>
  <artifactId>openejb-client</artifactId>
  <version>3.1</version>
 </dependency>

Accessing EJB 2.x Components

With Spring's support, accessing an EJB component can be significantly simplified. You can access an EJB component by its business interface. A business interface differs from an EJB remote interface in that it doesn't extend EJBObject, and its method declarations don't throw RemoteException, which means that the client doesn't have to handle this type of exception, and it doesn't know that the service is implemented by an EJB component. The business interface for postage calculation is shown following:

package com.apress.springrecipes.post;

public interface PostageService {
    public double calculatePostage(String country, double weight);
}

Now, in FrontDeskImpl, you can define a setter method for the PostageService business interface to let Spring inject the service implementation so that your FrontDeskImpl will no longer be EJB specific. Later, if you reimplement the PostageService interface with another technology (SOAP, RMI, Hessian/Burlap, Flash AMF, etc.), you won't need to modify a single line of code.

package com.apress.springrecipes.post;

public class FrontDeskImpl implements FrontDesk {

    private PostageService postageService;

    public void setPostageService(PostageService postageService) {
        this.postageService = postageService;
    }

    public double calculatePostage(String country, double weight) {
        return postageService.calculatePostage(country, weight);
    }
}

Spring offers the proxy factory bean SimpleRemoteStatelessSessionProxyFactoryBean to create a local proxy for a remote stateless session bean.

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

    <bean id="postageService"
class="org.springframework.ejb.access.SimpleRemoteStatelessSession
Accessing EJB 2.x Components
ProxyFactoryBean"> <property name="jndiEnvironment"> <props> <prop key="java.naming.factory.initial"> org.apache.openejb.client.RemoteInitialContextFactory </prop> <prop key="java.naming.provider.url"> ejbd://localhost:4201 </prop> </props> </property> <property name="jndiName" value="PostageServiceRemoteHome" /> <property name="businessInterface" value="com.apress.springrecipes.post.PostageService" /> </bean> <bean id="frontDesk" class="com.apress.springrecipes.post.FrontDeskImpl"> <property name="postageService" ref="postageService" /> </bean> </beans>

You have to configure the JNDI details for this EJB proxy in the jndiEnvironment and jndiName properties. The most important is to specify the business interface for this proxy to implement. The calls to methods declared in this interface will be translated into remote method calls to the remote EJB component. You can inject this proxy into FrontDeskImpl in the same way as a normal bean.

EJB proxies can also be defined using the <jee:remote-slsb> and <jee:local-slsb> elements in the jee schema. You must add the jee schema definition to the <beans> root element beforehand.

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

    <jee:remote-slsb id="postageService"
        jndi-name="PostageServiceRemoteHome"
        business-interface="com.apress.springrecipes.post.PostageService">
        <jee:environment>
            java.naming.factory.initial=
Accessing EJB 2.x Components
org.apache.openejb.client.RemoteInitialContextFactory java.naming.provider.url=ejbd://localhost:4201 </jee:environment> </jee:remote-slsb> ... </bean>

To access the EJB from a client, your code looks like almost any example demonstrating accessing beans from a Spring context. It describes the instantiation of a Spring ApplicationContext and the use of a bean that's interface-compatible with your service's POJO interface.

package com.apress.springrecipes.post;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FrontDeskMain {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans-front.xml");

        FrontDesk frontDesk = (FrontDesk) context.getBean("frontDesk");
        double postage = frontDesk.calculatePostage("US", 1.5);
        System.out.println(postage);
    }
}

Creating EJB 3.0 Components in Spring

Problem

Happily, creating EJB 3.0 components is much simpler than with EJB 2.x. Indeed, an EJB can be as simple as a business interface and a POJO implementation class. In EJB 3.1, even this restriction is lifted, so that, like with Spring, you can simply specify a POJO and expose that a service. However, writing EJB 3 beans could be unpleasant if you need access to beans in a Spring application context. Without special support, there is no way to have beans auto-wired into the EJB.

Solution

Use the org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor interceptor to let Spring configure @Autowired elements on your EJB.

How It Works

First, build an EJB component. You will need to specify an implementation and at minimum a remote interface. The business interface for our EJB 3.0 stateless session bean will be the same as for the EJB 2.0 example. The bean implementation class can be a simple Java class that implements this interface and is annotated with the EJB annotations. A remote stateless session bean requires the @Stateless and @Remote annotations. In the @Remote annotation, you have to specify the remote interface for this EJB component.

package com.apress.springrecipes.post;

public interface PostageService {
   public double calculatePostage(String country, double weight);
}

Next, we must create an implementation.

package com.apress.springrecipes.post;

import javax.ejb.Remote;
import javax.ejb.Stateless;

@Stateless
@Remote( { PostageService.class })
public class PostageServiceBean implements PostageService {

   public double calculatePostage(String country, double weight) {
      return 1.0;
   }
}

You specify the remote interface for the session bean using the @Remote annotation that decorates the class. That is all that's required for the coding to create a working EJB3 bean. Compile and then package these classes into a .jar. Then, deploy them to OpenEJB in the same manner as for the EJB 2.x example.

Thus far, you've created a very simple working service in almost no code and haven't needed to use Spring. Let's have Spring inject a resource, which might be useful for a host of reasons: proxying Spring services with EJB3s, injecting custom resources configured in Spring, or even using Spring to isolate your EJBs from acquiring references to other distributed resources such as a REST endpoint or an RMI endpoint.

To do this, use Spring's SpringBeanAutowiringInterceptor class to provide configuration for the EJB. Here is the implementation class (PostageServiceBean) with a few extra lines. First, an @Interceptors annotation decorates the PostageServiceBean. This tells Spring to handle @Autowired injection points in the class. The interceptor obtains beans, by default, from a ContextSingletonBeanFactoryLocation, which in turn looks for an XML application context named beanRefContext.xml, which is presumed to be on the classpath. The second new line of interest here is an example injection of a Spring JdbcTemplate instance.

package com.apress.springrecipes.post;

import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor;

@Stateless
@Remote( { PostageService.class })
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class PostageServiceBean implements PostageService {

               @Autowired
              private JdbcTemplate jdbcTemplate;

              public double calculatePostage(String country, double weight) {
                            // use the jdbcTemplate ...
                            return 1.0;
              }
}

There are few provisos associated with the use of the SpringBeanAutowiringIntereptor: First, don't use the same name for your Spring bean as you do your EJB name using this interceptor. Second, you'll need to extend the SpringBeanAutowiringInterceptor and override the getBeanFactoryLocatorKey method if you have more than one ApplicationContext on your EJB's class loader.

Accessing EJB 3.0 Components in Spring

Problem

EJB 3.0 offers some improvements on EJB 2.x. First, the EJB interface is a simple Java interface whose methods don't throw RemoteException, while the implementation class is a simple Java class annotated with EJB annotations. Moreover, the concept of home interface has been eliminated to simplify the EJB lookup process. You can look up an EJB reference from JNDI directly in EJB 3.0. However, the JNDI lookup code is still complex, and you also have to handle NamingException.

Solution

By using Spring's JndiObjectFactoryBean, you can easily declare a JNDI object reference in the Spring IoC container. You can use this factory bean to declare a reference to an EJB 3.0 component.

How It Works

Now that you've created and deployed an EJB component, you can create a client. If you have chosen OpenEJB as your EJB container, the default JNDI name for a remote EJB 3.0 component is the EJB class name with Remote as its suffix (PostageServiceBeanRemote, in this case). Note that the JNDI name formulation isn't specified in the standard in EJB 3.0, so this may change from container to container. In EJB 3.1, this is remedied, and beans are prescribed a predictable naming scheme so that bean JNDI names are portable across implementations.

Accessing EJB 3.0 Components with Spring's Support

Accessing EJB 3.0 components is simpler than EJB 2.x. You can look up an EJB reference from JNDI directly without looking up its home interface first, so you don't need to handle CreateException. Moreover, the EJB interface is a business interface that doesn't throw a RemoteException.

Although accessing EJB 3.0 components is simpler than EJB 2.x, the JNDI lookup code is still too complex, and you have to handle NamingException. With Spring's support, your FrontDeskImpl class can define a setter method for this EJB component's business interface for Spring to inject the EJB reference that is looked up from JNDI.

package com.apress.springrecipes.post;

public class FrontDeskImpl implements FrontDesk {

    private PostageService postageService;

    public void setPostageService(PostageService postageService) {
        this.postageService = postageService;
    }
public double calculatePostage(String country, double weight) {
        return postageService.calculatePostage(country, weight);
    }
}

Spring offers the factory bean JndiObjectFactoryBean to declare a JNDI object reference in its IoC container. You declare this bean in the front desk system's bean configuration file.

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

    <bean id="postageService"
        class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiEnvironment">
            <props>
                <prop key="java.naming.factory.initial">
                    org.apache.openejb.client.RemoteInitialContextFactory
                </prop>
                <prop key="java.naming.provider.url">
                    ejbd://localhost:4201
                </prop>
            </props>
        </property>
        <property name="jndiName" value="PostageServiceBeanRemote" />
    </bean>

    <bean id="frontDesk"
        class="com.apress.springrecipes.post.FrontDeskImpl">
        <property name="postageService" ref="postageService" />
    </bean>
</beans>

You can configure the JNDI details for this factory bean in the jndiEnvironment and jndiName properties. Then, you can inject this proxy into FrontDeskImpl in the same way as a normal bean.

In Spring, JNDI objects can also be defined using the <jee:jndi-lookup> element in the jee schema.

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

    <jee:jndi-lookup id="postageService"
        jndi-name="PostageServiceBeanRemote">
        <jee:environment>
            java.naming.factory.initial=org.apache.openejb.client.RemoteInitialContextFactory
java.naming.provider.url=ejbd://localhost:4201
        </jee:environment>
    </jee:jndi-lookup>
    ...
</beans>

Exposing and Invoking Services Through HTTP

Problem

RMI and EJB communicate through their own protocol, which may not pass through firewalls. Ideally, you'd like to communicate over HTTP.

Solution

Hessian and Burlap are two simple lightweight remoting technologies developed by Caucho Technology (http://www.caucho.com/). They both communicate using proprietary messages over HTTP and have their own serialization mechanism, but they are much simpler than web services. The only difference between them is that Hessian communicates using binary messages, and Burlap communicates using XML messages. The message formats of both Hessian and Burlap are also supported on other platforms besides Java, such as PHP, Python, C#, and Ruby. This allows your Java applications to communicate with applications running on the other platforms.

In addition to the preceding two technologies, the Spring framework itself also offers a remoting technology called HTTP Invoker. It also communicates over HTTP, but uses Java's object serialization mechanism to serialize objects. Unlike Hessian and Burlap, HTTP Invoker requires both sides of a service to be running on the Java platform and using the Spring framework. However, it can serialize all kinds of Java objects, some of which may not be serialized by Hessian/Burlap's proprietary mechanism.

Spring's remoting facilities are consistent in exposing and invoking remote services with these technologies. On the server side, you can create a service exporter such as HessianServiceExporter, BurlapServiceExporter, or HttpInvokerServiceExporter to export a Spring bean as a remote service whose methods can be invoked remotely. It's just several lines of bean configurations without any programming. On the client side, you can simply configure a proxy factory bean such as HessianProxyFactoryBean, BurlapProxyFactoryBean, or HttpInvokerProxyFactoryBean to create a proxy for a remote service. It allows you to use the remote service as if it were a local bean. Again, it requires no additional programming at all.

How It Works

Exposing a Hessian Service

To expose a Hessian service with Spring, you have to create a web application using Spring MVC. First, you create the following directory structure for your web application context.

Note

To expose a Hessian or Burlap service, you have to add the Hessian library to your classpath. If you are using Maven, add one or both of the following dependencies to your project.

<dependency>
   <groupId>com.caucho</groupId>
   <artifactId>hessian</artifactId>
   <version>3.1.5</version>
 </dependency>

 <dependency>
   <groupId>com.caucho</groupId>
   <artifactId>burlap</artifactId>
   <version>2.1.12</version></dependency>
weather/
    WEB-INF/
        classes/
        lib/*jar
        weather-servlet.xml
        web.xml

In the web deployment descriptor (i.e., web.xml), you have to configure Spring MVC's DispatcherServlet.

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <servlet>
        <servlet-name>weather</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

In the preceding servlet mapping definition, you map all URLs under the services path to DispatcherServlet. Because the name of this servlet is weather, you create the following Spring MVC configuration file, weather-servlet.xml, in the root of WEB-INF. In this file, you declare a bean for the weather service implementation and export it as a Hessian service using HessianServiceExporter.

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

    <bean id="weatherService"
        class="com.apress.springrecipes.weather.WeatherServiceImpl" />

    <bean name="/WeatherService"
        class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="weatherService" />
        <property name="serviceInterface"
            value="com.apress.springrecipes.weather.WeatherService" />
    </bean>
</beans>

For a HessianServiceExporter instance, you have to configure a service object to export and its service interface. You can export any bean configured in the IoC container as a Hessian service, and HessianServiceExporter will create a proxy to wrap this bean. When the proxy receives an invocation request, it will invoke the corresponding method on that bean. By default, BeanNameUrlHandlerMapping is preconfigured for a Spring MVC application. It maps requests to handlers according to the URL patterns specified as bean names. The preceding configuration maps the URL pattern /WeatherService to this exporter.

Now, you can deploy this web application to a web container (e.g., Apache Tomcat 6.0). By default, Tomcat listens on port 8080, so if you deploy your application to the weather context path, you can access this service with the following URL:

http://localhost:8080/weather/services/WeatherService

Invoking a Hessian Service

By using Spring's remoting facilities, you can invoke a remote service just like a local bean. In the client bean configuration file client.xml, you can use HessianProxyFactoryBean to create a proxy for the remote Hessian service. Then you can use this service as if it were a local bean.

<bean id="weatherService"
    class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl"
        value="http://localhost:8080/weather/services/WeatherService" />
    <property name="serviceInterface"
        value="com.apress.springrecipes.weather.WeatherService" />
</bean>

You have to configure two properties for a HessianProxyFactoryBean instance. The service URL property specifies the URL for the target service. The service interface property is for this factory bean to create a local proxy for the remote service. The proxy will send the invocation requests to the remote service transparently.

Exposing a Burlap Service

The configuration for exposing a Burlap service is similar to that for Hessian, except you should use BurlapServiceExporter instead.

<bean name="/WeatherService"
    class="org.springframework.remoting.caucho.BurlapServiceExporter">
    <property name="service" ref="weatherService" />
    <property name="serviceInterface"
        value="com.apress.springrecipes.weather.WeatherService" />
</bean>

Invoking a Burlap Service

Invoking a Burlap service is very similar to Hessian. The only difference is that you should use BurlapProxyFactoryBean.

<bean id="weatherService"
    class="org.springframework.remoting.caucho.BurlapProxyFactoryBean">
    <property name="serviceUrl"
        value="http://localhost:8080/weather/services/WeatherService" />
    <property name="serviceInterface"
        value="com.apress.springrecipes.weather.WeatherService" />
</bean>

Exposing an HTTP Invoker Service

Again, the configuration for exposing a service using HTTP Invoker is similar to that for Hessian and Burlap, except you have to use HttpInvokerServiceExporter instead.

<bean name="/WeatherService"
    class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="weatherService" />
    <property name="serviceInterface"
        value="com.apress.springrecipes.weather.WeatherService" />
</bean>

Invoking an HTTP Invoker Service

Invoking a service exposed by HTTP Invoker is also similar to Hessian and Burlap. This time, you have to use HttpInvokerProxyFactoryBean.

<bean id="weatherService"
    class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
    <property name="serviceUrl"
        value="http://localhost:8080/weather/services/WeatherService" />
    <property name="serviceInterface"
        value="com.apress.springrecipes.weather.WeatherService" />
</bean>

Choosing a SOAP Web Service Development Approach

Problem

When you are asked to develop a web service, you first have to consider which web service development approach you are going to use.

Solution

There are two approaches to developing a web service, depending on whether you define the contract first or last. A web service contract is described using Web Services Description Language (WSDL). In contract-last, you expose an existing service interface as a web service whose service contract is generated automatically. In contract-first, you design the service contract in terms of XML and then write code to fulfill it.

How It Works

Contract-Last Web Services

In contract-last web service development, you expose an existing service interface as a web service. Many tools and libraries can help expose a Java class/interface as a web service. They can generate the WSDL file for the class/interface by applying rules, such as turning the class/interface into a port type, turning the methods into operations, and generating the request/response message formats according to the method arguments and return value. All in all, everything is generated from a service interface like the following:

package com.apress.springrecipes.weather;
...
public interface WeatherService {

    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates);
}

This approach is called contract-last because you define the contract for this web service as the last step in the development process by generating it from Java code. In other words, you are designing the service with Java, not with WSDL or XML.

Contract-First Web Services

In contrast, the contract-first approach encourages you to think of the service contract first, in terms of XML, using XML schema (.xsd) and WSDL. In this approach, you design the request and response messages for your service first. The messages are designed with XML, which is very good at representing complex data structures in a platform- and language-independent way. The next step is to implement this contract in a particular platform and programming language.

For example, the request message of your weather service contains a city element and multiple date elements. Note that you should specify the namespace for your messages to avoid naming conflicts with other XML documents.

<GetTemperaturesRequest
    xmlns="http://springrecipes.apress.com/weather/schemas">
    <city>Houston</city>
    <date>2007-12-01</date>
    <date>2007-12-08</date>
    <date>2007-12-15</date>
</GetTemperaturesRequest>

Then, the response message would contain multiple Temperature elements in response to the requested city and dates.

<GetTemperaturesResponse
    xmlns="http://springrecipes.apress.com/weather/schemas">
    <TemperatureInfo city="Houston" date="2007-12-01">
        <min>5.0</min>
        <max>10.0</max>
        <average>8.0</average>
    </TemperatureInfo>
    <TemperatureInfo city="Houston" date="2007-12-08">
        <min>4.0</min>
        <max>13.0</max>
        <average>7.0</average>
    </TemperatureInfo>
    <TemperatureInfo city="Houston" date="2007-12-15">
        <min>10.0</min>
        <max>18.0</max>
        <average>15.0</average>
    </TemperatureInfo>
</GetTemperaturesResponse>

After designing the sample request and response messages, you can start creating the contract for this web service using XSD and WSDL. Many tools and IDEs can help generate the default XSD and WSDL files for an XML document. You only need to carry out a few optimizations to have it fit your requirements.

Comparison

When developing a contract-last web service, you are actually exposing the internal API of your application to clients. But this API is likely to be changed—and after it's changed, you will also have to change the contract of your web service, which may involve changing all the clients. However, if you design the contract first, it reflects the external API that you want to expose. It's not as likely to need changing as the internal API.

Although many tools and libraries can expose a Java class/interface as a web service, the fact is that the contract generated from Java is not always portable to other platforms. For example, a Java map may not be portable to other programming languages without a similar data structure. Sometimes, you have to change the method signature to make a service contract portable. In some cases, it's also hard to map an object to XML (e.g., an object graph with cyclic references) because there's actually an impedance mismatch between an object model and an XML model, just like that between an object model and a relational model.

XML is good at representing complex data structures in a platform- and language-independent way. A service contract defined with XML is 100 percent portable to any platform. In addition, you can define constraints in the XSD file for your messages so that they can be validated automatically. For these reasons, it's more efficient to design a service contract with XML and implement it with a programming language such as Java. There are many libraries in Java for processing XML efficiently.

From a performance viewpoint, generating a service contract from Java code may lead to an inefficient design. This is because you might not consider the message granularity carefully (specifically, the typical granularity of a Java method invocation is going to be considerably finer than that of an optimum network message. This is restated by Martin Fowler as Fowler's First Law of Distributed Object Design: "Don't distribute your objects (any more than you really have to)"), as it's derived from the method signature directly. In contrast, defining the service contract first is more likely to lead to an efficient design.

Finally, the biggest reason for choosing the contract-last approach is its simplicity. Exposing a Java class/interface as a web service doesn't require you to know much about XML, WSDL, SOAP, and so on. You can expose a web service very quickly.

Exposing and Invoking a Contract-Last SOAP Web Services Using JAX-WS

Problem

Because web services is a standard and cross-platform application communication technology, you want to expose a web service from your Java application for clients on different platforms to invoke.

Solution

Spring comes with several service exporters that can export a bean as a remote service based on the RMI, Hessian, Burlap, or HTTP Invoker remoting technologies, but Spring doesn't come with a service exporter that can export a bean as a SOAP web service. We will use Apache CXF, which is the de facto successor to XFire.

How It Works

The standard for deploying web services on the Java EE platform as of Java EE 1.4 was called JAX-RPC. It supported SOAP 1.0 and 1.1, but didn't support message-oriented web services. This standard was surfaced in the Servlet and EJB tiers in Java EE—you could take an EJB stateless session bean, for example, and have the container expose a SOAP endpoint. Java 5 saw the debut of support for annotations. When the architects behind JAX-RPC sought to create their next generation SOAP stack, JAX-RPC 2.0, they designed it with a significantly different approach that would support POJOs. Also, because this new stack would support message-oriented services—as opposed to simple synchronous RPC calls—it was decided the old name was no longer suitable. JAX-WS 2.0 was the result. It enjoys support in Java EE 5, as well as in JDK 1.6; you can expose an endpoint using the regular JDK with no need for Java EE. Java EE 6 (or, rather, JDK 1.6.0_04 or better) supports JAX-WS 2.1. So, while Spring has provided JAX-RPC support for a long time, we will focus on JAX-WS here, as it is clearly the intended path for services.

To send objects across the wire, beans need to be encoded using the Java Architecture for XML Binding (JAXB). JAXB supports many class types out of the box with no special support. A complex object graph may require some extra help. You can use JAXB annotations to give hints to the runtime as to the correct interpretation of the object graph.

Even within the world of JAX-WS, we have many choices for creating web services. If you are deploying into a Java EE 5 (or better) container, you may simply create a bean that is annotated with javax.jws.WebService or javax.jws.WebServiceProvider and deploy that into a container in your web application. From there, you'll have some intermediary steps. If you are using the JAX-RS Reference Implementation, this intermediary step will involve a tool called wsgen, which will generate the configuration (a file called sun-jaxws.xml) and wrapper beans required to expose your service. More contemporary frameworks like Apache CXF have no such step; your bean and web service implementation are generated at startup in your runtime: deployment of the annotated bean is the final step. In this scenario, there's little Spring needs to do for you, except perhaps enable access to the application context and so on. For this purpose, you may have your service extend org.springframework.web.context.support.SpringBeanAutowiringSupport. Your service endpoint then benefits from @Autowired and the like.

Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK

Another option is to deploy JAX-WS services outside of a container, using, for example, the stand-alone HTTP server. Spring provides a factory that can export beans annotated with javax.jws.WebService or javax.jws.WebServiceProvider inside the Spring context and then publishes the services using the JAX-WS runtime. If you have no runtime configured and are running on JDK 6, Spring will use the existing HTTP server support and JAX-WS RI in the JDK. Let's look at our example from previous recipes, the WeatherService service.

We need to annotate our service to indicate to the provider what should be exposed. Note the application of the javax.jws.WebMethod annotation to the method itself and the application of javax.jws.WebService to the service interface. You don't need to provide an endpointInterface or a serviceName, but we do here for the sake of demonstration. Similarly, you don't need to provide an operationName on the @WebMethod annotation, but we do for demonstration. This is generally good practice anyway, because it insulates clients of the SOAP endpoint from any refactoring you may do on the Java implementation.

The revised WeatherServiceImpl is as follows:

package com.apress.springrecipes.weather;

import javax.jws.WebMethod;
import javax.jws.WebService;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@WebService(serviceName = "WeatherService", endpointInterface = WeatherService.class+"")
public class WeatherServiceImpl implements WeatherService {

        @WebMethod(operationName = "getTemperatures")
    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates) {
        List<TemperatureInfo> temperatures = new ArrayList<TemperatureInfo>();

        for (Date date : dates) {
            temperatures.add(new TemperatureInfo(city, date, 5.0, 10.0, 8.0));
        }

        return temperatures;
    }
}

To then install the service and create the endpoint, we rely on Spring's SimpleJaxWsServiceExporter.

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <bean id="weatherServiceImpl"
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
class="com.apress.springrecipes.weather.WeatherServiceImpl"/> <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter"/> </beans>

If you launch a browser and inspect the results at http://localhost:8080/WeatherService?wsdl, you'll see the generated WSDL, which you can feed to clients to access the service. Remoting, even with Spring, doesn't get much simpler than this! The generated WSDL is pretty tame, all things considered.

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.6
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
in JDK 6. --> <definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
xmlns:tns="http://weather.springrecipes.apress.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://weather.springrecipes.apress.com/"
Exposing a Web Service Using The JAX-WS Endpoint Support in the JDK
name="WeatherService"> <types> <xsd:schema> <xsd:import namespace="http://weather.springrecipes.apress.com/" schemaLocation="http://localhost:8080/WeatherService?xsd=1"></xsd:import> </xsd:schema> </types> <message name="getTemperatures"> <part name="parameters" element="tns:getTemperatures"></part> </message> <message name="getTemperaturesResponse"> <part name="parameters" element="tns:getTemperaturesResponse"></part> </message> <portType name="WeatherServiceImpl"> <operation name="getTemperatures"> <input message="tns:getTemperatures"></input> <output message="tns:getTemperaturesResponse"></output> </operation> </portType> <binding name="WeatherServiceImplPortBinding" type="tns:WeatherServiceImpl"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"> </soap:binding> <operation name="getTemperatures"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal"></soap:body> </input> <output> <soap:body use="literal"></soap:body> </output> </operation> </binding> <service name="WeatherService"> <port name="WeatherServiceImplPort" binding="tns:WeatherServiceImplPortBinding"> <soap:address location="http://localhost:8080/WeatherService"></soap:address> </port> </service> </definitions>

Exposing a Web Service Using CXF

Exposing a stand-alone SOAP endpoint using the SimpleJaxWsServiceExporter or the support for JAX-WS in a Java EE container in conjunction with Spring is simple, but these solutions ignore the largest cross-section of developers—people developing on Tomcat. Here again, we have plenty of options. Tomcat doesn't support JAX-WS by itself, so we need to help it by embedding a JAX-WS runtime. There are many choices, and you're free to take your pick. Two popular choices are Axis2 and CXF, both of which are Apache projects. CXF represents the consolidation of the Celtix and XFire projects, which each had useful SOAP support. For our example, we'll embed CXF since it's robust, fairly well tested, and provides support for other important standards like JAX-RS, the API for REST-ful endpoints. Setup is fairly straightforward. You'll need to include the CXF dependencies on your classpath, as well as the Spring dependencies.

Let's walk through the moving pieces for configuration. A good place to start is the web.xml file. In our simple example, web.xml looks like this:

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


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

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

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>

    </servlet>
<servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/dispatch/*</url-pattern>
    </servlet-mapping>

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

    <servlet-mapping>
        <servlet-name>cxf</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

This web.xml file will look pretty much as all Spring MVC applications do. The only exception here is that we've also configured a CXFServlet, which handles a lot of the heavy lifting required to expose our service. In the Spring MVC configuration file, weather-servlet.xml, we'll declare a bean for the weather service implementation and export it as a web service by using the Spring namespace support that CXF provides for configuring services (as well as clients, which we'll soon see).

The Spring context file is underwhelming; most of it is boilerplate XML namespace and Spring context file imports. The only two salient stanzas are below, where we first configure the service itself as usual. Finally, we use the CXF jaxws:endpoint namespace to configure our endpoint.

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
       xmlns:simple="http://cxf.apache.org/simple"
       xmlns:soap="http://cxf.apache.org/bindings/soap"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
Exposing a Web Service Using CXF
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop
Exposing a Web Service Using CXF
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd http://cxf.apache.org/bindings/soap
Exposing a Web Service Using CXF
http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd ">
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>

    <bean id="weatherServiceImpl"
Exposing a Web Service Using CXF
class="com.apress.springrecipes.weather.WeatherServiceImpl"/> <jaxws:endpoint implementor="#weatherServiceImpl" address="/weatherService"> <jaxws:binding> <soap:soapBinding style="document" use="literal" version="1.1"/> </jaxws:binding> </jaxws:endpoint> </beans>

We tell the jaxws:endpoint factory to use our Spring bean as the implementation. We tell it at what address to publish the service using the address element. Note that we've found that using a forward slash at the beginning of the address is the only way to get the endpoint to work reliably across both Jetty and Tomcat; your mileage may vary. We also specify some extra niceties like what binding style to use. You don't need to, of course. This is mainly for illustration. The Java code stays the same as before, with the javax.jws.WebService method and javax.jws.WebMethod annotations in place. Launch the application and your web container, and then bring up the application in your browser. In this example, the application is deployed at the root context (/), so the SOAP endpoint is available at http://localhost:8080/weatherService. If you bring up the page at http://localhost:8080/, you'll see a directory of the available services and their operations. Click the link for the service's WSDL—or simply append ?wsdl to the service endpoint—to see the WSDL for the service. WSDL describes the messages and endpoint to clients. You can use it to infer a client to the service on most platforms.

Invoking a Web Service Using CXF

Let's now use CXF to define a web service client. We'll want one to work with our new weather service, after all! Our client is the same as in previous recipes, and there is no special Java configuration or coding to be done. We simply need the interface of the service on the classpath. Once that's done, you can use CXF's namespace support to create a client.

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
Invoking a Web Service Using CXF
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop
Invoking a Web Service Using CXF
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd ">
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
    <jaxws:client serviceClass="com.apress.springrecipes.weather.WeatherService"
              address="http://localhost:8080/weatherService" id="weatherService"/>
    <bean class="com.apress.springrecipes.weather.WeatherServiceClient" id="client">
        <property name="weatherService" ref="weatherService"/>
    </bean>
</beans>

We use the jaxws:client namespace support to define to which interface the proxy should be bound, and the endpoint of the service itself. That is all that's required. Our examples from previous recipes works otherwise unchanged: here we inject the client into the WeatherServiceClient and invoke it.

Defining the Contract of a Web Service

Problem

According to the contract-first web service approach, the first step of developing a web service is to define the service contract. How should you go about this?

Solution

A web service's contract consists of two parts: the data contract and the service contract. They are both defined with the XML technology in a platform- and language-independent way.

  • Data contract: Describes the complex data types and request and response messages of this web service. A data contract is typically defined with XSD, although you can also use DTDs, RELAX NG, or Schematron.

  • Service contract: Describes the operations of this web service. A web service may have multiple operations. A service contract is defined with WSDL.

When using a comprehensive web service development framework like Spring-WS, the service contract can usually be generated automatically. But you must create the data contract yourself.

To create the data contract for your web service, you can start by creating the XSD file. Because there are many powerful XML tools available in the community, this won't be too hard. However, most developers prefer to start by creating some sample XML messages and then generate the XSD file from them. Of course, you need to optimize the generated XSD file yourself, as it may not fit your requirements entirely, and sometimes, you may wish to add more constraints to it.

How It Works

Creating Sample XML Messages

For your weather service, you can represent the temperature of a particular city and date as in the following XML message:

<TemperatureInfo city="Houston" date="2007-12-01">
    <min>5.0</min>
    <max>10.0</max>
    <average>8.0</average>
</TemperatureInfo>

Then, you can define the data contract for your weather service. Suppose you want to define an operation that allows clients to query the temperatures of a particular city for multiple dates. Each request consists of a city element and multiple date elements. You should also specify the namespace for this request to avoid naming conflicts with other XML documents. Let's save this XML message to request.xml.

<GetTemperaturesRequest
    xmlns="http://springrecipes.apress.com/weather/schemas">
    <city>Houston</city>
    <date>2007-12-01</date>
    <date>2007-12-08</date>
    <date>2007-12-15</date>
</GetTemperaturesRequest>

The response consists of multiple TemperatureInfo elements, each of which represents the temperature of a particular city and date, in accordance with the requested dates. Let's save this XML message to response.xml.

<GetTemperaturesResponse
    xmlns="http://springrecipes.apress.com/weather/schemas">
    <TemperatureInfo city="Houston" date="2007-12-01">
        <min>5.0</min>
        <max>10.0</max>
        <average>8.0</average>
    </TemperatureInfo>
    <TemperatureInfo city="Houston" date="2007-12-08">
        <min>4.0</min>
        <max>13.0</max>
        <average>7.0</average>
    </TemperatureInfo>
    <TemperatureInfo city="Houston" date="2007-12-15">
        <min>10.0</min>
        <max>18.0</max>
        <average>15.0</average>
    </TemperatureInfo>
</GetTemperaturesResponse>

Generating an XSD File from Sample XML Messages

Now, you can generate the XSD file from the preceding sample XML messages. Most popular XML tools and enterprise Java IDEs can generate an XSD file from a couple of XML files. Here, I have chosen Apache XMLBeans (http://xmlbeans.apache.org/) to generate my XSD file.

Note

You can download Apache XMLBeans (e.g., v2.4.0) from the Apache XMLBeans web site and extract it to a directory of your choice to complete the installation.

Apache XMLBeans provides a tool called inst2xsd for generating XSD files from XML instance files. It supports several design types for generating XSD files. The simplest is called Russian doll design, which generates local elements and local types for the target XSD file. Because there's no enumeration type used in your XML messages, you should also disable the enumeration generation feature. You can execute the following command to generate the XSD file for your data contract:

inst2xsd -design rd -enumerations never request.xml response.xml

The generated XSD file will have the default name schema0.xsd, located in the same directory. Let's rename it to temperature.xsd.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified"
    elementFormDefault="qualified"
    targetNamespace="http://springrecipes.apress.com/weather/schemas"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="GetTemperaturesRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element type="xs:string" name="city" />
                <xs:element type="xs:date" name="date"
                    maxOccurs="unbounded" minOccurs="0" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="GetTemperaturesResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="TemperatureInfo"
                    maxOccurs="unbounded" minOccurs="0">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element type="xs:float" name="min" />
                            <xs:element type="xs:float" name="max" />
                            <xs:element type="xs:float" name="average" />
                        </xs:sequence>
<xs:attribute type="xs:string" name="city"
                            use="optional" />
                        <xs:attribute type="xs:date" name="date"
                            use="optional" />
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Optimizing the Generated XSD File

As you can see, the generated XSD file allows clients to query temperatures of unlimited dates. If you want to add a constraint on the maximum and minimum query dates, you can modify the maxOccurs and minOccurs a minOccurs attributes.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified"
    elementFormDefault="qualified"
    targetNamespace="http://springrecipes.apress.com/weather/schemas"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="GetTemperaturesRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element type="xs:string" name="city" />
                <xs:element type="xs:date" name="date"
                    maxOccurs="5" minOccurs="1" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="GetTemperaturesResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="TemperatureInfo"
                    maxOccurs="5" minOccurs="1">
                    ...
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Previewing the Generated WSDL File

As you will learn later, Spring-WS can automatically generate the service contract for you, based on the data contract and some conventions that you can override. Here, you can preview the generated WSDL file to better understand the service contract. For simplicity's sake, the less important parts are omitted.

<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions ...
    targetNamespace="http://springrecipes.apress.com/weather/schemas">
    <wsdl:types>
        <!-- Copied from the XSD file -->
        ...
    </wsdl:types>
    <wsdl:message name="GetTemperaturesResponse">
        <wsdl:part element="schema:GetTemperaturesResponse"
            name="GetTemperaturesResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="GetTemperaturesRequest">
        <wsdl:part element="schema:GetTemperaturesRequest"
            name="GetTemperaturesRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="Weather">
        <wsdl:operation name="GetTemperatures">
            <wsdl:input message="schema:GetTemperaturesRequest"
                name="GetTemperaturesRequest">
            </wsdl:input>
            <wsdl:output message="schema:GetTemperaturesResponse"
                name="GetTemperaturesResponse">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    ...
    <wsdl:service name="WeatherService">
        <wsdl:port binding="schema:WeatherBinding" name="WeatherPort">
            <soap:address
                location="http://localhost:8080/weather/services" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

In the Weather port type, a GetTemperatures operation is defined whose name is derived from the prefix of the input and output messages (i.e., <GetTemperaturesRequest> and <GetTemperaturesResponse>). The definitions of these two elements are included in the <wsdl:types> part, as defined in the data contract.

Implementing Web Services Using Spring-WS

Problem

Once you have defined the contract for your web service, you can start implementing the service itself according to this contract. You want to use Spring-WS to implement this service.

Solution

Spring-WS provides a set of facilities for you to develop contract-first web services. The essential tasks for building a Spring-WS web service include the following:

  • Setting up and configuring a Spring MVC application for Spring-WS

  • Mapping web service requests to endpoints

  • Creating service endpoints to handle the request messages and return the response messages

  • Publishing the WSDL file for this web service

The concept of an endpoint in web services is much like that of a controller in web applications. The difference is that a web controller deals with HTTP requests and HTTP responses, while a service endpoint deals with XML request messages and XML response messages. They both need to invoke other back-end services to handle the requests.

Spring-WS provides various abstract endpoint classes for you to process the request and response XML messages using different XML processing technologies and APIs. These classes are all located in the org.springframework.ws.server.endpoint package. You can simply extend one of them to process the XML messages with a particular technology or API. Table 17-2 lists these endpoint classes.

Table 17.2. Endpoint Classes for Different XML Processing Technologies/APIs

Technology/API

Endpoint Class

DOM

AbstractDomPayloadEndpoint

JDOM

AbstractJDomPayloadEndpoint

dom4j

AbstractDom4jPayloadEndpoint

XOM

AbstractXomPayloadEndpoint

SAX

AbstractSaxPayloadEndpoint

Event-based StAX

AbstractStaxEventPayloadEndpoint

Streaming StAX

AbstractStaxStreamPayloadEndpoint

XML marshalling

AbstractMarshallingPayloadEndpoint

Note that the preceding endpoint classes are all for creating payload endpoints. That means you can access only the payloads of the request and response messages (i.e., the contents in the SOAP body but not other parts of a message like the SOAP headers). If you need to get access to the entire SOAP message, you should write an endpoint class by implementing the org.springframework.ws.server.endpoint.MessageEndpoint org.springframework.ws.server.endpoint.MessageEndpoint interface.

How It Works

Setting Up a Spring-WS Application

To implement a web service using Spring-WS, you first create the following directory structure for your web application context. Ensure that your lib directory contains the latest version of Spring-WS.

weather/
    WEB-INF/
        classes/
        lib/*jar
        temperature.xsd
        weather-servlet.xml
        web.xml

In web.xml, you have to configure the MessageDispatcherServlet servlet of Spring-WS, which is different from DispatcherServlet for a typical Spring MVC application. This servlet specializes in dispatching web service messages to appropriate endpoints and detecting the framework facilities of Spring-WS.

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <servlet>
        <servlet-name>weather</servlet-name>
        <servlet-class>
            org.springframework.ws.transport.http.MessageDispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

In the Spring MVC configuration file, weather-servlet.xml, you first declare a bean for the weather service implementation. Later, you will define endpoints and mappings to handle the web service requests.

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

    <bean id="weatherService"
        class="com.apress.springrecipes.weather.WeatherServiceImpl" />
</beans>

Mapping Web Service Requests to Endpoints

In a Spring MVC application, you use handler mapping to map web requests to handlers. But in a Spring-WS application, you should use endpoint mapping to map web service requests to endpoints.

The most common endpoint mapping is PayloadRootQNameEndpointMapping. It maps web service requests to endpoints according to the name of the request payload's root element. The name used by this endpoint mapping is the qualified name (i.e., including the namespace). So you must include the namespace in the mapping keys, which is presented inside a brace.

<bean class="org.springframework.ws.server.endpoint.mapping.
Mapping Web Service Requests to Endpoints
PayloadRootQNameEndpointMapping"> <property name="mappings"> <props> <prop key="{http://springrecipes.apress.com/weather/schemas}
Mapping Web Service Requests to Endpoints
GetTemperaturesRequest"> temperatureEndpoint </prop> </props> </property> </bean>

Creating Service Endpoints

Spring-WS supports various XML parsing APIs, including DOM, JDOM, dom4j, SAX, StAX, and XOM. As an example, we will use dom4j (www.dom4j.org) to create a service endpoint. Creating an endpoint using other XML parsing APIs is very similar.

You can create a dom4j endpoint by extending the AbstractDom4jPayloadEndpoint class. The core method defined in this class that you must override is invokeInternal(). In this method, you can access the request XML element, whose type is org.dom4j.Element, and the response document, whose type is org.dom4j.Document, as method arguments. The purpose of the response document is for you to create the response element from it. Now, all you have to do in this method is handle the request message and return the response message.

Note

To create a service endpoint using dom4j with XPath, you have to add it to your classpath. If you are using Maven, add the following dependency to your project.

<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6.1</version>
 </dependency>
package com.apress.springrecipes.weather;
...
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.xpath.DefaultXPath;
import org.springframework.ws.server.endpoint.AbstractDom4jPayloadEndpoint;

public class TemperatureDom4jEndpoint extends AbstractDom4jPayloadEndpoint {

    private static final String namespaceUri =
        "http://springrecipes.apress.com/weather/schemas";

    private XPath cityPath;
    private XPath datePath;
    private DateFormat dateFormat;
    private WeatherService weatherService;

    public TemperatureDom4jEndpoint() {

        // Create the XPath objects, including the namespace
        Map<String, String> namespaceUris = new HashMap<String, String>();
        namespaceUris.put("weather", namespaceUri);
        cityPath = new DefaultXPath(
            "/weather:GetTemperaturesRequest/weather:city");
        cityPath.setNamespaceURIs(namespaceUris);
        datePath = new DefaultXPath(
            "/weather:GetTemperaturesRequest/weather:date");
        datePath.setNamespaceURIs(namespaceUris);

        dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    }
public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    protected Element invokeInternal(Element requestElement,
            Document responseDocument) throws Exception {

        // Extract the service parameters from the request message
        String city = cityPath.valueOf(requestElement);
        List<Date> dates = new ArrayList<Date>();
        for (Object node : datePath.selectNodes(requestElement)) {
            Element element = (Element) node;
            dates.add(dateFormat.parse(element.getText()));
        }

        // Invoke the back-end service to handle the request
        List<TemperatureInfo> temperatures =
            weatherService.getTemperatures(city, dates);

        // Build the response message from the result of back-end service
        Element responseElement = responseDocument.addElement(
                "GetTemperaturesResponse", namespaceUri);
        for (TemperatureInfo temperature : temperatures) {
            Element temperatureElement = responseElement.addElement(
                    "TemperatureInfo");
            temperatureElement.addAttribute("city", temperature.getCity());
            temperatureElement.addAttribute(
                    "date", dateFormat.format(temperature.getDate()));
            temperatureElement.addElement("min").setText(
                    Double.toString(temperature.getMin()));
            temperatureElement.addElement("max").setText(
                    Double.toString(temperature.getMax()));
            temperatureElement.addElement("average").setText(
                    Double.toString(temperature.getAverage()));
        }
        return responseElement;
    }
}

In the preceding invokeInternal() method, you first extract the service parameters from the request message. Here, you use XPath to help locate the elements. The XPath objects are created in the constructor so that they can be reused for subsequent request handling. Note that you must also include the namespace in the XPath expressions, or else they will not be able to locate the elements correctly.

After extracting the service parameters, you invoke the back-end service to handle the request. Because this endpoint is configured in the Spring IoC container, it can easily refer to other beans through dependency injection.

Finally, you build the response message from the back-end service's result. The dom4j library provides a rich set of APIs for you to build an XML message. Remember that you must include the default namespace in your response element.

With the service endpoint written, you can declare it in weather-servlet.xml. Because this endpoint needs the weather service bean's help to query temperatures, you have to make a reference to it.

<bean id="temperatureEndpoint"
    class="com.apress.springrecipes.weather.TemperatureDom4jEndpoint">
    <property name="weatherService" ref="weatherService" />
</bean>

Publishing the WSDL File

The last step to complete your web service is to publish the WSDL file. In Spring-WS, it's not necessary for you to write the WSDL file manually, although you may still supply a manually written WSDL file. You only declare a DynamicWsdl11Definition bean in the web application context, and then it can generate the WSDL file dynamically. MessageDispatcherServlet can also detect this bean by the WsdlDefinition interface.

<bean id="temperature"
    class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
    <property name="builder">
        <bean class="org.springframework.ws.wsdl.wsdl11.builder.
Publishing the WSDL File
XsdBasedSoap11Wsdl4jDefinitionBuilder"> <property name="schema" value="/WEB-INF/temperature.xsd" /> <property name="portTypeName" value="Weather" /> <property name="locationUri" value="http://localhost:8080/weather/services" /> </bean> </property> </bean>

The only property you must configure for this WSDL definition bean is a builder that builds the WSDL file from your XSD file. XsdBasedSoap11Wsdl4jDefinitionBuilder builds the WSDL file using the WSDL4J library. Suppose that you have put your XSD file in the WEB-INF directory—you specify this location in the schema property. This builder scans the XSD file for elements that end with the Request or Response suffix. Then, it generates WSDL operations using these elements as input and output messages, inside a WSDL port type specified by the portTypeName property.

Because you have defined <GetTemperaturesRequest> and <GetTemperaturseResponse> in your XSD file, and you have specified the port type name as Weather, the WSDL builder will generate the following WSDL port type and operation for you. The following snippet is taken from the generated WSDL file:

<wsdl:portType name="Weather">
    <wsdl:operation name="GetTemperatures">
        <wsdl:input message="schema:GetTemperaturesRequest"
            name="GetTemperaturesRequest" />
        <wsdl:output message="schema:GetTemperaturesResponse"
            name="GetTemperaturesResponse" />
    </wsdl:operation>
</wsdl:portType>

The last property, locationUri, is for you to include this web service's deployed location in the WSDL file. To allow an easy switch to a production URI, you should externalize this URI in a properties file and use Spring's PropertyPlaceholderConfigurer to read the properties from it.

Finally, you can access this WSDL file by joining its definition's bean name and the .wsdl suffix. Supposing that your service is deployed in http://localhost:8080/weather/services, this WSDL file's URL would be http://localhost:8080/weather/services/temperature.wsdl, given that the bean name of the WSDL definition is temperature.

Invoking Web Services Using Spring-WS

Problem

Given the contract of a web service, you can start creating a service client to invoke this service according to the contract. You want to use Spring-WS to create the service client.

Solution

When using Spring-WS on the client side, web services can be invoked through the core template class org.springframework.ws.client.core.WebServiceTemplate. It's very like the JdbcTemplate class and other data access templates in that it defines template methods for sending and receiving request and response messages.

How It Works

Now, let's create a Spring-WS client to invoke the weather service according to the contract it publishes. You can create a Spring-WS client by parsing the request and response XML messages. As an example, we will use dom4j to implement it. You are free to choose other XML parsing APIs for it, however.

To shield the client from the low-level invocation details, you can create a local proxy for the remote web service. This proxy also implements the WeatherService interface, and it will translate local method calls into remote web service calls.

Note

To invoke a web service using Spring-WS, you need to add Spring-WS to your classpath. If you are using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.springframework.ws</groupId>
  <artifactId>spring-oxm</artifactId>
  <version>1.5.2</version>
 </dependency>
<dependency>
  <groupId>org.springframework.ws</groupId>
  <artifactId>spring-ws-core</artifactId>
  <version>1.5.2</version>
 </dependency>

 <dependency>
  <groupId>org.springframework.ws</groupId>
  <artifactId>spring-xml</artifactId>
  <version>1.5.2</version>
 </dependency>

 <dependency>
  <groupId>org.springframework.ws</groupId>
  <artifactId>spring-oxm</artifactId>
  <version>1.5.2</version>
 </dependency>
package com.apress.springrecipes.weather;
...
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
import org.springframework.ws.client.core.WebServiceTemplate;

public class WeatherServiceProxy implements WeatherService {

    private static final String namespaceUri =
        "http://springrecipes.apress.com/weather/schemas";

    private DateFormat dateFormat;
    private WebServiceTemplate webServiceTemplate;

    public WeatherServiceProxy() throws Exception {
        dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    }

    public void setWebServiceTemplate(WebServiceTemplate webServiceTemplate) {
        this.webServiceTemplate = webServiceTemplate;
    }
public List<TemperatureInfo> getTemperatures(String city, List<Date> dates) {

        // Build the request document from the method arguments
        Document requestDocument = DocumentHelper.createDocument();
        Element requestElement = requestDocument.addElement(
                "GetTemperaturesRequest", namespaceUri);
        requestElement.addElement("city").setText(city);
        for (Date date : dates) {
            requestElement.addElement("date").setText(dateFormat.format(date));
        }

        // Invoke the remote web service
        DocumentSource source = new DocumentSource(requestDocument);
        DocumentResult result = new DocumentResult();
        webServiceTemplate.sendSourceAndReceiveToResult(source, result);

        // Extract the result from the response document
        Document responsetDocument = result.getDocument();
        Element responseElement = responsetDocument.getRootElement();
        List<TemperatureInfo> temperatures = new ArrayList<TemperatureInfo>();
        for (Object node : responseElement.elements("TemperatureInfo")) {
            Element element = (Element) node;
            try {
                Date date = dateFormat.parse(element.attributeValue("date"));
                double min = Double.parseDouble(element.elementText("min"));
                double max = Double.parseDouble(element.elementText("max"));
                double average = Double.parseDouble(
                        element.elementText("average"));
                temperatures.add(
                        new TemperatureInfo(city, date, min, max, average));
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }
        return temperatures;
    }
}

In the getTemperatures() method, you first build the request message using the dom4j API. WebServiceTemplate provides a sendSourceAndReceiveToResult() method that accepts a java.xml.transform.Source and a java.xml.transform.Result object as arguments. You have to build a dom4j DocumentSource object to wrap your request document and create a new dom4j DocumentResult object for the method to write the response document to it. Finally, you get the response message and extract the results from it.

With the service proxy written, you can declare it in a client bean configuration file such as client.xml. Because this proxy requires an instance of WebServiceTemplate for sending and receiving the messages, you have to instantiate it and inject this instance into the proxy. Also, you specify the default service URI for the template so that all the requests will be sent to this URI by default.

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

    <bean id="client"
        class="com.apress.springrecipes.weather.WeatherServiceClient">
        <property name="weatherService" ref="weatherServiceProxy" />
    </bean>

    <bean id="weatherServiceProxy"
        class="com.apress.springrecipes.weather.WeatherServiceProxy">
        <property name="webServiceTemplate" ref="webServiceTemplate" />
    </bean>

    <bean id="webServiceTemplate"
        class="org.springframework.ws.client.core.WebServiceTemplate">
        <property name="defaultUri"
            value="http://localhost:8080/weather/services" />
    </bean>
</beans>

Now, you can inject this manually written proxy into WeatherServiceClient and run it with the Client main class.

Because your DAO class can extend JdbcDaoSupport to get a precreated JdbcTemplate instance, your web service client can similarly extend the WebServiceGatewaySupport class to retrieve a WebServiceTemplate instance without explicit injection. At this point, you can comment out the webServiceTemplate variable and setter method.

package com.apress.springrecipes.weather;
...
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class WeatherServiceProxy extends WebServiceGatewaySupport
        implements WeatherService {

    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates) {
        ...
        // Invoke the remote web service
        DocumentSource source = new DocumentSource(requestDocument);
        DocumentResult result = new DocumentResult();
        getWebServiceTemplate().sendSourceAndReceiveToResult(source, result);
        ...
    }
}

However, without a WebServiceTemplate bean declared explicitly, you have to inject the default URI to the proxy directly. The setter method for this property is inherited from the WebServiceGatewaySupport class.

<beans ...>
    ...
    <bean id="weatherServiceProxy"
        class="com.apress.springrecipes.weather.WeatherServiceProxy">
        <property name="defaultUri"
            value="http://localhost:8080/weather/services" />
    </bean>
</beans>

Developing Web Services with XML Marshalling

Problem

To develop web services with the contract-first approach, you have to process request and response XML messages. If you parse the XML messages with XML parsing APIs directly, you'll have to deal with the XML elements one by one with low-level APIs, which is a cumbersome and inefficient task.

Solution

Spring-WS supports using XML marshalling technology to marshal and unmarshall objects to and from XML documents. In this way, you can deal with object properties instead of XML elements. This technology is also known as object/XML mapping (OXM), because you are actually mapping objects to and from XML documents.

To implement endpoints with an XML marshalling technology, you have to extend the AbstractMarshallingPayloadEndpoint class and configure an XML marshaller for it. Table 17-3 lists the marshallers provided by Spring-WS for different XML marshalling APIs.

Table 17.3. Marshallers for Different XML Marshalling APIs

API

Marshaller

JAXB 1.0

org.springframework.oxm.jaxb.Jaxb1Marshaller

JAXB 2.0

org.springframework.oxm.jaxb.Jaxb2Marshaller

Castor

org.springframework.oxm.castor.CastorMarshaller

XMLBeans

org.springframework.oxm.xmlbeans.XmlBeansMarshaller

JiBX

org.springframework.oxm.jibx.JibxMarshaller

XStream

org.springframework.oxm.xstream.XStreamMarshaller

To invoke a web service, WebServiceTemplate also allows you to choose an XML marshalling technology to process the request and response XML messages.

How It Works

Creating Service Endpoints with XML Marshalling

Spring-WS supports various XML marshalling APIs, including JAXB 1.0, JAXB 2.0, Castor, XMLBeans, JiBX, and XStream. As an example, I will create a service endpoint using Castor (www.castor.org) as the marshaller. Using other XML marshalling APIs is very similar.

The first step in using XML marshalling is creating the object model according to the XML message formats. This model can usually be generated by the marshalling API. For some marshalling APIs, the object model must be generated by them so that they can insert marshalling-specific information. Because Castor supports marshalling between XML messages and arbitrary Java objects, you can start creating the following classes by yourself.

package com.apress.springrecipes.weather;
...
public class GetTemperaturesRequest {

    private String city;
    private List<Date> dates;

    // Constructors, Getters and Setters
    ...
}

package com.apress.springrecipes.weather;
...
public class GetTemperaturesResponse {

    private List<TemperatureInfo> temperatures;

    // Constructors, Getters and Setters
    ...
}

With the object model created, you can write a marshalling endpoint by extending the AbstractMarshallingPayloadEndpoint class. The core method defined in this class that you must override is invokeInternal(). In this method, you can access the request object, which is unmarshalled from the request message, as the method argument. Now, all you have to do in this method is handle the request object and return the response object. Then, it will be marshalled to the response XML message.

Note

To create a service endpoint using Castor, you need to add Castor to your classpath. If you are using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.codehaus.castor</groupId>
  <artifactId>castor</artifactId>
  <version>1.2</version>
 </dependency>
package com.apress.springrecipes.weather;
...
import org.springframework.ws.server.endpoint.AbstractMarshallingPayloadEndpoint;

public class TemperatureMarshallingEndpoint extends
        AbstractMarshallingPayloadEndpoint {

    private WeatherService weatherService;

    public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    protected Object invokeInternal(Object requestObject) throws Exception {
        GetTemperaturesRequest request = (GetTemperaturesRequest) requestObject;

        List<TemperatureInfo> temperatures =
            weatherService.getTemperatures(request.getCity(), request.getDates());

        return new GetTemperaturesResponse(temperatures);
    }
}

A marshalling endpoint requires both the marshaller and unmarshaller properties to be set. Usually, you can specify a single marshaller for both properties. For Castor, you declare a CastorMarshaller bean as the marshaller.

<beans ...>
    ...
    <bean id="temperatureEndpoint"
        class="com.apress.springrecipes.weather.Temperature
Creating Service Endpoints with XML Marshalling
MarshallingEndpoint"> <property name="marshaller" ref="marshaller" /> <property name="unmarshaller" ref="marshaller" /> <property name="weatherService" ref="weatherService" /> </bean> <bean id="marshaller" class="org.springframework.oxm.castor.CastorMarshaller"> <property name="mappingLocation" value="classpath:mapping.xml" /> </bean> </beans>

Note that Castor requires a mapping configuration file to know how to map objects to and from XML documents. You can create this file in the classpath root and specify it in the mappingLocation property (e.g., mapping.xml). The following Castor mapping file defines the mappings for the GetTemperaturesRequest, GetTemperaturesResponse, and TemperatureInfo classes:

<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
    "http://castor.org/mapping.dtd">

<mapping>
    <class name="com.apress.springrecipes.weather.GetTemperaturesRequest">
        <map-to xml="GetTemperaturesRequest"
            ns-uri="http://springrecipes.apress.com/weather/schemas" />
        <field name="city" type="string">
            <bind-xml name="city" node="element" />
        </field>
        <field name="dates" collection="arraylist" type="string"
            handler="com.apress.springrecipes.weather.DateFieldHandler">
            <bind-xml name="date" node="element" />
        </field>
    </class>

    <class name="com.apress.springrecipes.weather.
Creating Service Endpoints with XML Marshalling
GetTemperaturesResponse"> <map-to xml="GetTemperaturesResponse" ns-uri="http://springrecipes.apress.com/weather/schemas" /> <field name="temperatures" collection="arraylist" type="com.apress.springrecipes.weather.TemperatureInfo"> <bind-xml name="TemperatureInfo" node="element" /> </field> </class>
<class name="com.apress.springrecipes.weather.TemperatureInfo">
        <map-to xml="TemperatureInfo"
            ns-uri="http://springrecipes.apress.com/weather/schemas" />
        <field name="city" type="string">
            <bind-xml name="city" node="attribute" />
        </field>
        <field name="date" type="string"
            handler="com.apress.springrecipes.weather.DateFieldHandler">
            <bind-xml name="date" node="attribute" />
        </field>
        <field name="min" type="double">
            <bind-xml name="min" node="element" />
        </field>
        <field name="max" type="double">
            <bind-xml name="max" node="element" />
        </field>
        <field name="average" type="double">
            <bind-xml name="average" node="element" />
        </field>
    </class>
</mapping>

Remember that for each class mapping, you must specify the namespace URI for the element. Besides, for all the date fields, you have to specify a handler to convert the dates with a particular date format. The handler is implemented as shown following:

package com.apress.springrecipes.weather;
...
import org.exolab.castor.mapping.GeneralizedFieldHandler;

public class DateFieldHandler extends GeneralizedFieldHandler {

    private DateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    public Object convertUponGet(Object value) {
        return format.format((Date) value);
    }

    public Object convertUponSet(Object value) {
        try {
            return format.parse((String) value);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    public Class getFieldType() {
        return Date.class;
    }
}

Invoking Web Services with XML Marshalling

A Spring-WS client can also marshall and unmarshall the request and response objects to and from XML messages. As an example, we will create a client using Castor as the marshaller so that you can reuse the object models GetTemperaturesRequest, GetTemperaturesResponse, and TemperatureInfo, and the mapping configuration file, mapping.xml, from the service endpoint.

Let's implement the service proxy with XML marshalling. WebServiceTemplate provides a marshalSendAndReceive() method that accepts a request object as the method argument, which will be marshalled to the request message. This method has to return a response object that will be unmarshalled from the response message.

Note

To create a service client using Castor, you need to add Castor to your classpath. If you are using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.codehaus.castor</groupId>
  <artifactId>castor</artifactId>
  <version>1.2</version>
 </dependency>
package com.apress.springrecipes.weather;
...
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class WeatherServiceProxy extends WebServiceGatewaySupport
        implements WeatherService {

    public List<TemperatureInfo> getTemperatures(String city, List<Date> dates) {
        GetTemperaturesRequest request = new GetTemperaturesRequest(city, dates);
        GetTemperaturesResponse response = (GetTemperaturesResponse)
            getWebServiceTemplate().marshalSendAndReceive(request);
        return response.getTemperatures();
    }
}

When you are using XML marshalling, WebServiceTemplate requires both the marshaller and unmarshaller properties to be set. You can also set them to WebServiceGatewaySupport if you extend this class to have WebServiceTemplate auto-created. Usually, you can specify a single marshaller for both properties. For Castor, you declare a CastorMarshaller bean as the marshaller.

<beans ...>
    <bean id="client"
        class="com.apress.springrecipes.weather.WeatherServiceClient">
        <property name="weatherService" ref="weatherServiceProxy" />
    </bean>

    <bean id="weatherServiceProxy"
        class="com.apress.springrecipes.weather.WeatherServiceProxy">
        <property name="defaultUri"
            value="http://localhost:8080/weather/services" />
        <property name="marshaller" ref="marshaller" />
        <property name="unmarshaller" ref="marshaller" />
    </bean>

    <bean id="marshaller"
        class="org.springframework.oxm.castor.CastorMarshaller">
        <property name="mappingLocation" value="classpath:mapping.xml" />
    </bean>
</beans>

Creating Service Endpoints with Annotations

Problem

By extending a Spring-WS base endpoint class, your endpoint class will be bound to the Spring-WS class hierarchy, and each endpoint class will only be able to handle one type of web service request.

Solution

Spring-WS supports annotating an arbitrary class as a service endpoint by the @Endpoint annotation, without extending a framework-specific class. You can also group multiple handler methods in an endpoint class so that it can handle multiple types of web service requests.

How It Works

For example, you can annotate your temperature endpoint with the @Endpoint annotation so that it doesn't need to extend a Spring-WS base endpoint class. The signature of the handler methods can also be more flexible.

package com.apress.springrecipes.weather;
...
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
@Endpoint
public class TemperatureMarshallingEndpoint {

    private static final String namespaceUri =
        "http://springrecipes.apress.com/weather/schemas";

    private WeatherService weatherService;

    public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    @PayloadRoot(
            localPart = "GetTemperaturesRequest",
            namespace = namespaceUri)
    public GetTemperaturesResponse getTemperature(GetTemperaturesRequest request) {
        List<TemperatureInfo> temperatures =
            weatherService.getTemperatures(request.getCity(), request.getDates());
        return new GetTemperaturesResponse(temperatures);
    }
}

Besides the @Endpoint annotation, you have to annotate each handler method with the @PayloadRoot annotation for mapping a service request. In this annotation, you specify the local name (localPort) and namespace of the payload root element to be handled. Then you just declare a PayloadRootAnnotationMethodEndpointMapping bean, and it will be able to detect the mapping from the @PayloadRoot annotation automatically.

<beans ...>
    ...
    <bean class="org.springframework.ws.server.endpoint.
How It Works
mapping.PayloadRootAnnotationMethodEndpointMapping" /> <bean id="temperatureEndpoint" class="com.apress.springrecipes.weather.Temperature
How It Works
MarshallingEndpoint"> <property name="weatherService" ref="weatherService" /> </bean> <bean class="org.springframework.ws.server.endpoint.adapter.
How It Works
GenericMarshallingMethodEndpointAdapter"> <property name="marshaller" ref="marshaller" /> <property name="unmarshaller" ref="marshaller" /> </bean> <bean id="marshaller" class="org.springframework.oxm.castor.CastorMarshaller"> <property name="mappingLocation" value="classpath:mapping.xml" /> </bean> </beans>

Because your endpoint class no longer extends a base endpoint class, it doesn't inherit the capabilities of marshalling and unmarshalling XML messages. You have to configure a GenericMarshallingMethodEndpointAdapter to do so.

Summary

This chapter discussed how to use Spring's remoting support. You learned how to both publish and consume an RMI service. You learned about Spring's support for legacy EJB 2.x services, as well as the support available for both EJB 3.0 and EJB 3.1 services. We also discussed building web services. We introduced using CXF, a third-party framework for building services. We used then used Spring-WS, Spring's excellent support for web service creation, to build the schema and then the web service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset