Friday, August 1, 2008

Declarative Spring AOP and Interfaces

I ran into what seems to be a somewhat frequent user error adding Spring AOP through a container when trying to add advice to a class that implements an interface. The exception:
PropertyAccessException 1: org.springframework.beans.TypeMismatchException:
Failed to convert property value of type [$Proxy0] to required type [UpperCaser] for property
'uppercaser'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of
type [$Proxy0] to required type [UpperCaser] for property 'uppercaser':
no matching editors or conversion strategy found" not found in container
Since I ran into the problem and saw a fair share of related posts on the Spring Forums, I thought I'd document it. In my test project, I have a simple class that converts a String to all upper-case letters. Before implementing an interface, everything worked fine.
public class UpperCaser {
public String convert(String toConvert) {
return toConvert.toUpperCase();
}
}
My entry class:
public class UpperCaserUMO  { 
UpperCaser uppercaser;
public String doit(String payload) {
String result = uppercaser.convert(payload);
return result;
}

// needed so Spring can inject uppercaser, can't say:
// UpperCaser caser = new UpperCaser();
public void setUppercaser(UpperCaser uppercaser) {
this.uppercaser = uppercaser;
}
}
And my advice the reverse the upper-cased Sting:
public class UpperCaserAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Object response = invocation.proceed();
String reversedResponse = new StringBuffer((String)response).reverse().toString();
return reversedResponse;
}
}
Everything worked fine with this configuration (my container is Mule 1.4.3):
<bean id="targetUpperCaser" class="UpperCaser" singleton="false"/>
<bean id="upperCaserAdvice" class="UpperCaserAdvice" singleton="false"/>

<bean id="upperCaserUMO" class="UpperCaserUMO" singleton="false">
<spring-property name="uppercaser">
<ref local="upperCaserProxy"/>
</spring-property>
</bean>

<bean id="upperCaserProxy"
class="org.springframework.aop.framework.ProxyFactoryBean"
singleton="false">
<spring-property name="target">
<ref local="targetUpperCaser"/>
</spring-property>
<spring-property name="interceptorNames">
<spring-list>
<value>upperCaserAdvisor</value>
</spring-list>
</spring-property>
</bean>

<bean id="upperCaserAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"
singleton="false">
<spring-property name="advice">
<ref local="upperCaserAdvice"/>
</spring-property>
<spring-property name="mappedNames">
<spring-list>
<value>convert</value>
</spring-list>
</spring-property>
</bean>
In my "at work" project, the target class, UpperCaser here, implemented an interface, so I added an interface to UpperCaser:
public interface Converter {
public String convert(String toConvert);
}

public class UpperCaser implements Converter {
...
}
I resolved the exception above by changing my entry class to have members of the interface and not the implementation:
public class UpperCaserUMO  { 
Converter uppercaser;
...
public void setUppercaser(Converter uppercaser) {
this.uppercaser = uppercaser;
}
}
And changing my ProxyFactoryBean to acknowledge the interface with the proxyInterfaces property to set things up right took care of the exception:
<bean id="upperCaserProxy" 
class="org.springframework.aop.framework.ProxyFactoryBean"
singleton="false">
<!-- added after adding Converter interface -->
<spring-property name="proxyInterfaces">
<spring-list>
<value>Converter</value>
</spring-list>
</spring-property>
<spring-property name="target">
<ref local="targetUpperCaser"/>
</spring-property>
<spring-property name="interceptorNames">
<spring-list>
<value>upperCaserAdvisor</value>
</spring-list>
</spring-property>
</bean>
And all was well. I based my test project from Mule's Echo example. I also added logging statments which I omitted above so I could track what's going on.
INFO  2008-08-01 12:55:20,063 [WrapperSimpleAppMain] org.mule.MuleServer: Mule Server initialized.
Please enter something:
hello

INFO 2008-08-01 12:55:26,568 [UpperCaserUMO.2] UpperCaserUMO: Inside method doit().
INFO 2008-08-01 12:55:26,575 [UpperCaserUMO.2] UpperCaserAdvice: About to invoke method: convert
INFO 2008-08-01 12:55:26,575 [UpperCaserUMO.2] UpperCaser: Received: hello
INFO 2008-08-01 12:55:26,575 [UpperCaserUMO.2] UpperCaser: Result: HELLO
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserAdvice: Method 'convert' complete.
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserAdvice: Method response: HELLO
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserAdvice: Reversing response...
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserAdvice: Reversed response: OLLEH
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserAdvice: Returning reversed response...
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserUMO: Conversion result: OLLEH
INFO 2008-08-01 12:55:26,576 [UpperCaserUMO.2] UpperCaserUMO: Returning result.
INFO 2008-08-01 12:55:26,593 [SystemStreamConnector.dispatcher.1] org.mule.providers.stream.StreamMessageDispatcher: Connected: StreamMessageDispatcher{this=14fcd9a, endpoint=stream://System.out}

OLLEH
I entered a string in the consloe, UpperCaser upper-cased it, and the advice class reversed it.
Seems a whole lot less complicated to me now. Hopefully this helps someone out in a similar situation.

Pro Spring 2.5
This example was with version 2.0.6 of Spring that comes with Mule 1.4.3. I'm going to assume it works with 2.5.

1 comment: