學習到此為止告一段落
對教程內容和jsp有什么建議的話,可以跟叮咚姐姐聯系,隨著技術的進步,我們也會同步更新教程的內容。
提出問題:“session是在什么時候生成的?”
現象:
如果寫一個servlet,在里邊調用getSession(false),得到的是null。(這里的參數表示當session不存在時是否新建,返回null表示還沒為當前用戶生成對應的session)。
如果寫一個jsp,在里邊打印session,可以看到打印的信息,說明session已經創(chuàng)建了。
對現象的感性認識:jsp會自動創(chuàng)建HttpSession對象,而servlet則不會,只有在servlet中顯式調用getSession()方法時才創(chuàng)建session。
對現象的理性說明
只有執(zhí)行了getSession()才會生成session。(此處無參數,默認與getSession(true)功能相同)。
jsp轉換成servlet后,生成的servlet代碼中包含getSession()。
例子中見tomcat目錄下,work\Catalina\localhost\99-01\org\apache\jsp\test1.jsp,其中包含了pageContext.getSession()。
我們可以在jsp中使用<%@ page session="false"%>禁止生成session的代碼,這樣即使訪問此jsp頁面,也不會創(chuàng)建session。
例子中見tomcat目錄下,work\Catalina\localhost\99-01\org\apache\jsp\test2.jsp,這里已經看不到getSession()的代碼了。
因為<%@ page session="false"%>不會生成默認的session,在這個jsp頁面里就無法直接使用session變量了。下面的代碼會導致jsp無法編譯的錯誤。
<%@ page session="false"%><%out.println(session);%><hr />
訪問test3.jsp會出現500服務器內部錯誤
在使用過<%@ page session="false"%>的頁面里只能使用getSession()手工獲得session才能使用了。
演示用代碼放在99-01目錄下,TestServlet映射到/請求路徑,test1.jsp會自動創(chuàng)建session,test2.jsp禁用了session,test3.jsp直接使用session會出現錯誤。
這個問題是典型的無用研究之一,實際工作中會有人使用getSession(false)嗎?
有同學提到了一個問題:讓用戶自己編寫jsp上傳到服務器,服務器接收后保存入數據庫,在用戶訪問的時候從數據庫中取出對應的jsp,運行后返回響應。
這個效果從理論上講是可以實現的,我們已經對jsp的運行機制非常熟悉了,第 6.2 節(jié) “jsp與servlet的關系”,我們只需要將jsp轉換為對應servlet的java文件,再編譯為class,最后使用ClassLoader加載生成的class執(zhí)行即可。
好消息是tomcat為我們提供了批量轉換jsp的工具,使用以下的ant腳本就可以將指定目錄下的jsp轉換成對應servlet的java代碼。(ant的使用方法已經超出了本文的討論范圍,如果有興趣請自行參閱官方網站。http://ant.apache.org/)
<project name="jspc" default="jspc" basedir="."><property name="CATALINA_HOME" location="../../"/><path id="build.lib"><pathelement location="WEB-INF/classes"/><fileset dir="WEB-INF/lib"><include name="*.jar"/></fileset></path><target name="jspc"><taskdef classname="org.apache.jasper.JspC" name="jasper2"><classpath><pathelement location="${java.home}/../lib/tools.jar"/><fileset dir="${CATALINA_HOME}/server/lib"><include name="*.jar"/></fileset><fileset dir="${CATALINA_HOME}/common/lib"><include name="*.jar"/></fileset><path refid="build.lib"/></classpath></taskdef><jasper2 verbose="1"package="org.apache.jsp"uriroot="."webXmlFragment="WEB-INF/generated_web.xml"outputDir="./WEB-INF/src/" /></target></project>
為了順利完成轉換工作,我們需要把一系列jar文件放入classpath中:
WEB-INF/lib/*.jar(星號代表所有)
${java.home}/../lib/tools.jar(${java.home}表示jdk安裝目錄)
${CATALINA_HOME}/server/lib/*.jar(${CATALINA_HOME}表示tomcat的安裝目錄)
${CATALINA_HOME}/common/lib/*.jar
簡要介紹一下jasper2的配置參數。
verbose="1"表示打印進度信息,如果verbose="0"則不打印信息。
package="org.apache.jsp"表示生成servlet中的包名(package)。
uriroot="."表示會把當前目錄下的所有jsp都轉換成servlet。
webXmlFragment="WEB-INF/generated_web.xml"會在WEB-INF目錄下生成servlet的默認配置。
比如我們轉換的test.jsp將生成如下部分。
<servlet> <servlet-name>org.apache.jsp.test_jsp</servlet-name> <servlet-class>org.apache.jsp.test_jsp</servlet-class> </servlet> <servlet-mapping> <servlet-name>org.apache.jsp.test_jsp</servlet-name> <url-pattern>/test.jsp</url-pattern> </servlet-mapping>
將這部分復制到你項目中的web.xml中就可以通過/test.jsp請求來訪問生成的servlet了。
outputDir="./WEB-INF/src/"表示生成的servlet文件放在WEB-INF/src目錄下,tomcat會自動生成package對應的目錄,比如test.jsp生成的最終路徑是WEB-INF/src/org/apache/jsp/test_jsp.java。
接著把生成的servlet編譯成class,這次除了common/lib/servlet-api.jar以外我們還需要common/lib/jasper-runtime.jar加入classpath中,編譯腳本參考WEB-INF/src/compile.bat。
現在可以刪除test.jsp了,web.xml中已經將/test.jsp請求轉發(fā)至對應的servlet處理,我們甚至不需要修改任何鏈接。
jspc的主要功能在于預先編譯jsp發(fā)現其中的語法錯誤,有些公司也使用這種方式進行加密(毫無意義的做法,把jsp唯一的靈活性都浪費了)。
演示程序在99-02目錄下,需要安裝ant之后才可能執(zhí)行run.bat將jsp轉換成servlet。
在了解如何手工轉換jsp之后,我們可以來討論在數據庫中保存jsp的問題了。
將jsp從數據庫中提取出來,將這些數據保存成本地文件,使用jspc轉換成servlet再編譯為class,最后使用自定義的ClassLoader讀取到jvm中執(zhí)行。
難點在于生成文件要保證互不影響和自定義ClassLoader如何加載管理這些生成的class。
這個問題是典型的無用研究之一,任何想獲得靈活模板功能的同學都應該亦然決然的拋棄jsp,jsp這種先解釋編譯再執(zhí)行的機制不僅沒有幫助我們提升效率,反而大大增加技術難度并降低響應效率。需要自定義模板的同志務必考慮velocity, freemarker此類模板引擎或者groovy一類腳本語言,這時使用jsp無異于給自己帶上一副沉重的枷鎖。
會話都保存在服務器端。
每個用戶打開瀏覽器就服務器就會給它生成一個sessionId,瀏覽器或者把這個sessionId放到cookie里,或者每次請求都帶在url后邊(自動的),然后服務器就拿到這個sessionid,在內存里翻啊翻啊,翻出對應的session來,就這么對應上的。
ServletContext和session就沒多大關系了,雖然ServletContext也是在服務器端,每個web應用發(fā)布的時候,就要創(chuàng)建這么一塊空間放置ServletContext,這個web應用中的所有的servlet, jsp, filter, listener都可以訪問這塊空間。你可以把他看作是一個全局變量,所有共享數據都放到里邊。
參考 如何在PHP下載文件名中解決亂碼。
基本代碼如下:
<%@page contentType="text/html;charset=UTF-8"%><%request.setCharacterEncoding("utf-8");%><%String client = request.getHeader("User-Agent");String fileName = "中文";if (client.indexOf("MSIE") > 0) {fileName = "attachment; filename=\"" + java.net.URLEncoder.encode(fileName, "utf-8") + ".txt\"";} else {fileName = "attachment; filename*=\"utf8''" + java.net.URLEncoder.encode(fileName, "utf-8") + ".txt\"";}String content = "xml格式內容." ;response.setContentType("application/octet-stream;charset=UTF-8");response.setHeader("Content-Disposition", fileName);out.print(content);%>
按照RFC2231的定義, 多語言編碼的Content-Disposition應該這么定義:
Content-Disposition: attachment; filename*="utf8''%E4%B8%AD%E6%96%87%20%E6%96%87%E4%BB%B6%E5%90%8D.txt"
filename后面的等號之前要加 *
filename的值用單引號分成三段,分別是字符集(utf8)、語言(空)和urlencode過的文件名。
最好加上雙引號,否則文件名中空格后面的部分在Firefox中顯示不出來。
使用urlencode對非iso-8859-1字符進行編碼。
經過試驗,發(fā)現幾種主流瀏覽器的支持情況如下:
IE6
attachment; filename="<URL編碼之后的UTF-8文件名>"
FF3
attachment; filename="UTF-8文件名" attachment; filename*="utf8''<URL編碼之后的UTF-8文件名>"
O9
attachment; filename="UTF-8文件名"
Safari3(Win)
貌似不支持?上述方法都不行