August 31, 2005

A proxy warning with Spring

When you're using a Spring proxy or proxy factory (like TransactionProxyFactoryBean) you're getting back by default a JDK 1.4 dynamic proxy. Unfortunately these only work on interfaces, and if you want the proxy to also reflect on class methods you need to (a) explicitly say so and (b) ensure you have CGLIB (and ASM) available.

For instance, say you have an interface:

package eg;
public interface Command {
    public void execute();
}

and an implementation:

package eg;
public class MyCommand implements Command {
    public void execute() { ... }
    public void setOperator( String operator ) { ... }
}

If you create a Spring bean and transactional proxy around it like:

<bean id="myCommandTarget"
    class="eg.MyCommand" />
 
<bean id="myCommand"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="target" ref="myCommandTarget" />
    ...
</bean>

And then try to dynamically assign the 'operator' property:

    try {
        Command cmd = (Command)ctx.getBean( "myCommand" );
        BeanWrapper bw = new BeanWrapperImpl( cmd );
        bw.setPropertyValue( "operator", "chucky" );
    }
    catch ( BeansException e ) {
        System.out.println( "Caught BeansException: " + e.getMessage() );
    }

You'll get an error like:

   Caught BeansException: Invalid property 'operator' of bean class 
   [$Proxy0]: Bean property 'operator' is not writable or has an 
   invalid setter method: Does the parameter type of the setter match 
   the return type of the getter?

The problem is that the generated proxy only has the method execute() since that's the only method in the interface Command. To fix this you need to explicitly proxy the class -- just set the proxyTargetClass property to true:

<bean id="myCommand"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="proxyTargetClass" value="true" />
    <property name="target" ref="myCommandTarget" />
    ...
</bean>

This will tell Spring to proxy the class methods as well and, like I mentioned, it requires CGLIB. If you don't have CGLIB in your classpath when you try to use this you'll see an exception like:

   Exception in thread "main" org.springframework.beans.factory.BeanCreationException: 
   Error creating bean with name 'foo' defined in class path 
   resource [my_ctx.xml]: Initialization of bean failed; nested 
   exception is org.springframework.aop.framework.AopConfigException: 
   Cannot proxy target class because CGLIB2 is not available. Add 
   CGLIB to the class path or specify proxy interfaces....

It's easy to tell if your proxies are actually using CGLIB -- instead of the class name being something like $Proxy3, $Proxy4 and so on, you'll see eg.MyCommand$$EnhancerByCGLIB$$14e427bf.

Next: 'Everlasting gobstopper'
Previous: On the west coast for a few days