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();
}
我有一个 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();
}