Tuesday, May 18, 2010

Intercepting method calls of @WebService annotated classes

I ran into this issue recently and I thought sharing this with everyone will help in saving some time.

I am making use of Metro + Spring for my web service implementation. I have a requirement that I should intercept all the calls in the web service implementation (the class that has is annotated with @WebService) and log the time taken to execute each call.

So I created the following AOP advice:

public class WebServiceMethodsAspect {
    @Around("execution(* com.mydomain.service.*Impl.*(..)) && args(request)")       
    public Object interceptWebServiceCall(ProceedingJoinPoint pjp,
            Object request) throws Throwable {

Once created the aspect I enabled the annotation based aspects using the following tag in my applicationContext.xml file:

< bean id="webServiceMethodsAspect" class="com.mydomain.aspects.WebServiceMethodsAspect"/>

I was expecting everything to be working fine as soon as I restarted my app. But I got the following exception (I folded the lines for clarity):
Error creating bean with name 'MyServiceBinding' defined in ServletContext resource [/WEB-INF/applicationContext.xml]:
Cannot create inner bean '(inner bean)' of type [org.jvnet.jax_ws_commons.spring.SpringService] while setting bean property 'service';
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)':
FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException:
class $Proxy13 has neither @WebService nor @WebServiceProvider annotation

If you carefully look at it, the problem is due to the fact that I am intercepting calls directly in the web service implementation. When I provided the Aspect to intercept the calls in the web service implementation, Spring automatically created a proxy for my web service implementation. The @WebService annotation does not have the @Inherited meta-annotation on it. Hence the proxy doesn't have the @WebService annotation on it. Hence the object that was given to the binding didn't have the @WebService annotation on it.

To solve this problem, I introduced a delegator layer on top of the web service implementation layer and annotated the delegator with the @WebService. Delegator simply calls the underlying web service implementation (it just delegates the calls to the respective web service implementation). Now I can intercept the calls to the web service implementation, as only the delegator is used by in the binding.

If you need access to the WebServiceContext, you must explicitly pass that from the delegator to the web service implementation class.


Anonymous said...

We had this issue as well and solved it by adding the "impl" attribute to the service element in the spring config:

Anonymous said...

Per the previous comment:

<bean id="myWebservice" class="com.abc.xyz.MyWebService"/>

<wss:binding url="/path/to/myService">
<ws:service bean="#myWebservice" impl="com.abc.xyz.MyWebService"/>

Anonymous said...

Unfortunately the "impl" attribute doesn't work if you are using an interface for your webservice and an implementing class because when using JDK proxies the proxy only implements the interface and then you will get an error.