最近寫個人網(wǎng)站,遇到一個問題,如題,到網(wǎng)上查找最終找到的一種解決方法:
使用Filter實現(xiàn)靜態(tài)HTML緩沖(一種折中方法)
緩沖是Web應用中必須考慮的一個提高性能的重要手段。對于基于JSP/Servlet技術(shù)的站點,常用的緩沖有持久層的數(shù)據(jù)庫連接池緩沖,內(nèi)存中的值對象緩沖,JSP頁面緩沖,以及各種各樣的緩沖框架等等,無不是為了提高系統(tǒng)的吞吐量。 ¬[ @2v4Y p e5c P.k b
然而對于大型站點來說,將JSP頁面轉(zhuǎn)換為靜態(tài)Html也許是最高效的方法,特別適合于數(shù)據(jù)不經(jīng)常變化但是頁面訪問量特別大的站點,如新聞等,通過把JSP動態(tài)頁面預先轉(zhuǎn)換為靜態(tài)Html頁面,當用戶請求此頁面時,系統(tǒng)自動導向到對應的Html頁面,從而避免解析JSP請求,調(diào)用后臺邏輯以及訪問數(shù)據(jù)庫等操作所帶來的巨大開銷。
如何將一個已有的JSP站點的動態(tài)JSP頁面轉(zhuǎn)化為靜態(tài)Html呢?我們希望在不用更改現(xiàn)有Servlet,JSP的前提下讓系統(tǒng)自動將這些JSP轉(zhuǎn)換為Html頁。幸運的是,F(xiàn)ilter為我們提供了一種實現(xiàn)方案。 o'iK$T0K
Filter是Servlet 2.2規(guī)范中最激動人心的特性。Filter能過濾特定URL如/admin/*并進行必要的預處理,如修改Request和Response,從而實現(xiàn)定制的輸入輸出。更強大的是,F(xiàn)ilter本身是一個責任鏈模式,它能一個接一個地傳遞下去,從而將實現(xiàn)不同功能的Filter串起來,并且可以動態(tài)組合。
要自動生成靜態(tài)頁面,用Filter截獲jsp請求并先進行預處理,自動生成Html,是個不錯的主意。一個很容易想到的方法是在Filter截獲Request后,導向一個Servlet,在這個Servlet中向本機發(fā)送一個http請求,然后將響應寫入一個文件:
B Q u s ]F/B5V ~:? Y)~
URLConnection urlConn = URLConnection.open(http://localhost/req);
3l T tL*} w ^
注意要避免遞歸。 K p-i8n f2F cN7^ f
7s s i A:a l*o A)~ ~ q
另一個方法是不模擬http,而是定制Response,把服務器返回的JSP響應輸出到我們自己的Response中,就可以將響應快速寫入Html文件,然后再發(fā)送給客戶。而且,由于沒有http模擬請求,直接讀取服務器響應速度非常快。 S%M/|*M }9A S
2K3` G#J3n&|O Y w.A g
截獲Response的關(guān)鍵便是實現(xiàn)一個WrappedResponse,讓服務器將響應寫入我們的WrappedResponse中。這類似于一個代理模式,Servlet 2.x已經(jīng)提供了一個WrappedResponse類,我們只需要復寫其中的一些關(guān)鍵方法即可。 !c P6I)k y I
Q8@ [ u Q
WrappedResponse實現(xiàn)了Response接口,它需要一個Response作為構(gòu)造函數(shù)的參數(shù),事實上這正是代理模式的應用:WrappedResponse充當了代理角色,它會將JSP/Servlet容器的某些方法調(diào)用進行預處理,我們需要實現(xiàn)自己的方法。 n j T Bp
k
0Q;P(U0h e2h:n
綜上:用Filter實現(xiàn)HTML緩沖的步驟是:
p D mn h t'L;H
1. 用Filter截獲請求,如/a.jsp?id=123,映射到對應的html文件名為/html/a.jsp$id=123.htm。 8b6h r l!j;T$k z V
2. 查找是否有/html/a.jsp$id=123.htm,如果有,直接forward到此html,結(jié)束。
3. 如果沒有,實現(xiàn)一個WrappedResponse,然后調(diào)用filterChain(request, wrappedResponse)。
4. 將返回的WrappedResponse寫入文件/html/a.jsp$id=123.htm,然后返回響應給用戶。 Q q,l Z P A P
5. 下一次用戶發(fā)送相同的請求時,到第2步就結(jié)束了。 | J O't ^
*i Q2j m Qu/}"^-^ x
使用這個方法的好處是不用更改現(xiàn)有的Servlet,JSP頁,限制是,JSP頁面結(jié)果不能與Session相關(guān),需要登陸或用戶定制的頁面不能用這種方法緩沖
源代碼:
三、利用Filter和定制Response,把服務器返回的JSP響應輸出到我們自己的Response中,就可以將響應快速寫入Html文件,然后再發(fā)送給客戶。 p8P!`!N C |
.^ A4w%F*IL [¬|1R
import java.io.*;
import javax.servlet.*; 5\ S o @ l w2L o1W
import javax.servlet.http.*; %^ Q"Ng H*W J Q
import java.util.Calendar; M A&O(~ t C e
S$y u i-v2P ` R+We
public class CacheFilter implements Filter { D i3K/C A e ~4s(~
ServletContext sc; /P1{,Y G0B2X¬Y Q
FilterConfig fc; 8O F/~ u"z [-`
long cacheTimeout = Long.MAX_VALUE; 5h9l9r I l
public void doFilter(ServletRequest req,
ServletResponse res,
FilterChain chain) 1{ P z n [)\
throws IOException, ServletException {
HttpServletRequest request = y/R%A Z d m F l.U s
(HttpServletRequest) req;
HttpServletResponse response =
(HttpServletResponse) res;
// check if was a resource that shouldn't be cached. | N D l!Y em B
String r = sc.getRealPath("");
String path =
fc.getInitParameter(request.getRequestURI());
if (path!= null && path.equals("nocache")) { )b d(e'I7J @
chain.doFilter(request, response);
return; \ _ N i8e
} &a2{ c Y#w u x
path = r+path;
String id = request.getRequestURI() +
request.getQueryString(); 6r | g:i u g"[ Y
File tempDir = (File)sc.getAttribute( p R2P0l J1u$u
"javax.servlet.context.tempdir"); -A%Z C G g5d
P c [;S2x$? | t5Z `5W
// get possible cache
String temp = tempDir.getAbsolutePath();
File file = new File(temp+id); &G/Y1N G*`4y3_
// get current resource
if (path == null) {
path = sc.getRealPath(request.getRequestURI());
}
H K/J f P v H
m @)T
File current = new File(path);
so,Z3[ ~ J2u.D
try {
long now = {:{.i$U Q0} o
Calendar.getInstance().getTimeInMillis(); b Ta#f¬u.f9]
//set timestamp check
if (!file.exists() || (file.exists() &&
current.lastModified() > file.lastModified()) || q Z G H j¬u2_+k
cacheTimeout < now - file.lastModified()) { H ~2X0e*uS
String name = file.getAbsolutePath();
name = @ D2U)V7o }:V U
name.substring(0,name.lastIndexOf("/"));
new File(name).mkdirs();
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
CacheResponseWrapper wrappedResponse = )f7c:s \
o ^ V*a$w S
new CacheResponseWrapper(response, baos); z s y W$u5U+C f
chain.doFilter(req, wrappedResponse); /Y1t [1O(I"a
l x&e X¬u/n {7D$I
FileOutputStream fos = new FileOutputStream(file); m i u { L0M H i
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} 0~#q S L;B
} catch (ServletException e) {
if (!file.exists()) { D @¬? d r L¬x
throw new ServletException(e); f ~1C dl C(R \6q
} ¬q:T p p R-D
} +_ P;? u
L3I.L
catch (IOException e) { R7L0|
A7I7hB
if (!file.exists()) { 6u-i Y L d e A
throw e;
} r F)C I PN
} -X%V i N$w h K.q
FileInputStream fis = new FileInputStream(file);
String mt = sc.getMimeType(request.getRequestURI());
response.setContentType(mt); #l ?-p1e$Y9i R¬|
ServletOutputStream sos = res.getOutputStream();
for (int i = fis.read(); i!= -1; i = fis.read()) { ,f7f Iy4Y8V m.B:T
sos.write((byte)i);
} V A ] ^$Q7I
}
public void init(FilterConfig filterConfig) { h g D3| u k d
this.fc = filterConfig; 7N l!]"X#M
String ct = x¬C m X W `!a I5D7[
fc.getInitParameter("cacheTimeout");
if (ct != null) { Z9d#X7y m"jV
cacheTimeout = 60*1000*Long.parseLong(ct); d
U {6n L*H0s h
} W'W Fz l,?
this.sc = filterConfig.getServletContext();
}
public void destroy() { 6?-K e p4Y j z _ |
this.sc = null;
this.fc = null; 6lo N ] x A
} 0~$T*M$_3e p0m
_,W \
} D:~ F M-}
t!J/b$q4_ Q F s*? u