Spring aop、cglib 增强 class 丢失通用信息

Spring aop, cglib enhanced class lose generic info

我有一个 struts 2 动作 class:

public class MyAction{
    private ArrayList<User> users;
    public void setUsers(ArrayList<User> users){
        this.users = users;
    }
    public String doMyAction(){
        //...
    }
}

doMyAction 方法有一个 AOP 切入点,所以 MyAction 实际上是一个在运行时代理 class 的 cglib,而 users 字段将由来自客户端的 json 数据填充,当启用 aop 时,struts JSONInterceptor 将无法将 json 数据填充到 users 字段中。我调试了 struts json 插件的源代码并在 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 });
                        }
                    }
                }
            }
        }
    }

并在这一行:

Type[] genericTypes = method.getGenericParameterTypes();

当启用AOP 时,它returns java.util.ArrayList 反对users 字段的setter 方法。但 java.util.ArrayList<User> 预期。

似乎我的操作 class 在被 cglib 代理时丢失了它的通用信息。我也找到了关于这个的 a old bug

我可以从 aop 配置中排除我的方法来解决这个问题。但我还是想知道有没有更好的解决办法?

Cglib 是在泛型类型存在之前创建的。代理生成为 cglib 中代理 class 的子 class,它不保留通用类型信息。因此,您无法从代理查询它 class.

我的想法是尝试找到代理背后的实际类型。根据spring文档,任何从springaop获取的代理都实现了org.springframework.aop.framework.Advised接口,这个接口暴露方法查询目标class.

Any AOP proxy obtained from Spring can be cast to this interface to allow manipulation of its AOP advice.

所以这里我们有一个相当大的选择,我们可以下载 struts json 插件源代码并构建我们自己的,修改 [=15= 的 populateObject 方法]

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 });
                        }
                    }
                }
            }
        }
    }

请注意我添加的这些行:

 // if it is a proxy, find the actual type behind it
            if(Advised.class.isAssignableFrom(clazz)){
                clazz = ((Advised)object).getTargetSource().getTargetClass();
            }