I have a struts 2 action class:
public class MyAction{
private ArrayList<User> users;
public void setUsers(ArrayList<User> users){
this.users = users;
}
public String doMyAction(){
//...
}
}
the doMyAction method has a AOP pointcut, so MyAction
is actually a cglib proxied class at runtime, and the users
field will be populated by json data from client, when aop is enabled, struts JSONInterceptor
will fail to populate json data into users
field. I debuged with the source code of struts json plugin and found this in org.apache.struts2.json.JSONPopulator:
public void populateObject(Object object, final Map elements)
throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException, IntrospectionException,
IllegalArgumentException, JSONException, InstantiationException {
Class clazz = object.getClass();
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
// iterate over class fields
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
if (elements.containsKey(name)) {
Object value = elements.get(name);
Method method = prop.getWriteMethod();
if (method != null) {
JSON json = method.getAnnotation(JSON.class);
if ((json != null) && !json.deserialize()) {
continue;
}
// use only public setters
if (Modifier.isPublic(method.getModifiers())) {
Class[] paramTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
if (paramTypes.length == 1) {
Object convertedValue = this.convert(paramTypes[0],
genericTypes[0], value, method);
method.invoke(object, new Object[] { convertedValue });
}
}
}
}
}
}
and on this line:
Type[] genericTypes = method.getGenericParameterTypes();
when AOP is enabled, it returns java.util.ArrayList
against the setter method of users
field. but java.util.ArrayList<User>
expected.
It seems that my action class lose it's generic info when proxied by cglib. I also found a old bug about this.
I can exclude my method from aop configurations to fix this. but I still want to know if there is a better solution?
My idea is try to find the actual type behind the proxy. According to spring documentation, any proxy obtained from spring aop implements the org.springframework.aop.framework.Advised
interface, and this interface expose method to query the target class.
Any AOP proxy obtained from Spring can be cast to this interface to allow manipulation of its AOP advice.
so here we have a considerable option, we can download the struts json plugin source code and build our own one, with modification on populateObject
method of JSONPopulator
public void populateObject(Object object, final Map elements) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException, IntrospectionException,
IllegalArgumentException, JSONException, InstantiationException {
Class clazz = object.getClass();
// if it is a proxy, find the actual type behind it
if(Advised.class.isAssignableFrom(clazz)){
clazz = ((Advised)object).getTargetSource().getTargetClass();
}
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
// iterate over class fields
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
if (elements.containsKey(name)) {
Object value = elements.get(name);
Method method = prop.getWriteMethod();
if (method != null) {
JSON json = method.getAnnotation(JSON.class);
if ((json != null) && !json.deserialize()) {
continue;
}
// use only public setters
if (Modifier.isPublic(method.getModifiers())) {
Class[] paramTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
if (paramTypes.length == 1) {
Object convertedValue = this.convert(paramTypes[0], genericTypes[0], value,
method);
method.invoke(object, new Object[] { convertedValue });
}
}
}
}
}
}
please note these lines I added:
// if it is a proxy, find the actual type behind it
if(Advised.class.isAssignableFrom(clazz)){
clazz = ((Advised)object).getTargetSource().getTargetClass();
}