Thursday, September 24, 2009

Spring AOP : Avoide JDK proxies for struts application

Spring AOP uses both JDK proxies and cglib for creating dynamic proxies. The main purpose of proxy classes is to intercept method invocation on the target object and the execute the code from applicable advice. If the class implements an interface, then spring uses JDK proxies by default, and for all other classes it uses cglib to generate proxies.

Proxy classes generated by JDK proxy provides implementation for all methods in all the interfaces that are implemented by target class. So, generated class will not have methods that are defined by target class but are not part of any interface. All the implementations of the interface can use the same generated class which results in less number of dynamically generated proxy classes.
On the other hand, proxies generated by cglib extends the target class. So the generated proxy has all the methods defined by the class. This results in more number of proxy classes as every class, even different implementation of the same interface, will have their own dynamically generated proxy. However, the generated proxy classes are cached, so new classes are not created on every invocation.

In a web application which uses Struts, its not possible to use JDK proxies for Spring AOP. Consider the following scenario.

I have an action class -

package com.amey.labs.web.action;
//imports

public class HomeAction implements ModelDriven {

private String name;
private PersonService personService;
private HomePage homePage = new HomePage();

public HomeAction() {
}

public HomeAction(PersonService personService) {
this.personService = personService;
}

@Action(value = "/home", results = {@Result(name = SUCCESS, location="home.jsp")})
public String execute() {
homePage.setPerson(personService.getDetails(name));
return SUCCESS;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public HomePage getModel() {
return homePage;
}
}

I have configured an around advice for this action to log the entry and exit of the execute method.

<aop:config>
<aop:aspect ref="loggerAspect">
<aop:around method="doLogging" pointcut="execution(public * com.amey.labs.web.action..*.execute(..))">
</aop:around>
</aop:aspect>

HomeAction implements ModelDriven interface. So Spring will use JDK proxies to create a proxy class. The created proxy class will implement all the methods from ModelDriven interface. However, it won't have execute() method as it is not defined in ModelDriven interface. If I try to execute this action, I get NoSuchMethodException exception in AnnotationValidationInterceptor. This interceptor gets the method name which has @action annotation and then get the method object with the same name from action's Class object. However, the Class object is a object of proxy class generated by JDK proxy instead of the actual action class. So it does not have execute() method.

To fix this issue, I have to implement the com.opensymphony.xwork2.Action interface (or extend the ActionSupport). Once i do this, the created proxy class implements Action interface and has the execute() method.

Now, when I invoke the action from the UI, it can execute the execute() in the action. However, the generated proxy class doesn't have the methods for setting individual attributes in the action class (name in HomePageAction). So there is no way that the ParameterInterceptor finds out that this name-value passed in the request (either GET or POST) is actually a attribute on action and it just ignores that. CompoundRootAccessor class, which is invoked from ParameterInterceptor generates the warning message that the setter for "name" is not present in Action class or the model class and it is logged if the struts is running in dev mode, otherwise the parameters is considered as junk and it is ignored.

So the action class can not have any attribute which should be set from the UI. I need to move the name attribute from action to the Model class. If attribute is part of model, then it gets populated properly and I can use it in my action. However, attribute can not always be a logical part of the model object.

To force Spring to use cglib proxies, we just have to set proxy-target-class attribute to true.

<aop:config class="true">
// aop aspects
</aop:config>

In the struts actions where we have a property that we want to set from the UI, JDK proxies can not be used. So we have to use proxies generated by cglib. Even in other cases, cglib proxies performs better than JDK proxies. With JDK proxies, proxy class intercepts all the method invocations on the target object. All methods on target object are invoked by reflection, even if they are not advised. With cglib, proxy classes extends the target class and intercepts only advised methods. All other methods are invoked directly. So, using cglib proxies is considerably faster. Obviously, cglib has its own set of problems, but its a better alternative than JDK proxies.

1 comment:

Anonymous said...
This comment has been removed by a blog administrator.