最近在做一个项目时,碰到了这个问题。琢磨了好长时间代码,都没有发现问题。原本以为是过滤器和切面的异步操作造成最后调用returnObject时多调用了一次,但是把过滤器中的相关代码去掉之后,发现还是这个问题。
一个多小时,就为了解决这个问题。下面说明解法:
先上出问题的代码:
package com.projecthome.aspect;
import java.lang.reflect.Method;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import com.projecthome.bean.User;
import com.projecthome.obejctpool.KeyUserObjectPoolFactory;
import com.projecthome.util.ConvertXmlToObject;
/**
*
* 在这个切面中装填 对象池中的相应的<span>User</span>对象
* 因为我们在过滤器中,以及后面的方法中,对于同一个<span>email</span>处理的是同一个对象,所以这里完全可以这么做
*
* @author AlstonWilliams
* @date 2016/03/19
* @contact pshuyue@gmail.com
* */
@Aspect
@Component
public class FillUserAspect {
@Pointcut("execution(* com.projecthome.operation.UserOperation.query*(..))")
public void declareExpression(){}
@Before("declareExpression()")
public void beforeCall(JoinPoint joinPoint) throws Exception{
String email = joinPoint.getArgs()[0].toString();
GenericKeyedObjectPool<String, User> userPool = new GenericKeyedObjectPool<String, User>(KeyUserObjectPoolFactory.newInstance());
User user = userPool.borrowObject(email);
String url = "http://localhost/UserService_3.0/dao/v3/user/find?useremail=pshuyue@gmail.com";
HttpGet httpGet = new HttpGet(url);
user = (User) ConvertXmlToObject.newInstance().convert(user, EntityUtils.toString(new DefaultHttpClient().execute(httpGet).getEntity(), "utf-8"));
if (null != user) {
userPool.returnObject(email, user);}
}
}
其中ConvertXmlToObject这个类是我自己写的一个工具类,用于在约定好的情况下将xml文档直接转换成一个对象,这样调用起来方便。下面是这个类的代码:
package com.projecthome.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
*
* 将一个xml文件转换成一个对象
*
* 能完成这种转换是基于约定的:
* 1.该xml文档中除了根节点只能有一级节点
* 2.该一级节点必须对应要转换的Bean中的一个属性
*
* @author AlstonWilliams
* @date 2016/03/15
* @contact pshuyue@gmail.com
*
* */
public class ConvertXmlToObject {
private static ConvertXmlToObject convertXmlToObject;
private ConvertXmlToObject(){}
public static ConvertXmlToObject newInstance(){
if (convertXmlToObject == null) {
convertXmlToObject = new ConvertXmlToObject();
}
return convertXmlToObject;
}
/*
* @param fullClassName qualified name of class
* @param xml string whose format is xml
*
*/
public Object convert(String fullClassName,String xml) throws ClassNotFoundException, DocumentException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Object object = Class.forName(fullName).newInstance();
SAXReader saxReader = new SAXReader();
Document document = DocumentHelper.parseText(xml);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()) {
Element element = (Element) iterator.next();
String elementName = element.getName().toLowerCase();
String methodName = "set"
+ new Character(elementName.charAt(0)).toString().toUpperCase()
+ elementName.substring(1);
Method method = object.getClass().getMethod(methodName, String.class);
method.invoke(object, element.getText());
}
return object;
}
}
* @throws IllegalStateException if an object is returned to the pool that
* was not borrowed from it or if an object is
* returned to the pool multiple times
出在 https://commons.apache.org/proper/commons-pool/api-2.0/src-html/org/apache/commons/pool2/impl/GenericKeyedObjectPool.html
可以看到,如果你要returnObject的对象不是你从borrow方法中获取的那个对象,那么就会抛出异常。那么就很明显了,因为"="运算符是将一个对象的引用赋值给另一个对象,所以其实是将ConvertXmlToObject中的convert方法中的object的引用赋值给了FillUserAspect中的user对象了。这时user对象指向的当然就不是以前的对象。我们通过比较赋值前后的user对象的hash值就能清楚的得到这个结论。既然原来的对象了,自然就不能使用returnObject方法来返回了。
于是乎,我修改了ConvertXmlToObject中的convert方法,现在就一切正常了:
package com.projecthome.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
*
* 将一个xml文件转换成一个对象
*
* 能完成这种转换是基于约定的:
* 1.该xml文档中除了根节点只能有一级节点
* 2.该一级节点必须对应要转换的Bean中的一个属性
*
* @author AlstonWilliams
* @date 2016/03/15
* @contact pshuyue@gmail.com
*
* */
public class ConvertXmlToObject {
private static ConvertXmlToObject convertXmlToObject;
private ConvertXmlToObject(){}
public static ConvertXmlToObject newInstance(){
if (convertXmlToObject == null) {
convertXmlToObject = new ConvertXmlToObject();
}
return convertXmlToObject;
}
/*
* @param object object you want to convert
* @param xml string whose format is xml
*
*/
public Object convert(Object object,String xml) throws ClassNotFoundException, DocumentException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
SAXReader saxReader = new SAXReader();
Document document = DocumentHelper.parseText(xml);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()) {
Element element = (Element) iterator.next();
String elementName = element.getName().toLowerCase();
String methodName = "set"
+ new Character(elementName.charAt(0)).toString().toUpperCase()
+ elementName.substring(1);
Method method = object.getClass().getMethod(methodName, String.class);
method.invoke(object, element.getText());
}
return object;
}
}
以前看到这个知识点,一直也没有注意,直到今天调试时,注意到赋值前后的user对象的哈希码不同,才明白过来。今后也应当注意才是。