Recently, Jieba Wu posted an interesting bug report on the Spring JIRA detailing a problem he was having when proxying Spring MVC Controllers using CGLIB proxies. Essentially, he would create his target Controller object, pass in some dependency using Spring IoC and then proxy the Controller. At runtime, all invocations were sent directly to the proxy and not to the target. As a result he was seeing NullPointerExceptions because state from the target is not copied to the proxy when creating a class proxy.
After many back and forth exchanges we managed to determine that Jieba was actually extending Spring’s AbstractCommandController which declares the handleRequest() method as final. This causes an issue when CGLIB proxies, because CGLIB cannot override that method (just like any Java code) and it will not be proxied. The result at runtime is that, although we have a proxy object, calls to the final methods are not intercepted and redirected through the advice chain to the target, instead they are sent directly to the proxy instance which is not configured with the correct state.
So what does this mean in real terms? Well, consider the class below:
public class MyBean {
private String name;
public final String getName() {
return name;
}
public final void setName(String name) {
this.name = name;
}
}
Here you can see a simple bean with a single private field and two public final methods. Now consider this unit test:
public void testProxyClassWithFinalMethods() throws Exception {
MyBean bean = new MyBean();
bean.setName("Rob Harrop");
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvice(new NopInterceptor());
proxyFactory.setTarget(bean);
proxyFactory.setProxyTargetClass(true);
MyBean proxy = (MyBean)proxyFactory.getProxy();
// name on target should still be not null
assertNotNull(bean.getName());
// getName() is final so cant intercept - method call goes directly to
// the proxy and will not have access to a valid name field
assertNull(proxy.getName());
}
What we are going to see here is that we first create the target object which is an instance of MyBean, then we create a proxy for the target adding in a NopInterceptor. When CGLIB comes to create the proxy it will take all non-final methods and give Spring a chance to define how that method should be proxied. Note the disclaimer from that sentence - only non-final methods are proxied. Of course, if you think about it, since CGLIB is creating a dynamic subclass of your target class, then there is no real way around this.
At runtime what will happen when you invoke one of the final methods on the proxy is that the invocation will not be intercepted and, instead, it will be sent directly to the proxy object. This essentially presents two problems: firstly, your advice chain won’t be invoked, potentially causing havoc in your application, and secondly the invocation may die when it try to access state that will not be initialized in the proxy.
The moral of this story is that you need to be extra careful when creating your Spring AOP proxies using CGLIB because things like final methods will cause problems. Interestingly, in the case cited in the bug report, the final method in question was actually defined on the Controller interface and as such this kind of proxy will work perfectly fine when using JDK proxies in Spring.
My recommendation? Use JDK proxies unless you have real issues with performance or you are dealing with legacy code that doesn’t implement the interfaces you need for JDK proxies.