JSP中把動態(tài)頁面轉(zhuǎn)換成靜態(tài)頁面的技巧
要自動生成靜態(tài)頁面,用Filter截獲jsp請求并先進(jìn)行預(yù)處理,自動生成Html,是個(gè)不錯(cuò)的主意。一個(gè)很容易想到的方法是在Filter截獲Request后,導(dǎo)向一個(gè)Servlet,在這個(gè)Servlet中向本機(jī)發(fā)送一個(gè)http請求,然后將響應(yīng)寫入一個(gè)文件:
URLConnection urlConn = URLConnection.open(http://localhost/req);
注意要避免遞歸。
另一個(gè)方法是不模擬http,而是定制Response,把服務(wù)器返回的JSP響應(yīng)輸出到我們自己的Response中,就可以將響應(yīng)快速寫入Html文件,然后再發(fā)送給客戶。而且,由于沒有http模擬請求,直接讀取服務(wù)器響應(yīng)速度非???。
截獲Response的關(guān)鍵便是實(shí)現(xiàn)一個(gè)WrappedResponse,讓服務(wù)器將響應(yīng)寫入我們的WrappedResponse中。這類似于一個(gè)代理模式,Servlet2.x已經(jīng)提供了一個(gè)WrappedResponse類,我們只需要復(fù)寫其中的一些關(guān)鍵方法即可。
WrappedResponse實(shí)現(xiàn)了Response接口,它需要一個(gè)Response作為構(gòu)造函數(shù)的參數(shù),事實(shí)上這正是代理模式的應(yīng)用:WrappedResponse充當(dāng)了代理角色,它會將JSP/Servlet容器的某些方法調(diào)用進(jìn)行預(yù)處理,我們需要實(shí)現(xiàn)自己的方法。
綜上:用Filter實(shí)現(xiàn)HTML緩沖的步驟是:
1. 用Filter截獲請求,如/a.jsp?id=123,映射到對應(yīng)的html文件名為/html/a.jsp$id=123.htm。
2. 查找是否有/html/a.jsp$id=123.htm,如果有,直接forward到此html,結(jié)束。
3. 如果沒有,實(shí)現(xiàn)一個(gè)WrappedResponse,然后調(diào)用filterChain(request, wrappedResponse)。
4. 將返回的WrappedResponse寫入文件/html/a.jsp$id=123.htm,然后返回響應(yīng)給用戶。
5. 下一次用戶發(fā)送相同的請求時(shí),到第2步就結(jié)束了。
使用這個(gè)方法的好處是不用更改現(xiàn)有的Servlet,JSP頁,限制是,JSP頁面結(jié)果不能與Session相關(guān),需要登陸或用戶定制的頁面不能用這種方法緩沖。
JSP效率提升的幾點(diǎn)竅門
方法一:在servlet的init()方法中緩存數(shù)據(jù)
當(dāng)應(yīng)用服務(wù)器初始化servlet實(shí)例之后,為客戶端請求提供服務(wù)之前,它會調(diào)用這個(gè)servlet的init()方法。在一個(gè)servlet的生命周期中,init()方法只會被調(diào)用一次。通過在init()方法中緩存一些靜態(tài)的數(shù)據(jù)或完成一些只需要執(zhí)行一次的、耗時(shí)的操作,就可大大地提高系統(tǒng)性能。
例如,通過在init()方法中建立一個(gè)JDBC連接池是一個(gè)最佳例子,假設(shè)我們是用jdbc2.0的DataSource接口來取得數(shù)據(jù)庫連接,在通常的情況下,我們需要通過JNDI來取得具體的數(shù)據(jù)源。我們可以想象在一個(gè)具體的應(yīng)用中,如果每次SQL請求都要執(zhí)行一次JNDI查詢的話,那系統(tǒng)性能將會急劇下降。解決方法是如下代碼,它通過緩存DataSource,使得下一次SQL調(diào)用時(shí)仍然可以繼續(xù)利用它:
public class ControllerServlet extends HttpServlet
{
private javax.sql.DataSource testDS = null;
public void init(ServletConfig config) throws ServletException
{
super.init(config);
Context ctx = null;
try
{
ctx = new InitialContext();
testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS";
}
catch(NamingException ne)
{
ne.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public javax.sql.DataSource getTestDS()
{
return testDS;
}
...
...
}
方法 2:禁止servlet和JSP 自動重載(auto-reloading)
Servlet/JSP提供了一個(gè)實(shí)用的技術(shù),即自動重載技術(shù),它為開發(fā)人員提供了一個(gè)好的開發(fā)環(huán)境,當(dāng)你改變servlet和JSP頁面后而不必重啟應(yīng)用服務(wù)器。然而,這種技術(shù)在產(chǎn)品運(yùn)行階段對系統(tǒng)的資源是一個(gè)極大的損耗,因?yàn)樗鼤oJSP引擎的類裝載器(classloader)帶來極大的負(fù)擔(dān)。因此關(guān)閉自動重載功能對系統(tǒng)性能的提升是一個(gè)極大的幫助。
方法 3: 不要濫用HttpSession
在很多應(yīng)用中,我們的程序需要保持客戶端的狀態(tài),以便頁面之間可以相互聯(lián)系。但不幸的是由于HTTP具有天生無狀態(tài)性,從而無法保存客戶端的狀態(tài)。因此一般的應(yīng)用服務(wù)器都提供了session來保存客戶的狀態(tài)。在JSP應(yīng)用服務(wù)器中,是通過HttpSession對像來實(shí)現(xiàn)session的功能的,但在方便的同時(shí),它也給系統(tǒng)帶來了不小的負(fù)擔(dān)。因?yàn)槊慨?dāng)你獲得或更新session時(shí),系統(tǒng)者要對它進(jìn)行費(fèi)時(shí)的序列化操作。你可以通過對HttpSession的以下幾種處理方式來提升系統(tǒng)的性能:
如果沒有必要,就應(yīng)該關(guān)閉JSP頁面中對HttpSession的缺省設(shè)置: 如果你沒有明確指定的話,每個(gè)JSP頁面都會缺省地創(chuàng)建一個(gè)HttpSession。如果你的JSP中不需要使用session的話,那可以通過如下的JSP頁面指示符來禁止它:
<%@ page session="false"%>
不要在HttpSession中存放大的數(shù)據(jù)對像:如果你在HttpSession中存放大的數(shù)據(jù)對像的話,每當(dāng)對它進(jìn)行讀寫時(shí),應(yīng)用服務(wù)器都將對其進(jìn)行序列化,從而增加了系統(tǒng)的額外負(fù)擔(dān)。你在HttpSession中存放的數(shù)據(jù)對像越大,那系統(tǒng)的性能就下降得越快。
當(dāng)你不需要HttpSession時(shí),盡快地釋放它:當(dāng)你不再需要session時(shí),你可以通過調(diào)用HttpSession.invalidate()方法來釋放它。
盡量將session的超時(shí)時(shí)間設(shè)得短一點(diǎn):在JSP應(yīng)用服務(wù)器中,有一個(gè)缺省的session的超時(shí)時(shí)間。當(dāng)客戶在這個(gè)時(shí)間之后沒有進(jìn)行任何操作的話,系統(tǒng)會將相關(guān)的session自動從內(nèi)存中釋放。超時(shí)時(shí)間設(shè)得越大,系統(tǒng)的性能就會越低,因此最好的方法就是盡量使得它的值保持在一個(gè)較低的水平。
方法 4: 將頁面輸出進(jìn)行壓縮
壓縮是解決數(shù)據(jù)冗余的一個(gè)好的方法,特別是在網(wǎng)絡(luò)帶寬不夠發(fā)達(dá)的今天。有的瀏覽器支持gzip(GNUzip)進(jìn)行來對HTML文件進(jìn)行壓縮,這種方法可以戲劇性地減少HTML文件的下載時(shí)間。因此,如果你將servlet或JSP頁面生成的HTML頁面進(jìn)行壓縮的話,那用戶就會覺得頁面瀏覽速度會非???。但不幸的是,不是所有的瀏覽器都支持gzip壓縮,但你可以通過在你的程序中檢查客戶的瀏覽器是否支持它。下面就是關(guān)于這種方法實(shí)現(xiàn)的一個(gè)代碼片段:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
OutputStream out = null
String encoding = request.getHeader("Accept-Encoding";
if (encoding != null && encoding.indexOf("gzip" != -1)
{
request.setHeader("Content-Encoding" , "gzip";
out = new GZIPOutputStream(request.getOutputStream());
}
else if (encoding != null && encoding.indexOf("comdivss" != -1)
{
request.setHeader("Content-Encoding" , "comdivss";
out = new ZIPOutputStream(request.getOutputStream());
}
else
{
out = request.getOutputStream();
}
...
...
}
方法 5: 使用線程池
應(yīng)用服務(wù)器缺省地為每個(gè)不同的客戶端請求創(chuàng)建一個(gè)線程進(jìn)行處理,并為它們分派service()方法,當(dāng)service()方法調(diào)用完成后,與之相應(yīng)的線程也隨之撤消。由于創(chuàng)建和撤消線程會耗費(fèi)一定的系統(tǒng)資源,這種缺省模式降低了系統(tǒng)的性能。但所幸的是我們可以通過創(chuàng)建一個(gè)線程池來改變這種狀況。另外,我們還要為這個(gè)線程池設(shè)置一個(gè)最小線程數(shù)和一個(gè)最大線程數(shù)。在應(yīng)用服務(wù)器啟動時(shí),它會創(chuàng)建數(shù)量等于最小線程數(shù)的一個(gè)線程池,當(dāng)客戶有請求時(shí),相應(yīng)地從池從取出一個(gè)線程來進(jìn)行處理,當(dāng)處理完成后,再將線程重新放入到池中。如果池中的線程不夠地話,系統(tǒng)會自動地增加池中線程的數(shù)量,但總量不能超過最大線程數(shù)。通過使用線程池,當(dāng)客戶端請求急劇增加時(shí),系統(tǒng)的負(fù)載就會呈現(xiàn)的平滑的上升曲線,從而提高的系統(tǒng)的可伸縮性。
方法 6: 選擇正確的頁面包含機(jī)制
在JSP中有兩種方法可以用來包含另一個(gè)頁面:1、使用include指示符(<%@ includee file=”test.jsp” %>)。2、使用jsp指示符(<jsp:includeepage=”test.jsp” flush=”true”/>)。在實(shí)際中我發(fā)現(xiàn),如果使用第一種方法的話,可以使得系統(tǒng)性能更高。
方法 7:正確地確定javabean的生命周期
JSP的一個(gè)強(qiáng)大的地方就是對javabean的支持。通過在JSP頁面中使用<jsp:useBean>標(biāo)簽,可以將javabean直接插入到一個(gè)JSP頁面中。它的使用方法如下:
<jsp:useBean id="name" scope="page|request|session|application" class=
"package.className" type="typeName">
</jsp:useBean>
其中scope屬性指出了這個(gè)bean的生命周期。缺省的生命周期為page。如果你沒有正確地選擇bean的生命周期的話,它將影響系統(tǒng)的性能。
舉例來說,如果你只想在一次請求中使用某個(gè)bean,但你卻將這個(gè)bean的生命周期設(shè)置成了session,那當(dāng)這次請求結(jié)束后,這個(gè)bean將仍然保留在內(nèi)存中,除非session超時(shí)或用戶關(guān)閉瀏覽器。這樣會耗費(fèi)一定的內(nèi)存,并無謂的增加了JVM垃圾收集器的工作量。因此為bean設(shè)置正確的生命周期,并在bean的使命結(jié)束后盡快地清理它們,會使用系統(tǒng)性能有一個(gè)提高
其它一些有用的方法
在字符串連接操作中盡量不使用“+”操作符:在java編程中,我們常常使用“+”操作符來將幾個(gè)字符串連接起來,但你或許從來沒有想到過它居然會對系統(tǒng)性能造成影響吧?由于字符串是常量,因此JVM會產(chǎn)生一些臨時(shí)的對像。你使用的“+”越多,生成的臨時(shí)對像就越多,這樣也會給系統(tǒng)性能帶來一些影響。解決的方法是用StringBuffer對像來代替“+”操作符。
避免使用System.out.println()方法:由于System.out.println()是一種同步調(diào)用,即在調(diào)用它時(shí),磁盤I/O操作必須等待它的完成,因此我們要盡量避免對它的調(diào)用。但我們在調(diào)試程序時(shí)它又是一個(gè)必不可少的方便工具,為了解決這個(gè)矛盾,我建議你最好使用Log4j工具(http://Jakarta.apache.org,它既可以方便調(diào)試,而不會產(chǎn)生System.out.println()這樣的方法。
ServletOutputStream 與PrintWriter的權(quán)衡:使用PrintWriter可能會帶來一些小的開銷,因?yàn)樗鼘⑺械脑驾敵龆嫁D(zhuǎn)換為字符流來輸出,因此如果使用它來作為頁面輸出的話,系統(tǒng)要負(fù)擔(dān)一個(gè)轉(zhuǎn)換過程。而使用ServletOutputStream作為頁面輸出的話就不存在一個(gè)問題,但它是以二進(jìn)制進(jìn)行輸出的。因此在實(shí)際應(yīng)用中要權(quán)衡兩者的利弊。
JSP/Servlet重定向
1.RequestDispatcher.forward()
是在服務(wù)器端起作用,當(dāng)使用forward()時(shí),Servletengine傳遞HTTP請求從當(dāng)前的Servlet or JSP到另外一個(gè)Servlet,JSP或普通HTML文件,也即你的form提交至a.jsp,在a.jsp用到了forward()重定向至b.jsp,此時(shí)form提交的所有信息在b.jsp都可以獲得,參數(shù)自動傳遞.
但forward()無法重定向至有frame的jsp文件,可以重定向至有frame的html文件,同時(shí)forward()無法在后面帶參數(shù)傳遞,比如servlet?name=frank,這樣不行,可以程序內(nèi)通過response.setAttribute("name",name)來傳至下一個(gè)頁面.
重定向后瀏覽器地址欄URL不變.
例:在servlet中進(jìn)行重定向
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html; charset=gb2312");
ServletContext sc = getServletContext();
RequestDispatcher rd = null;
rd = sc.getRequestDispatcher("/index.jsp"); //定向的頁面
rd.forward(request, response);
}
通常在servlet中使用,不在jsp中使用。
2.response.sendRedirect()
是在用戶的瀏覽器端工作,sendRedirect()可以帶參數(shù)傳遞,比如servlet?name=frank傳至下個(gè)頁面,同時(shí)它可以重定向至不同的主機(jī)上,sendRedirect()可以重定向有frame.的jsp文件.
重定向后在瀏覽器地址欄上會出現(xiàn)重定向頁面的URL
例:在servlet中重定向
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html; charset=gb2312");
response.sendRedirect("/index.jsp");
}
由于response是jsp頁面中的隱含對象,故在jsp頁面中可以用response.sendRedirect()直接實(shí)現(xiàn)重定位。
注意:
(1).使用response.sendRedirect時(shí),前面不能有HTML輸出。
這并不是絕對的,不能有HTML輸出其實(shí)是指不能有HTML被送到了瀏覽器。事實(shí)上現(xiàn)在的server都有cache機(jī)制,一般在8K(我是說JSP SERVER),這就意味著,除非你關(guān)閉了cache,或者你使用了out.flush()強(qiáng)制刷新,那么在使用sendRedirect之前,有少量的HTML輸出也是允許的。
(2).response.sendRedirect之后,應(yīng)該緊跟一句return;
我們已經(jīng)知道response.sendRedirect是通過瀏覽器來做轉(zhuǎn)向的,所以只有在頁面處理完成后,才會有實(shí)際的動作。既然你已經(jīng)要做轉(zhuǎn)向了,那么后的輸出還有什么意義呢?而且有可能會因?yàn)楹竺娴妮敵鰧?dǎo)致轉(zhuǎn)向失敗。
比較:
(1).Request Dispatcher.forward()是容器中控制權(quán)的轉(zhuǎn)向,在客戶端瀏覽器地址欄中不會顯示出轉(zhuǎn)向后的地址;
(2).response.sendRedirect()則是完全的跳轉(zhuǎn),瀏覽器將會得到跳轉(zhuǎn)的地址,并重新發(fā)送請求鏈接。這樣,從瀏覽器的地址欄中可以看到跳轉(zhuǎn)后的鏈接地址。
前者更加高效,在前者可以滿足需要時(shí),盡量使用RequestDispatcher.forward()方法.
注:在有些情況下,比如,需要跳轉(zhuǎn)到一個(gè)其它服務(wù)器上的資源,則必須使用HttpServletResponse.sendRequest()方法。
3.
<jsp:forward page="" />
它的底層部分是由RequestDispatcher來實(shí)現(xiàn)的,因此它帶有RequestDispatcher.forward()方法的印記。
如果在 jsp:forward 之前有很多輸出,前面的輸出已使緩沖區(qū)滿,將自動輸出到客戶端,那么該語句將不起作用,這一點(diǎn)應(yīng)該特別注意。
另外要注意:它不能改變?yōu)g覽器地址,刷新的話會導(dǎo)致重復(fù)提交
4.修改HTTP header的Location屬性來重定向
通過設(shè)置直接修改地址欄來實(shí)現(xiàn)頁面的重定向。
jsp文件代碼如下:
<%
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
String newLocn = "/newpath/jsa.jsp";
response.setHeader("Location",newLocn);
%>
5.JSP中實(shí)現(xiàn)在某頁面停留若干秒后,自動重定向到另一頁面
在html文件中,下面的代碼:
<meta http-equiv="refresh" content="300; url=target.jsp">
它的含義:在5分鐘之后正在瀏覽的頁面將會自動變?yōu)閠arget.html這一頁。代碼中300為刷新的延遲時(shí)間,以秒為單位。targer.html為你想轉(zhuǎn)向的目標(biāo)頁,若為本頁則為自動刷新本頁。
由上可知,可以通過setHeader來實(shí)現(xiàn)某頁面停留若干秒后,自動重定向到另一頁面。
關(guān)鍵代碼:
String content=stayTime+";URL="+URL;
response.setHeader("REFRESH",content);
JSP中的pageEncoding和contentType
關(guān)于JSP頁面中的pageEncoding和contentType兩種屬性的區(qū)別:
pageEncoding是jsp文件本身的編碼
contentType的charset是指服務(wù)器發(fā)送給客戶端時(shí)的內(nèi)容編碼
JSP要經(jīng)過兩次的“編碼”,第一階段會用pageEncoding,第二階段會用utf-8至utf-8,第三階段就是由Tomcat出來的網(wǎng)頁, 用的是contentType。
第一階段是jsp編譯成.java,它會根據(jù)pageEncoding的設(shè)定讀取jsp,結(jié)果是由指定的編碼方案翻譯成統(tǒng)一的UTF-8 JAVA源碼(即.java),如果pageEncoding設(shè)定錯(cuò)了,或沒有設(shè)定,出來的就是中文亂碼。
第二階段是由JAVAC的JAVA源碼至java byteCode的編譯,不論JSP編寫時(shí)候用的是什么編碼方案,經(jīng)過這個(gè)階段的結(jié)果全部是UTF-8的encoding的java源碼。
JAVAC用UTF-8的encoding讀取java源碼,編譯成UTF-8 encoding的二進(jìn)制碼(即.class),這是JVM對常數(shù)字串在二進(jìn)制碼(java encoding)內(nèi)表達(dá)的規(guī)范。
第三階段是Tomcat(或其的application container)載入和執(zhí)行階段二的來的JAVA二進(jìn)制碼,輸出的結(jié)果,也就是在客戶端見到的,這時(shí)隱藏在階段一和階段二的參數(shù)contentType就發(fā)揮了功效
contentType的設(shè)定.
pageEncoding 和contentType的預(yù)設(shè)都是ISO8859-1. 而隨便設(shè)定了其中一個(gè), 另一個(gè)就跟著一樣了(TOMCAT4.1.27是如此). 但這不是絕對的,這要看各自JSPC的處理方式. 而pageEncoding不等于contentType, 更有利亞洲區(qū)的文字CJKV系JSP網(wǎng)頁的開發(fā)和展示, (例pageEncoding=GB2312 不等于 contentType=utf-8)。
jsp文件不像.java,.java在被編譯器讀入的時(shí)候默認(rèn)采用的是操作系統(tǒng)所設(shè)定的locale所對應(yīng)的編碼,比如中國大陸就是GBK,臺灣就是BIG5或者M(jìn)S950。而一般我們不管是在記事本還是在ue中寫代碼,如果沒有經(jīng)過特別轉(zhuǎn)碼的話,寫出來的都是本地編碼格式的內(nèi)容。所以編譯器采用的方法剛好可以讓虛擬機(jī)得到正確的資料。
但是jsp文件不是這樣,它沒有這個(gè)默認(rèn)轉(zhuǎn)碼過程,但是指定了pageEncoding就可以實(shí)現(xiàn)正確轉(zhuǎn)碼了。
舉個(gè)例子:
<%@ page contentType="text/html;charset=utf-8" %>
大都會打印出亂碼,因?yàn)槲逸斎氲?#8220;你好嗎”是gbk的,但是服務(wù)器是否正確抓到“你好嗎”不得而知。
但是如果更改為
<%@ page contentType="text/html;charset=utf-8" pageEncoding="GBK"%>
這樣就服務(wù)器一定會是正確抓到“你好嗎”了。
JSP中的兩種include
我們都知道在jsp中include有兩種形式,分別是
< %@ include file=” ”%>
< jsp:include page=” ” flush=”true”/>
前者是指令元素、后者是行為元素。具體它們將在何處用?如何用及它們有什么區(qū)別?這應(yīng)該是很多人看到它都會想到的問題。下面一起來看看吧。
通常當(dāng)應(yīng)用程序中所有的頁面的某些部分(例如標(biāo)題、頁腳和導(dǎo)航欄)都相同的時(shí)候,我們就可以考慮用include。具體在哪些時(shí)候用< %@ include file=” ”%>,哪些時(shí)候用<jsp:include page=” ”flush=”true”/>.這種形式。首先要明白的是它們之間的區(qū)別。只有了解了它們用法的不同才理解該在何時(shí)去用以及如何選擇。
< %@ include file=” ”%>,jsp的include指令元素讀入指定頁面的內(nèi)容。并把這些內(nèi)容和原來的頁面融合到一起。(這個(gè)過程是在翻譯階段:也就是jsp被轉(zhuǎn)化成servlet的階段進(jìn)行的。
這里對翻譯階段進(jìn)行一下說明:我們知道,jsp頁面不能原封不動地被傳送給瀏覽器,所有的jsp元素都必須首先由服務(wù)器進(jìn)行處理。這是通過將jsp頁面轉(zhuǎn)達(dá)化成servlet,然后執(zhí)行這個(gè)servlet來完成的。服務(wù)器需要一個(gè)jsp容器來處理jsp頁面。jsp容器通常以servlet的形式來實(shí)現(xiàn),這個(gè)servlet經(jīng)過配置,可以處理對jsp頁面的所有請求。
Jsp容器負(fù)責(zé)將jsp頁面轉(zhuǎn)化成servlet(稱為jsp頁面實(shí)現(xiàn)類?JSP Page implementation class),并編譯這個(gè)servlet。這兩步就構(gòu)成了翻譯階段.
由此我們就會知道:jsp頁面是把include指令元素所指定的頁面的實(shí)際內(nèi)容(也就是代碼段)加入到引入它的jsp頁面中,合成一個(gè)文件后被jsp容器將它轉(zhuǎn)化成servlet??梢钥吹竭@時(shí)會產(chǎn)生一個(gè)臨時(shí)class文件和一個(gè)java文件。下面舉個(gè)例子。
服務(wù)器用tomcat,引入頁面的jsp文件叫test.jsp。被引入的頁面叫date.jsp.這個(gè)jsp文件里存放的是一個(gè)有關(guān)時(shí)間的jsp代碼,當(dāng)前的上下文根設(shè)為test
//======date.jsp的源文件=====//
<%@ page language=”java” contentType="text/html;charset=gb2312"%>
<%
java.util.Date date=new java.util.Date();
String date_cn ="";
String dateStr = "";
switch(date.getDay())
{
case 0:date_cn ="日"; break;
case 1:date_cn ="一"; break;
case 2:date_cn ="二"; break;
case 3:date_cn ="三"; break;
case 4:date_cn ="四"; break;
case 5:date_cn ="五"; break;
case 6:date_cn ="六"; break;
}
dateStr = (1900+date.getYear())+"年"+(date.getMonth()+1)+"月"+date.getDate()+
"日(星期" + date_cn + ")";
%>
document.write("<%=dateStr%>");
//======以下是test.jsp的源文件=============//
<%@ page language=”java” contentType=”text/html;charset=gb2312”%>
<html>
<head>
<title>include的兩種用法</title>
<jsp:include page=”date.jsp” flush=”true”/>
<%--@ include file=”date.jsp” %-->
//我們在這里用include的兩種不同形式來引入date.jsp這個(gè)文件.
<head>
<body>
<table><tr><td>
有關(guān)jsp中include的兩種用法.敬請關(guān)注。
</td></tr></table>
</body>
</html>
在test.jsp 文件中,我們只輸出了一行文本“有關(guān)jsp中include的兩種用法.敬請關(guān)注。”,現(xiàn)在讓我們先用<%@ include file=”date.jsp”%>這種形式引入date.jsp這個(gè)文件。你想會出現(xiàn)什么問題了嗎?此時(shí)出現(xiàn)了錯(cuò)誤提示:
HTTP Status 500 ?
org.apache.jasper.JasperException: /date.jsp(0,0) Page directive:
can't have multiple occurrences of contentType
以下還有一堆錯(cuò)誤,但我們只要看這里就知道問題的所在了。狀態(tài)碼為http 500服務(wù)器內(nèi)部錯(cuò)誤。再看下面的提示。在date.jsp頁面中不能指定多個(gè)contentType.
原因就在這里了。是因?yàn)樵诜g階段,date.jsp文件的代碼被原封不動地加入到了test.jsp頁面從而合成一個(gè)文件。合成后的文件中就會相同的:
< %@ page language=”java” contentType=”text/html;charset=gb2312”%>
這句代碼。解決的辦法是把date.jsp文件中的這句刪掉。刷新后再請求test.jsp頁面
請求test.jsp在頁面顯示如下
2003年12月10日 13:12:40
有關(guān)jsp中include的兩種用法.敬請關(guān)注。
這時(shí)我們還不能發(fā)現(xiàn)什么。還是去查看tomcat下的臨時(shí)文件吧。到那里去看看date.jsp文件的內(nèi)容是否已被加入到了test.jsp文件中。
< 注.此處的tomcat裝在E盤根目錄下>
目錄
E:\tomcat\work\Standalone\localhost\test.
在這個(gè)目錄下會看到
test_jsp.java和test_jsp.class兩個(gè)文件。
這里的java文件就是jsp容器將jsp轉(zhuǎn)化成了servlet而得到的test_jsp.java這個(gè)文件。
相對應(yīng)的test_jsp.class這個(gè)文件就是編譯test_jsp.java這個(gè)servlet文件產(chǎn)生的類文件了。打開所產(chǎn)生的servlet文件(test_jsp.java)。此時(shí)我們會發(fā)現(xiàn),在test.jsp文件被轉(zhuǎn)化成servlet文件時(shí),在輸出的< haed >之間加入了一些不是test.jsp頁面里面的代碼,新加入的內(nèi)容就是date.jsp里面的代碼: 新加入了哪些內(nèi)容或是否真的加入了新的內(nèi)容請自己測試去看一下就會一目了然了.在這里不再詳述.
以上就是我們用< %@ include file=”date.jsp”%>這種形式得到的結(jié)果.
下面我們換用< jsp:include page=”dae.jsp” flush=”true”/>也就是將
< %@ include file=”date.jsp”%>換成< jsp:include page=”dae.jsp” flush=”true”/>,然后請求test.jsp.
2003? ê 12??10?? 13:30:13
有關(guān)jsp中include的兩種用法.敬請關(guān)注。
此時(shí)會在頁面上看見.我們所引入date.jsp輸出的日期中中文出現(xiàn)了亂碼.什么原因?是因?yàn)閕nclude行為元素是在請求處理階段執(zhí)行的(此處要對請求處理階段進(jìn)行說明一下.Jsp容器除了上面提到的負(fù)責(zé)將jsp頁面轉(zhuǎn)化成servlet外,還負(fù)責(zé)調(diào)用jsp頁面實(shí)現(xiàn)類以處理每個(gè)請求并產(chǎn)生應(yīng)答.這個(gè)階段我們就稱為請求處理階段.請求處理階段僅執(zhí)行類文件)。
所以在我們作include行為元素引入頁面時(shí),實(shí)際只是引用了date.jsp這個(gè)文件被轉(zhuǎn)化并被編譯后產(chǎn)生的servlet類文件.既如此,date.jsp就是作為一個(gè)單獨(dú)的文件在執(zhí)行后才被test.jsp文件運(yùn)行時(shí)調(diào)用.由于date.jsp文件中沒有指定字符編碼.所以出現(xiàn)了亂碼.解決辦法是在date.jsp文件中重新把剛才去掉的
<%@ page language=”java” contentType=”text/html;charset=gb2312”%>
這行語句加入后刷新重新運(yùn)行.此時(shí)頁面顯示正確,并跟用include指令正常運(yùn)行時(shí)一樣.再查看tomcat下的臨時(shí)文件會發(fā)現(xiàn).此時(shí)多出了一個(gè)date_jsp.java文件和一個(gè)date_jsp.class文件.這兩個(gè)文件得來的方式跟test_jsp.java和test_jsp.class文件得來的方式一樣.再查看一下此時(shí)test_jsp.java文件的代碼會發(fā)現(xiàn).此時(shí)只新增加了一句代碼:
JspRuntimeLibrary.include(request, response, "date.jsp", out, true);
它并沒有把date.jsp文件的代碼加入到test.jsp.
只是在運(yùn)行時(shí)引入了date.jsp頁面執(zhí)行后所產(chǎn)生的應(yīng)答.這意味著我們可以指定任何能夠產(chǎn)生應(yīng)答的Web資源,(例如一個(gè)servlet或一個(gè)jsp頁面),只要這些資源所產(chǎn)生的類型和jsp頁面產(chǎn)生的內(nèi)容類型相同.JSP容器將通過一個(gè)內(nèi)部的函數(shù)調(diào)用來執(zhí)行指定的資源.因此,這些被引入的資源可以幫助處理原來的請求,所以這些資源可以訪問請求作用域內(nèi)的所有對象.以及所有原來的請求參數(shù).
由于在主頁面被請求時(shí),這些頁面還沒有被引入到主頁面中,所以你可以對page屬性使用一個(gè)請求時(shí)屬性值,以便根據(jù)運(yùn)行時(shí)的情況來決定要引入哪一個(gè)頁面.還可以添加一些將被引入的頁面讀取的請求參數(shù).
<jsp:include page=”<%=pageSelectedAtRuntime%>” flush=”true” >
<jsp:param name=”fitstParamer” value=”firstValue”>
<jsp:param name=”lastParamer” value=”lastValue”>
</jsp:include>
如果修改了被引入的jsp頁面,那么可以立刻使用該頁面的最新版本,這是因?yàn)閷Υ灰氲捻撁娴姆绞脚c對待由瀏覽器直接調(diào)用的jsp頁面的方式完全相同.即容器檢測頁面的變化,并自動進(jìn)入翻譯階段,以得到頁面的最新版本.
(注意,include行為元素同jsp其它元素一樣,沒有行為體時(shí)要以”/”結(jié)束.就像下面這樣.
< jsp:include page=”<%=pageSelectedAtRuntime%>” flush=”true” />
以下是對include 兩種用法的區(qū)別
主要有兩個(gè)方面的不同;
1.執(zhí)行時(shí)間上:
< %@ include file=”relativeURI”%> 是在翻譯階段執(zhí)行
< jsp:include page=”relativeURI” flush=”true” /> 在請求處理階段執(zhí)行.
2.引入內(nèi)容的不同:
< %@ include file=”relativeURI”%>
引入靜態(tài)文本(html,jsp),在JSP頁面被轉(zhuǎn)化成servlet之前和它融和到一起.
< jsp:include page=”relativeURI” flush=”true” />引入執(zhí)行頁面或servlet所生成的應(yīng)答文本.
另外在兩種用法中file和page屬性都被解釋為一個(gè)相對的URI.如果它以斜杠開頭,那么它就是一個(gè)環(huán)境相關(guān)的路徑.將根據(jù)賦給應(yīng)用程序的URI的前綴進(jìn)行解釋,如果它不是以斜杠開頭,那么就是頁面相關(guān)的路徑,就根據(jù)引入這個(gè)文件的頁面所在的路徑進(jìn)行解釋.更多有關(guān)URL是如何解釋的請參考相關(guān)資料或書籍。
解決JSP中文問題
方法一:最簡單也是用的最多的方法
<%@ page language="java" pageEncoding="GBK" %>
或者<%@ page contenttype="text/html;charset=gbk";>這里可以用gb2312或者gbk,只是gbk比gb2312支持跟多的字符。
這個(gè)方法用于jsp頁面中的中文顯示。
方法二:使用過濾器
過濾器使用主要針對表單提交,插入數(shù)據(jù)庫的數(shù)據(jù)都是?號。這也是應(yīng)為tomcat不按request所指定的編碼進(jìn)行編碼,還是自作主張的采用默認(rèn)編碼方式iso-8859-1編碼。
編寫一個(gè)SetCharacterEncodingFilter類。
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class SetCharacterEncodingFilter implements Filter {
protected String encoding = null;
protected FilterConfig filterConfig = null;
protected boolean ignore = true;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig=filterConfig;
this.encoding=filterConfig.getInitParameter("encoding");
String value=filterConfig.getInitParameter("ignore");
if(value==null)
this.ignore=true;
else if(value.equalsIgnoreCase("true"))
this.ignore=true;
else
this.ignore=false;
}
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO 自動生成方法存根
if (ignore || (request.getCharacterEncoding() == null)) {
String encoding = selectEncoding(request);
if (encoding != null)
request.setCharacterEncoding(encoding);
}
chain.doFilter(request, response);
}
public void destroy() {
// TODO 自動生成方法存根
this.encoding = null;
this.filterConfig = null;
}
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}
}
然后再web.xml加上
<!-- Set Character Encoding-->
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>com.struts.common.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Set Character Encoding-->
使用過濾器的好處很多,特別是項(xiàng)目之中。
而且在使用國際化時(shí)就更有用了,只要在頁面指定 <%@ page language="java" pageEncoding="UTF-8" %>,服務(wù)器就會根據(jù)本地Locale來顯示正確的字符集。
所以我特別推薦使用過濾器。
方法三:修改tomcat的server.xml文件中URIEncoding
<Connector debug="0" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true"
port="80" redirectPort="8443" enableLookups="false" minSpareThreads="25" maxSpareThreads="75"
maxThreads="150" maxPostSize="0" URIEncoding="GBK" >
</Connector>
這個(gè)方法主要針對從url中獲取字符串的問題。
在tomcat5.0及以上版本,post和get方法在處理編碼時(shí)有所不同。如果你在url中獲取中文就會出現(xiàn)?號。但在tomcat4.1版本沒有問題,因?yàn)閠omcat4.1的post和get方法在處理編碼時(shí)是一樣的。