Spring aop, cglib enhanced class lose generic info

1.4k views Asked by At

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?

2

There are 2 answers

0
WestFarmer On BEST ANSWER

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();
            }
0
Rafael Winterhalter On

Cglib was created before generic types existed. A proxy is generated as a subclass of the proxied class in cglib which does not retain the generic type information. Therefore, you cannot query it from the proxy class.