讓servlet支持瀏覽器緩存
大家都知道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請求都可以用到瀏覽器緩存,從而提高性能。