Axis2 Integration With The Spring Framework

This document is a guide on how to use Axis2 with the Spring Framework

Content

Introduction

The idea behind Axis2 and Spring integration is that Axis2 simply needs to have Spring supply in one of its pre-loaded beans to the Axis2 Message Receiver defined in the AAR services.xml . While Axis2 typically uses reflection to instantiate the ServiceClass defined in the services.xml that the Message Receiver will use, alternatively one can define a ServiceObjectSupplier that will supply the Object.

This guide will show how to use two separate ServiceObjectSupplier classes that are part of the Axis2 standard distribution: One for use with a servlet container, and one without. Once configured, the web service itself acts like any other Spring wired bean. These Spring beans can be loaded any way desired, as Axis2 has no configuration file dependencies from Spring. Spring versions 1.2.6 and 1.2.8 have been tested, but probably any version would work as only core functionality is required.

This guide assumes some basic knowledge of Axis2. See the User's Guide for more information.

Configuring Axis2 to be Spring Aware

Programming Model

From an Axis2 standpoint, two hooks are needed to be placed into the AAR services.xml: The ServiceObjectSupplier that hooks Axis2 and Spring together, and the name of Spring bean that Axis2 will use as the service. All Message Receivers are currently supported, as would be any Message Receiver that extends org.apache.axis2.receivers.AbstractMessageReceiver .

Simple Spring Config Example

For the purpose of this example, and for no other reason besides simplicity, we'll configure Spring via a WAR file's web.xml. Lets add a context-param and a listener:

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

Next we will show two examples of Spring's /WEB-INF/applicationContext.xml referenced in the web.xml listener - one with a servlet container, and one without.

Inside a Servlet Container

This 'inside a servlet container' example applicationContext.xml should be familiar to any Spring user:

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <!-- Axis2 Web Service, but to Spring, its just another bean that has dependencies -->
  <bean id="springAwareService" class="spring.SpringAwareService">
    <property name="myBean" ref="myBean"/>
  </bean>

  <!-- just another bean / interface with a wired implementation, that's injected by Spring
          into the Web Service -->
   <bean id="myBean" class="spring.MyBeanImpl">
     <property name="val" value="Spring, emerge thyself" />
  </bean>
</beans>

If the service is running in a Servlet Container, i.e., Axis2 will be able to get a hold of ServletContext, the services.xml for the example would be using SpringServletContextObjectSupplier such as:

 <service name="SpringAwareService">
    <description>
        simple spring example
    </description>
    <parameter name="ServiceObjectSupplier" locked="false">org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier</parameter>
    <parameter name="SpringBeanName" locked="false">springAwareService</parameter>
    <operation name="getValue">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
    </operation>
</service> 

While the above example uses RawXMLINOutMessageReceiver as its messageReceiver class, all Message Receivers are currently supported, as would be any Message Receiver that extends org.apache.axis2.receivers.AbstractMessageReceiver .

Outside a Servlet Container

In the case Axis2 can't get a ServletContext, you have the option of defining a bean that takes advantage of Spring's internal abilities (ApplicationContextAware interface, specifically) to provide an Application Context to Axis2, with a bean ref 'applicationContext' :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <!-- Configure spring to give a hook to axis2 without a ServletContext -->
  <bean id="applicationContext" 
    class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" />

  <!-- Axis2 Web Service, but to Spring, its just another bean that has dependencies -->
  <bean id="springAwareService"
        class="spring.SpringAwareService">
    <property name="myBean" ref="myBean" />
  </bean>

  <!-- just another bean with a wired implementation, that's injected by Spring 
          into the Web Service -->
   <bean id="myBean"
        class="spring.MyBeanImpl">
     <property name="val" value="Spring, emerge thyself" />
  </bean>
</beans>

If the service is _NOT_ running in a Servlet Container, i.e., Axis2 will _NOT_ be able to get a hold of ServletContext, the services.xml for the example would be using SpringAppContextAwareObjectSupplier such as:

 <service name="SpringAwareService">
    <description>
        simple spring example
    </description>
    <parameter name="ServiceObjectSupplier" locked="false">org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier</parameter>
    <parameter name="SpringBeanName" locked="false">springAwareService</parameter>
    <operation name="getValue">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
    </operation>
</service> 

While the above example uses RawXMLINOutMessageReceiver as its messageReceiver class, all Message Receivers are currently supported, as would be any Message Receiver that extends org.apache.axis2.receivers.AbstractMessageReceiver .

In a non-servlet container environment, one way you could load the applicationContext.xml file is in a place that will be run once, upon start-up, execute:

import org.springframework.context.support.ClassPathXmlApplicationContext;

 public void createSpringAppCtx(ClassLoader cl)
            throws Exception {

    ClassPathXmlApplicationContext ctx = new
      ClassPathXmlApplicationContext(new String[] {Constants.MY_PATH +
      "spring/applicationContext.xml"}, false);
           ctx.setClassLoader(cl);
           ctx.refresh();
}

Putting It All Together

From here, its just standard Axis2 coding, only now the service has Spring wiring capabilities. The implementation is the same whether using either SpringServletContextObjectSupplier or SpringAppContextAwareObjectSupplier. The service is below:

package spring;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMText;

public class SpringAwareService {

    private MyBean myBean = null;

    //spring 'injects' this implementation
    public void setMyBean(MyBean myBean) {
            this.myBean = myBean;
    }

    // The web service
    public OMElement getValue(OMElement ignore) {
            OMFactory factory=
                OMAbstractFactory.getOMFactory();
            OMNamespace payloadNs= factory.createOMNamespace(
                "http://springExample.org/example1", "example1");
            OMElement payload =
                factory.createOMElement("string", payloadNs);
            OMText response = factory.createOMText(this.myBean.emerge());
            payload.addChild(response);
            return payload;
    }
} 

For those new to Spring, one of the ideas is that you program to an Interface, and the implementation is pluggable. This idea is referenced in the Spring config file above. Below is the interface:

package spring;

/** Interface for Spring aware Bean */
public interface MyBean {
         String emerge();
}

Here's the implementation:

/** Spring wired implementation */
public class MyBeanImpl implements MyBean {

    String str = null;
    // spring 'injects' this value
    public void setVal(String s) {
        str = s;
    }
    // web service gets this value
    public String emerge() {
        return str;
    }
}

Lastly here's the client - not really necessary for the example, other than for completeness:

package client;

import java.io.StringWriter;

import javax.xml.stream.XMLOutputFactory;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;

public class TestClient {
          /** Access point inside the servlet container. **/
    private static EndpointReference targetEPR =
        new EndpointReference(
                "http://localhost:8080/axis2/services/springExample");

    /**
     * Simple axis2 client.
     *
     * @param args Main
     */
    public static void main(String[] args) {
        try {
            OMFactory factory = OMAbstractFactory.getOMFactory();
            OMNamespace omNs = factory.createOMNamespace(
                        "http://springExample.org/example1", "example1");

            OMElement method = factory.createOMElement("getValue", omNs);
            OMElement value = factory.createOMElement("Text", omNs);
            value.addChild(factory.createOMText(value, "Some String "));
            method.addChild(value);

            ServiceClient serviceClient = new ServiceClient();

            Options options = new Options();
            serviceClient.setOptions(options);
            options.setTo(targetEPR);

            //Blocking invocation
            OMElement result = serviceClient.sendReceive(method);

            StringWriter writer = new StringWriter();
            result.serialize(XMLOutputFactory.newInstance()
                    .createXMLStreamWriter(writer));
            writer.flush();

            System.out.println("Response: " + writer.toString());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
} 

To run this example, make sure you have the axis2-spring*.jar that comes from the axis2-std-*-bin distro in the server side WEB-INF/lib, as well as the appropriate Spring jar - most will use the full spring.jar, but the minimum requirements are spring-core, spring-beans, spring-context and spring-web. Running the client, you should see this output:

Response: <example1:string xmlns:example1="http://springExample.org/example1" 
  xmlns:tns="http://ws.apache.org/axis2">Spring, emerge thyself</example1:string>

Spring Inside an AAR

Frequently Axis2 users wish to run Spring inside the AAR. Here we show you how. There are four points to be aware of here:

(A) You need to configure Spring to use the Axis2 Service Classloader. See the Known issues running Spring inside the AAR area.

(B) Its up to you to load Spring, though we give an example below.

(C) For reasons such as classloader isolation the SpringAppContextAwareObjectSupplier is the best choice.

(D) The springframework jar and axis2-spring-*.jar will be placed inside the AAR under the lib directory.

./springExample.aar
./META-INF
./META-INF/MANIFEST.MF
./META-INF/services.xml
./applicationContext.xml
./lib
./lib/axis2-spring-SNAPSHOT.jar
./lib/spring.jar
./spring
./spring/MyBean.class
./spring/MyBeanImpl.class
./spring/SpringAwareService.class
./spring/SpringInit.class 

One way to initialize Spring is to use the org.apache.axis2.engine.Service.

IMPORTANT: this interface at the time of this writing is being discussed for refactoring. We'll update the Spring documents as soon as possible. Be aware that this interface name perhaps will change, though the functionality will probably be in place, somewhere. The important part for us is the startUp() method and setting Spring to use the Service classloader:

package spring;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.engine.Service;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.description.AxisService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SpringInit implements Service {
        
    private static Log logger = LogFactory
        .getLog(SpringInit .class);

    // The web service
    public OMElement springInit(OMElement ignore) {

        return null;
    }

    public void init(ServiceContext serviceContext) {
        
    }

    public void setOperationContext(OperationContext arg0) {

    }

    public void destroy(ServiceContext arg0) {

    }

     /**
     * this will be called during the deployement time of the service. irrespective
     * of the service scope this method will be called
     */
    public void startUp(ConfigurationContext ignore, AxisService service) {
 
        ClassLoader classLoader = service.getClassLoader();
        ClassPathXmlApplicationContext appCtx = new
        ClassPathXmlApplicationContext(new String[] {"applicationContext.xml"}, false);
            appCtx.setClassLoader(classLoader);
            appCtx.refresh();
        if (logger.isDebugEnabled()) {
            logger.debug("\n\nstartUp() set spring classloader via axisService.getClassLoader() ... ");
        }
    }
}

Here's the services.xml that now includes SpringInit and the needed load-on-startup parameter. There is also the forceTCCL parameter which is needed when loading Spring in the AAR - see the Known issues running Spring inside the AAR area.

<serviceGroup>
  <service name="SpringInit">
    <description>
        This is a spring sample Web Service with two operations.
    </description>
    <parameter name="ServiceClass" locked="false">spring.SpringInit</parameter>
    <parameter name="forceTCCL" locked="false">true</parameter>
    <parameter name="load-on-startup" locked="false">true</parameter>
    <operation name="springInit">
        <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
    </operation>
  </service>
  <service name="SpringAwareService">
     <description>
         simple spring example
     </description>
     <parameter name="ServiceObjectSupplier" locked="false">org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier</parameter>
     <parameter name="SpringBeanName" locked="false">springAwareService</parameter>
     <operation name="getValue">
         <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
     </operation>
  </service>
</serviceGroup>

The Axis2 classloader strategy by default does not permit Spring to run inside the AAR. To allow Spring to run inside the AAR, the forceTCCL parameter is used in the services.xml as shown in the example above. The behavior of forceTCCL was the default in the developement cycle in between 1.0 and 1.1, but it resulted in the JIRA issue AXIS2-1214 - essentially problems with getting an initContext.lookup() handle inside the AAR. Spring users typically have little desire to use initContext.lookup() however, as they get their Datasources via org.springframework.jdbc.datasource.DriverManagerDataSource in an xml file or with annotations. For ejb home references and the like, Spring provides JndiObjectFactoryBean. While fully testing JndiObjectFactoryBean with ejb has not been done yet - if you do, please send a message to the axis users list - Datasources via Spring inside the AAR have been tested. Basically it works as typically done with Spring, though if you are passing Hibernate XML files you need to put them in a place where Spring will find them. The most flexible way is as follows, using logging in DEBUG mode to see where Spring will look in your jar / class locations:

    <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
                <property name="mappingLocations">
                   <value>classpath*:**/Asset.hbm.xml</value>
                </property>
                ...
    </bean>