J2EEweb开发中的缓存问题的研究
一般情况下,浏览器都会缓存已经访问过的页面内容,关于如何禁止浏览器缓存的介绍,在网上到处都有相关的文章,但是,关于浏览器如何利用缓存,如何处理缓存的讲解,却鲜有人谈及
.浏览器在访问已缓存过的资源时,它在什么情况下会向服务器发送请求?在什么情况下根本就不向服务器发送请求.这与浏览器的缓存设置有关!但是,由于几乎所有人的浏览器都是采用的默认设置,所以,重点应该放在分析浏览器的默认缓存设置的研究上.
浏览器缓存内幕与getLastModified方法:
在HttpServlet类中定义了一个getLastModified方法,其完整语法定义如下:
protected long getLastModified(HttpServletRequest req)
其中的返回值表示自1970年1月1日的0点0分0秒开始计算的一个毫秒数,HttpServlet类中定义的getLastModified方法总是返回一个负数,在HttpServlet子类中可以对这个方法进行覆盖,以便返回一个代表当前输出的响应内容的修改时间,HttpServlet类的service方法可以根据这个返回值在响应消息中自动生成Last-Modified头字段。
一般情况下,浏览器都会缓存已经访问过的页面内容,getLastModified方法的返回值可以影响浏览器如何处理和利用缓存内容。在详细了解getLastModified方法的应用之前,应该先对浏览器的缓存机制有所了解。单击IE浏览器的“工具”à“Internet选项”菜单,打开“Internet选项”对话框,接着再单击“常规”选项卡中的“Internet临时文件”栏中的“设置”按钮,打开如图4.16所示的“设置”对话框。
还可以看到,其“检查所存网页的较新版本”功能项的设置值有4个选项,只要先单击“设置”对话框标题栏中的问号按钮,然后再单击相应的选项,就可以看到每个选项的作用和意义:
“每次访问此页时检查”选项表示浏览器每次访问一个页面时,不管浏览器是否缓存过此页面,都要向服务器发出访问请求。这种设置的优点是实时性很强,肯定能够访问到网页的最新内容,但是如果网页内容很少更新,这种设置的访问效率就比较低了。
“每次启动Internet Explorer时检查”选项表示在浏览器的每次启动运行期间,在第一次访问一个页面时,不管浏览器是否缓存过此页面,都要向服务器发出访问请求,但是在浏览器的本次启动运行期间对该页面的后续访问,浏览器将不再向服务器发出访问请求,而是直接使用缓存中的内容。这种设置具有较高的访问效率,同时也兼顾了较好的实时性,它可以保证每次启动浏览器后看到的都是最新的网页内容。
“自动”选项与“每次启动Internet Explorer时检查”选项的功能相似,只是对图像的访问有所不同,如果随着时间的推移,浏览器发现网页上的图像更新并不频繁,这样,即使浏览器在对某个已缓存的图像执行本次启动运行以来的第一次访问时,它也不一定会向服务器发出访问请求,而是干脆直接使用缓存中的内容。“自动”选项是浏览器的默认设置,所以,几乎所有人的浏览器都是按照这种方式工作的,这个选项的作用和意义应该成为读者熟悉的重点。
“不检查”选项表示浏览器不管在什么情况下访问一个页面时,只要能够在本地找到此页面的缓存信息,浏览器就不会向服务器发出访问请求,而是直接使用缓存的内容。这种设置的优点是访问效率很高,但是如果服务器端的网页内容更新后,浏览器看到的内容很可能是过期的内容。
浏览器将根据响应消息中是否包含Last-Modified头字段来进行处理,如果响应消息中没有包含Last-Modified头字段,它将在每次访问此页面时都向服务器发出访问请求,否则,它仅在每次启动运行后的第一次访问此页面时才向服务器发出访问请求,而在启动运行期间对此页面的后续访问都不再向服务器发出访问请求。
响应消息中的Last-Modified头字段可用于指定响应内容的最后更新时间,当客户机缓存此文档内容后,它在以后的请求消息中将根据Last-Modified头字段指定的时间来生成If-Modified-Since请求头字段,以指出缓存文档的最后更新时间。只有文档的修改时间比If-Modified-Since请求头指定的时间新时,服务器才会返回文档内容。如果自从If-Modified-Since指定的时间以来,网页内容没有发生修改,服务器将返回一个304(Not Modified)状态码来表示浏览器缓存的版本是最新的,而不会向浏览器返回文档内容,浏览器则继续使用以前缓存的内容。通过这种方式,可以在一定程度上减少浏览器与服务器之间的通信数据量,从而提高了通信效率。
HttpServlet类为If-Modified-Since请求头和Last-Modified头字段的这种应用提供了处理机制,当继承了HttpServlet类的Servlet程序接收到一个GET方式的访问请求时,HttpServlet中重载的service方法在调用doGet方法之前,它还将调用getLastModified方法,并根据getLastModified方法的返回值来决定是否调用doGet方法和在响应消息中是否生成Last-Modified头字段,具体规则如下:
当getLastModified方法返回一个负数时,不管请求消息中的情况怎样,service方法都将直接调用doGet方法来生成响应内容,这正是HTTPServlet类中定义的getLastModified方法的行为;
当getLastModified方法返回一个正数,且请求消息中没有包含If-Modified-Since请求头时(这往往出现在第对某个资源的第一次访问时),或者请求消息中包含的If-Modified-Since请求头中的时间值比getLastModified方法返回的时间值旧时,service方法将根据getLastModified方法的返回值生成一个Last-Modified头字段,然后调用doGet方法生成响应内容;
当getLastModified方法返回一个正数时,且请求消息中包含的If-Modified-Since请求头中的时间值比getLastModified方法返回的时间值新或者与之相同时,service方法将不调用doGet方法,而是向浏览器返回一个304(Not Modified)状态码表示浏览器可以使用其以前缓存的内容。
动手体验:揭开浏览器缓存的奥秘
编写一个名为CacheServlet的Servlet程序,在其doGet方法中向浏览器和Tomcat的命令行窗口中都打印出当前的时间值,getLastModified方法也是向Tomcat的命令行窗口中打印出当前的时间值和返回当前时间值,这里先将getLastModified方法注释掉,如例程4-9所示。
例程4-9 CacheServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class CacheServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
PrintWriter out = response.getWriter();
long now = System.currentTimeMillis();
out.println("doGet:" + now);
System.out.println("doGet:" + now);
}
/*protected long getLastModified(HttpServletRequest req)
{
long now = System.currentTimeMillis();
System.out.println("getLastModified:" + now);
return now;
}*/
}