一、环境简介
项目以前使用的是RCP框架,现想支持web请求,为了减少后台逻辑处理工作量,重用之前的RCP程序代码,通过添加一个新的插件用于处理web请求。具体实现可见http://blog.csdn.net/rongyongfeikai2/article/details/39577237。
二、问题现象
在前台的一个页面中有两种ajax请求device和plan。页面显示的表格内容是通过plan请求填充,操作需要用到的数据通过定时的device请求来获取。这两个请求的后台处理类为同一个HttpServlet类。在重复刷新的过程中,出现plan请求失败的现象,出现请求失败时device请求也会出现异常,但成功返回。
关键代码如下:
AbstractServlet类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import javax.servlet.http.HttpServlet; 2 import javax.servlet.http.HttpServletRequest; 3 import javax.servlet.http.HttpServletResponse; 4 5 public abstract class AbstractServlet extends HttpServlet { 6 7 /** 请求*/ 8 protected HttpServletRequest request; 9 10 /** 响应*/ 11 protected HttpServletResponse response; 12 13 /** 操作名称*/ 14 protected String action; 15 16 /** 请求结果*/ 17 protected boolean result; 18 19 @Override 20 protected void doGet(HttpServletRequest request, HttpServletResponse response){ 21 this.request = request; 22 this.response = response; 23 24 getActionParam(); 25 26 excetue(); 27 28 returnResult(); 29 30 } 31 32 /** 33 * 获取前端的action字段 34 */ 35 protected void getActionParam() { 36 action = request.getParameter("action"); 37 } 38 39 40 abstract public void excetue(); 41 42 abstract protected void returnResult(); 43 }
TestServlet类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.io.IOException; 2 3 import org.codehaus.jackson.JsonNode; 4 5 import com.macrosan.core.nonui.util.JsonMapper; 6 7 public class TestServlet extends AbstractServlet { 8 9 private JsonNode resultNode; 10 11 @Override 12 public void excetue() { 13 if ("plan".equals(action)) { 14 queryAllPlans(); 15 } else if ("device".equals(action)) { 16 queryAllDevices(); 17 } 18 } 19 20 private void queryAllDevices() { 21 /// ....... 22 resultNode = JsonMapper.toNormalTree("device response json string"); 23 24 } 25 26 private void queryAllPlans() { 27 /// ....... 28 resultNode = JsonMapper.toNormalTree("plan response json string"); 29 } 30 31 /** 32 * 33 */ 34 protected void returnResult() { 35 36 try { 37 String resultStr = resultNode.toString(); 38 39 String callBack = request.getParameter("callback"); 40 41 // 普通访问 42 String responseStr = ""; 43 if (callBack == null) { 44 responseStr = resultStr; 45 } else { 46 responseStr = callBack + "(" + resultStr + ")"; 47 } 48 49 response.getWriter().print(responseStr); 50 } catch (IOException e) { 51 e.printStackTrace(); 52 } 53 } 54 }
三、问题定位
复现问题后,通过浏览器调试工具,找出后台返回的结果,如下:
jQuery110102986526135296296_1444812367854([{plan}])jQuery110102986526135296296_1444812367854([{device1},{device2}])
正常情况下应该只返回一个jQuery110102986526135296296_1444812367854字符串,很显然是一个请求结果中包含了两个请求的结果,由于是jsonp跨域请求,所以第二个ajax请求失败。那第一个请求出现error的原因是什么呢?
从结果上看,应该是plan请求先来处理完成后在returnResult方法中将结果先写入,且device请求也来了,接着讲device的结果也写入导致的该字符串。
习惯了使用Struts2等框架,每次action请求都是一个单独的实例来处理请求,跟上一次请求无关。但servlet插件则不同,每次处理请求的实例是同一个对象,所以上一次请求将该实例中的属性修改了会影响到下一次请求。该问题的出现原因是plan请求先来,request和response都是plan请求的,当完成数据收集后,device请求来了,将request和response修改为device的信息,导致plan请求流程中的returnResult方法中的response是device请求的,故导致device请求的结果有两个字符串,而plan请求由于没有返回数据导致请求失败。
四、问题解决
解决方法非常简单,在doGet方法前添加同步关键字,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @Override 2 synchronized protected void doGet(HttpServletRequest request, HttpServletResponse response){ 3 this.request = request; 4 this.response = response; 5 6 getActionParam(); 7 8 excetue(); 9 10 returnResult(); 11 12 }
五、问题总结
该问题包含多线程处理同一对象,导致信息达不到预期的效果;请求没有任何返回会作为error处理;jsonp结果字符串必须要严格的格式等知识点。