都是缓存惹的祸

最近的工作内容就是修改公司的一个基于jsp的网站项目。这个网站原来使用的数据库是access,当然说到这里有人会说access几乎就不是一个数据库,根本就是一个文件。谁说不是呢?现在改为使用mysql,顿时专业了好多。当然在更换数据库的过程中遇到的很多问题,但都不值得一提。因为更换数据库遇到的无非是sql语句不兼容, access中分页使用select top ….而mysql中不支持top关键字,可以使用效率更高的limit;或者是把拼接sql语句的逻辑直接放到jsp文件中,而不进行任何封装。

这些问题都老生常谈了,也没有任何记录的必要。让我真正郁闷好几天的是使用ajax时候浏览器缓存功能造成的问题。

网站中使用到ajax的功能非常简单。这里先简单描述一下,有两个下拉框A,B。下拉框B的内容是根据用户对A的选择而变化的。这里的主要流程就一目了然了:

  1. 用户更改了下拉框A的选项;
  2. 浏览器向服务器发送一个异步的命令;
  3. 服务器接收到之后查询数据库;
  4. 服务器把查询结构封装成xml发送到客户端;
  5. 客户端解析xml数据,修改下拉框B的选项条目。

根据上面的大纲,先做第一步,在下拉框A上注册onchange事件。

<select id="area" onchange="AreaChange()">

响应的JS函数是AreaChange(),很明显这个函数的任务就是发送一个异步命令到服务器端。

function AreaChange()
{
	var area = document.getElementById("area").value;
	var roleID = document.getElementsByName("rolename")[0].value;
	var url = "PrivilegeUpdate?area=" + area + "&roleid=" + roleID;
	createXMLHttpRequest();
	xmlHttp.onreadystatechange = AreaChangeReady;
	xmlHttp.open("GET", url, true);
	xmlHttp.send(null);
}

函数中首先得到下拉框选中的值,然后拼接成url,发送到服务器。由于是异步操作,因此还要注册一个接收消息的响应函数AreaChangeReady。

接下来就是服务器端的操作了,服务器端的代码我是用java写的。

public class PrivilegeUpdate extends HttpServlet 
{
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
	{
		response.setContentType("text/html charset=UTF-8");
		String area = request.getParameter("area");
		areaDB Area = new areaDB();
		lineTable [] lt = Area.getLine(area);
		if(lt == null)
			return;
		StringBuffer results = new StringBuffer("<root><line>");
		for(int i=0;i<lt.length;i++)
		{
			results.append("<id>");
			results.append(lt[i].getLineID());
			results.append("</id>");
			results.append("<name>");
			results.append(lt[i].getLineName());
			results.append("</name>");
		}
		results.append("</line>");
		results.append("</root>");
		response.setContentType("text/xml");
		response.getWriter().write(results.toString());
	}
}

这个类继承了HttpServlet类,并且重载了doGet方法,由于以xml的方式发送到客户端,设置ContentType为text/xml。然后根据返回的结果拼接xml字符串。

接下来就是客户端的AreaChangeReady开始执行了。

function AreaChangeReady()
{
	if(xmlHttp.readyState == 4)
	{
		if(xmlHttp.status == 200)
		{
			updateLine();
			updateUnAddPrivilege();
		}
	}
}

第二个函数调用可以不去理会,重要的是updateLine函数,主要功能是更新下拉框B。

function updateLine()
{
	clearLine();
	var linexml = null;
	linexml = xmlHttp.responseXML.getElementsByTagName("line");
	var line = document.getElementById("line");
	var option = null;
	if(linexml == null || linexml.length == 0)
	{
		option = document.createElement("option");
		option.setAttribute("value", "");
		option.appendChild(document.createTextNode("无线路"));
		line.appendChild(option);
		return;
	}
	var lineID = linexml[0].getElementsByTagName("id");
	var lineName = linexml[0].getElementsByTagName("name");
	for(var i=0;i<lineID.length;i++)
	{
		option = document.createElement("option");
		option.setAttribute("value", lineID[i].firstChild.nodeValue);
		option.appendChild(document.createTextNode(lineName[i].firstChild.nodeValue));
		line.appendChild(option);
	}
}

解析了服务器发来的xml数据,然后根据读取的数据更新下拉框B。

上面的代码从理论上来说没有问题,一些简单的测试也符合预期。但是在进一步的测试中就出现了莫名其妙的问题。我更新数据库中的数据,然后再进行选择下拉框A的操作,下拉框B并不会如我预期那样显示正确的数据。这里很显然,肯定就是浏览器缓存的原因了。

第一次选择下拉框A的时候浏览器会如预期一样向服务器发送请求,然后服务器返回,浏览器更新下拉框B的选项。但同时,浏览器也做了个小动作,保存了这个数据以备下一次使用。所以第二次,浏览器将不再向服务器发送数据,而是直接从缓存中拿出数据,更新下拉框B的选项。如果这时候更新了数据库,悲剧也就发生了。

明白了这个道理,解决方案当然也就出来了:不要让浏览器缓存这个xml数据。HTTP协议本身就可以设置浏览器是否进行缓存。

在servlet的doGet方法进行输出之前加入这么一行代码:

response.setHeader("Cache-Control", "no-cache");

但是问题还是存在,虽然servlet中看上去只只有一个输出

response.getWriter().write(results.toString());

但是方法还有一个出口那就是return。一开始我也以为return之后并不会向浏览器发送数据,但是我错了,虽然没有发送数据,浏览器也会把没有数据这个结果缓存起来。这个错误相当隐蔽,我被这个错误折磨了2天之后才突然明白过来。修改后的代码如下

public class PrivilegeUpdate extends HttpServlet 
{
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
	{
		response.setContentType("text/html charset=UTF-8");
		String area = request.getParameter("area");
		String roleid = request.getParameter("roleid");
		areaDB Area = new areaDB();
		lineTable [] lt = Area.getLine(area);
		if(lt == null)
		{
			response.setContentType("text/xml");
			response.setHeader("Cache-Control", "no-cache");
			response.getWriter().write("<root></root>");
			return;
		}
		StringBuffer results = new StringBuffer("<root><line>");
		for(int i=0;i<lt.length;i++)
		{
			results.append("<id>");
			results.append(lt[i].getLineID());
			results.append("</id>");
			results.append("<name>");
			results.append(lt[i].getLineName());
			results.append("</name>");
		}
		results.append("</line>");
		results.append("</root>");
		response.setContentType("text/xml");
		response.setHeader("Cache-Control", "no-cache");
		response.getWriter().write(results.toString());
	}
}

经历过这次事件,以后使用ajax时候肯定会注意HTTP的Cache-Control属性。

  • 1
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 61
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 61
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值