Tuesday, April 1, 2008

using Spring in an Axis Web Service Impl

This is a re-post from another blog of mine:

I have an existing code base using Axis for web services, and am working on integrating Spring (2.0) into the system, for transaction management.

This is not straightforward, because Spring likes to be the one that creates your objects, so that it can inject dependencies. But with web services, Axis creates the service implementation class, not the Spring container. A 'hack' is necessary as an alternative to the standard Spring injection. And while it is not straightforward, it's not difficult, either. It just seemed that way to me because all the examples I found on the internet were confusing.

Here's the short version of how to get things working:

  1. If you are generating a Skeleton class with wsdl2java (-skeletonDeploy), stop doing it.
  2. Create a wrapper class for your service impl. For me this meant renaming my MyServiceSoapBindingImpl class to MyServiceImpl, and then regenerating MyServiceSoapBindingImpl. MyServiceSoapBindingImpl is now the wrapper class, and you have it contain a MyServiceImpl object and delegate all calls to that object.
  3. Change the wrapper class to extend ServletEndpointSupport, and make sure that your wsdd points to that wrapper class.
  4. Override ServletEndpointSupport.onInit(), and get the real impl (MyServiceImpl) as a bean using getWebApplicationContext().getBean("myServiceImplBean");

Now your impl class can use Spring for dependency injection just like any other class.

Here's the long version:

30,000 feet

What we want is to have a service interface (MyService), a service impl (MyServiceImpl) with the actual code, and be able to inject dependencies into MyServiceImpl (dao, etc.).

public interface MyService
{
public int doSomething();
}

public class MyServiceImpl implements MyService
{
private HelperBean myHelperObj;

public void setMyHelperObj(HelperBean myHelperObj)
{
this.myHelperObj = myHelperObj;
}

public int doSomething()
{
return myHelperObj.doTheSomething();
}
}

We want to be able to set this up in Spring:


<bean id="helperObj" class="HelperBean"></bean>

<bean id="myService" class="MyServiceImpl">
<property name="myHelperObj" ref="helperObj"/>
</bean>


And then set that service class up as a web service. This means that your wsdd would have something like this:


<service name="MyService" provider="java:RPC"></service>
...
<parameter name="className" value="MyServiceImpl"/>
...
</service>

The problem is that since MyServiceImpl is your web service class, Axis is in charge of it, not Spring. Since Spring does not create the class, it can't inject the HelperObj!

So let's see what the workaround is. First, I need to explain how I have things set up.

Axis/service class setup

I generate the wsdl from the service interface using the ant java2wsdl task, and then generate the stub, locator, etc. classes from the wsdl using the ant wsdl2java task. Because of this, my service impl class is actually named MyServiceSoapBindingImpl, just because that's what the ant task generates. The classes generated are:
  • MyServiceSoapBindingStub
  • MyServiceService
  • MyServiceServiceLocator
  • MyServiceSoapBindingImpl
Originally we were also generating a Skeleton class for the service (skeletonDeploy="yes"). This ended up causing me headaches, and I'm not really sure what the purpose of the Skeleton class is, anyway. Bottom line: don't do it.

To get around Spring not being able to inject dependencies into your service impl, it's necessary to make your service impl class (the one that your deploy.wsdd points to) a wrapper around the real impl class that you want to inject dependencies into. The wrapper class extends Spring's ServletEndpointSupport class, which provides an onInit method that you can override to get access to the spring application context:

public class MyServiceImpl implements MyService
{
private HelperBean myHelperObj;

public void setMyHelperObj(HelperBean myHelperObj)
{
this.myHelperObj = myHelperObj;
}

public int doSomething()
{
return myHelperObj.doTheSomething();
}
}

public class MyServiceSoapBindingImpl extends ServletEndpointSupport implements MyService
{
private MyService impl;

protected void onInit() throws ServiceException
{
impl = (MyService) getWebApplicationContext().getBean("myService");
}

public int doSomething()
{
return impl.doSomething();
}
}


So the 'hack' is that you have to use ServletEndpointSupport and access the Spring context directly, but from that point on, your real impl class can behave like any other Spring bean.

The part that messed me up was the Skeleton class. Since the wsdd pointed to the Skeleton class instead of the Impl, Spring for some reason didn't call my onInit() callback method. Hopefully this helps someone else having the same problem.

web.xml

Your web.xml doesn't need to have anything special. Just the normal configation. I got confused because all the examples I found on the web made it seem that you had to register a DispatcherServlet, which is not necessary just for what we are trying to do here.

No comments:

Post a Comment