让servlet支持浏览器缓存

jopen 10年前

    大家都知道IE等浏览器支持缓存,并且缓存策略可配置,这样可以极大提高访问服务器的性能,对于一些如JS脚本、CSS文件、图片等静态资源,已经缺省被支持,但是对于自定义Servlet,则默认情况下不支持缓存,这

是什么原因呢?如何让自定义Servlet也支持缓存呢?
    首先来了解下浏览器的缓存机制:对于所有的HTTP Get请求,浏览器会先检查客户端本地缓存是否存在,如果存在,则会在请求头中包含If-Modified-Since头信息,这个Header的值是本地缓存中资源的最后修改时间,服务端通过访问这个头信息和服务器上资源的最后修改时间进行比较,如果服务器端时间更新,则表示资源被修改了,需要重新下载,会调用doGet方法当作正常的Get请求处理,如果服务器端资源最后修改时间不大于客户端的最后修改时间,则表示资源没有修改,直接返回一个304的状态码,表示请浏览器使用自己缓存中的就行了,从而实现了缓存,减少了网络传输,提高了性能。
    但是不是这样的处理在每次请求时都会做呢?答案是否定的!其实这种处理是每个浏览器进程对相同的请求只会做一次,第一次检查完版本后对于后续相同的请求根本不会向服务器发任何请求,直接从客户端缓存中取资源。这样就更进一步提高了性能。
    好了,明白了浏览器的缓存原理后,我们来看下如何让我们自己写的Servlet也能够被浏览器识别并缓存呢?其实这种处理在Servlet的基类中已经处理好了,实现者只需要提供一个最后修改时间的机制就行了。先来看下

基类(HttpServlet)中是如何实现的(反编译代码):

protected void service(HttpServletRequest req, HttpServletResponse resp)          throws ServletException, IOException      {          String method = req.getMethod();          if(method.equals("GET"))          {              long lastModified = getLastModified(req);              if(lastModified == -1L)              {                  doGet(req, resp);              } else              {                  long ifModifiedSince = req.getDateHeader("If-Modified-Since");                  if(ifModifiedSince < (lastModified / 1000L) * 1000L)                  {                      maybeSetLastModified(resp, lastModified);                      doGet(req, resp);                  } else                  {                      resp.setStatus(304);                  }              }          } else          ......      }

从上面的代码实现就可以看出,取最后修改时间是调用getLastModifyed方法,这是一个保护的方法,定义如下:
protected long getLastModified(HttpServletRequest req)
    {
        return -1L;
    }
所以要想Servlet能够支持浏览器缓存,只需要把这个方法重新实现为返回正确的最后修改时间就行了。
举个例子:
我们实现一个Servlet,产生一个服务器端的Java类的客户端JavaScript代理,这被一些Ajax框所使用,
整个类如下所示,覆盖实现了getLastModifyed方法:

public class RemoteServlet extends HttpServlet {   public RemoteServlet() {   }   /**    * 记录类的最近访问时间.    */   private static final Map DATEMAP=new HashMap();   /*    * (non-Javadoc)    *     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,    *      javax.servlet.http.HttpServletResponse)    */   protected void doGet(HttpServletRequest request,     HttpServletResponse response) throws ServletException, IOException {    response.setContentType("application/x-javascript;charset=GBK");    String path = request.getServletPath();    int index = path.lastIndexOf('/');    path = path.substring(index + 1);    PrintWriter writer = response.getWriter();    String scriptStr = "";    try {     //如果不存在,则添加当前时间     if(!DATEMAP.containsKey(path)){      DATEMAP.put(path,new Date());     }     scriptStr = ClassToRemoteUtil.ClassToRemote(path);    } catch (Exception e) {     if(log.isErrorEnabled()){      log.error(e.getMessage(),e);     }     scriptStr="alert('产生远程服务代理"+path+"失败:"+e.getMessage()+"');";    }    writer.write(scriptStr);   }     /**    * {@inheritDoc}    * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)    */   protected long getLastModified(HttpServletRequest req) {    String path = req.getServletPath();    int index = path.lastIndexOf('/');    path = path.substring(index + 1);    Date lastModifyDate=(Date)DATEMAP.get(path);    if(lastModifyDate==null){     lastModifyDate=new Date();     DATEMAP.put(path, lastModifyDate);    }    return lastModifyDate.getTime();   }    }

这样处理以后,这个Servlet的所有请求都能够被浏览器缓存支持。这个Servlet处理的请求为http://xxxx.xxxx.xxx.xx:port/xxxx/classfullname.ro,
则所有的.ro请求都可以用到浏览器缓存,从而提高性能。