今天碰上的這個錯誤訊息是在JSP頁面發生
ServletResponse.getOutputStream()
ServetResponse.getWriter()
這兩個方法都會有可能在JSP輸出頁面內容的時候被調用
當其中一個方法被調用過後,再呼叫另一個方法就會報錯誤
解決的方法不外如下:
out.clearBuffer(); //清除buff
out = pageContext.pushBody(); //更新out的內容
2012年9月19日 星期三
2011年6月10日 星期五
Eclipse上安裝Spring IDE套件
Eclipse版本:Eclipse Java EE IDE 3.6.2 Helios
在Eclipse中輸入Spring的updatesite安裝Spring IDE
updatesite:http://springide.org/updatesite
安裝選擇 Core / Spring IDE 、Extensions(Incubation) / Spring IDE、Resources / Spring IDE
以上三項應該就可以了
遇到無法安裝的情形時
可以嘗試先安裝AJDT套件,簡單查一下資料發現這似乎是AOP技術的一種
套件位置:http://www.eclipse.org/ajdt/
挑最新的版本安裝即可
在Eclipse中輸入Spring的updatesite安裝Spring IDE
updatesite:http://springide.org/updatesite
安裝選擇 Core / Spring IDE 、Extensions(Incubation) / Spring IDE、Resources / Spring IDE
以上三項應該就可以了
遇到無法安裝的情形時
可以嘗試先安裝AJDT套件,簡單查一下資料發現這似乎是AOP技術的一種
套件位置:http://www.eclipse.org/ajdt/
挑最新的版本安裝即可
2011年5月4日 星期三
[轉載] FreeMarker IDE
環境說明
作業環境:Windows XP SP3軟體版本:
- Eclipse v3.4.2
說明:FreeMarker 是一套 Java 的樣板引擎,然而 Eclipse 沒有支援 FreeMarker 所以要透過外掛,安裝FreeMarker IDE 可以將 FreeMarker 的語法加上色彩標記,這樣撰寫 FreeMarker 可以比較清楚。
Update Site: http://download.jboss.org/jbosstools/updates/stable官方網站:http://freemarker.sourceforge.net/
版本更新
安裝步驟
- 請參考 Subclipse 這篇文章使用 Eclipse 內建的「Software Updates...」來安裝來安裝
- 因為 FreeMarker IDE 是由 JBoss 所維護的,所以有許多的 JBoss 的外掛,基本上只要勾選「FreeMarkerIDE Feature」並按下「Install...」即可
- 安裝完後,附檔名為「ftl」的 FreeMarker 樣板檔就會改變圖示。
- 開啟編輯就會有色彩標示了。
2010年12月11日 星期六
[轉載] [FCKeditor] 用 Java Taglib 使用 FCKeditor
FCKeditor 是所見即所得(WYSIWYG)的線上編輯器, 支援多國語言, 多種瀏覽器支援, 可更換SKIN,還可以用JavaScript控制按鈕,功能十分多樣化,外掛的功能也是十分強大,支援瀏覽器管理檔案...等多樣化外掛。
支援語言: ASP.Net, ASP, ColdFusion, PHP, Java, Active-FoxPro, Lasso, Perl, Python
支援流覽器: IE, FireFox, Safari, Opera, Google Chrome, Camino
支援平台: MS Windows , MAC , Linux
版權: under the GPL, LGPL and MPL open source licenses
在 Java 的平台上有一個很好用的工具 Taglib , 可以讓你的 jsp 程式簡單化.配置如下
FCKeditor: FCKeditor_2.6.4.zip
FCKeditor.Java: fckeditor-java-2.4.1-bin.zip
FCKeditor.Java 文件參考: http://java.fckeditor.net/
SLF4j: slf4j-1.5.6.zip
Application Server: Tomcat 6.x.x
step01:
在 WebContent 目錄下建立一個目錄 fckeditor , 並將 FCKeditor_2.6.4.zip 解開的資料放在這個目錄下.
step02:
解 開 fckeditor-java-2.4.1-bin.zip , 將 fckeditor-java-core-2.4.1.jar, 及 lib 目錄下的三個檔案 commons-fileupload-1.2.1.jar, commons-io-1.3.2.jar, slf4j-api-1.5.2.jar , 複製到 WEB-INF\lib
step03:
解開 slf4j-1.5.6.zip , 將slf4j-api-1.5.6.jar, slf4j-simple-1.5.6.jar 複製到 WEB-INF\lib
step04:
在 WEB-INF\web.xml 檔增加下列程式碼
step05:
在 WEB-INF 目錄下增加一個檔案 FCKeditor.tld , 內容如下:
step06:
建立一個 fck.jsp 來測試, 程式碼如下:
fckeditor-java-2.4.1 的設定比較簡便, 就連上載檔案, 也只要在step04: 設定好 servlet : Connector 就可以了. 不需要太繁瑣. 不過若要上載檔案, 不要忘了要在 classpath 建立一個檔案 fckeditor.properties , 內容如下:
這樣才不會有問題. 參數說明:
connector.userFilesPath : 是伺服器上要存放檔案的地方.如 /userfiles
connector.resourceType.file.extensions.allowed : 可以限制上傳到/userfiles/file 目錄的附加檔名有那些
connector.resourceType.image.extensions.allowed: 可以限制上傳到/userfiles/image 目錄的附加檔名有那些
因為上載的檔案只能存在這個 application WEBcontent 的目錄內, 所以若將 java 打包成 war 檔在 JBoss 上使用, 則會有問題.
支援語言: ASP.Net, ASP, ColdFusion, PHP, Java, Active-FoxPro, Lasso, Perl, Python
支援流覽器: IE, FireFox, Safari, Opera, Google Chrome, Camino
支援平台: MS Windows , MAC , Linux
版權: under the GPL, LGPL and MPL open source licenses
在 Java 的平台上有一個很好用的工具 Taglib , 可以讓你的 jsp 程式簡單化.配置如下
FCKeditor: FCKeditor_2.6.4.zip
FCKeditor.Java: fckeditor-java-2.4.1-bin.zip
FCKeditor.Java 文件參考: http://java.fckeditor.net/
SLF4j: slf4j-1.5.6.zip
Application Server: Tomcat 6.x.x
step01:
在 WebContent 目錄下建立一個目錄 fckeditor , 並將 FCKeditor_2.6.4.zip 解開的資料放在這個目錄下.
step02:
解 開 fckeditor-java-2.4.1-bin.zip , 將 fckeditor-java-core-2.4.1.jar, 及 lib 目錄下的三個檔案 commons-fileupload-1.2.1.jar, commons-io-1.3.2.jar, slf4j-api-1.5.2.jar , 複製到 WEB-INF\lib
step03:
解開 slf4j-1.5.6.zip , 將slf4j-api-1.5.6.jar, slf4j-simple-1.5.6.jar 複製到 WEB-INF\lib
step04:
在 WEB-INF\web.xml 檔增加下列程式碼
<servlet>
<servlet-name>Connector</servlet-name>
<servlet-class>net.fckeditor.connector.ConnectorServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Connector</servlet-name>
<url-pattern> /fckeditor/editor/filemanager/connectors/*
</url-pattern>
</servlet-mapping>step05:
在 WEB-INF 目錄下增加一個檔案 FCKeditor.tld , 內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>
The FCKeditor Tag Library offers a very convenient way to create
several FCKeditor instances with different configurations.
Additionally, you can check for user-based capabilities.
</description>
<display-name>FCKeditor Tag Library</display-name>
<tlib-version>2.4</tlib-version>
<short-name>FCK</short-name>
<uri>http://java.fckeditor.net</uri>
<tag>
<description>
Creates a FCKeditor instance with the given parameters. Any
parameter except instanceName which is empty or contains
whitespaces only will be ignored.
</description>
<display-name>editor</display-name>
<name>editor</name>
<tag-class>net.fckeditor.tags.EditorTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
The unique instance name under which the editor can be
retrieved through the API.
</description>
<name>instanceName</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
Width of the FCKeditor instance in the browser window.
</description>
<name>width</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
Height of the FCKeditor instance in the browser window.
</description>
<name>height</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
The toolbar set which shall be displayed to the user.
</description>
<name>toolbarSet</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
The path/folder in which the editor is deployed under
the given context. The context path will be attached
automatically. (e.g. '/fckeditor')
</description>
<name>basePath</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
Passes any content to the FCKeditor document. Use the
jsp:attribute tag for large inline content. \r, \n, and
\t will be truncated.
</description>
<name>value</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<example><![CDATA[
<FCK:editor instanceName="editorDefault" height="500px" />]]>
</example>
</tag>
<tag>
<description>
Sets a config property of the editor to the supplied value.
You may provide any attribute you want for the editor. Set
at least one attribute per tag or several attributes with
one tag. This tag can only be nested within an editor tag.
For all configuration options click
<![CDATA[<a href="http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/Configuration/Configuration_Options">here</a>]]>.
</description>
<display-name>config</display-name>
<name>config</name>
<tag-class>net.fckeditor.tags.ConfigTag</tag-class>
<body-content>empty</body-content>
<dynamic-attributes>true</dynamic-attributes>
<example>
<![CDATA[
<FCK:config SkinPath="/skins/silver/" AutoDetectLanguage="true" />]]>
</example>
</tag>
<tag>
<description>
Displays session-dependent and compatibility-related
information. This tag is intended for developers only.
Response messages cannot be localized, they are English
only.
</description>
<display-name>check</display-name>
<name>check</name>
<tag-class>net.fckeditor.tags.CheckTag</tag-class>
<body-content>empty</body-content>
<attribute>
<description>
Provide the feature name you want to check. Valid
features are [FileUpload, FileBrowsing,
CompatibleBrowser]
</description>
<name>command</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<example><![CDATA[
<FCK:check command="FileUpload" />
<FCK:check command="CompatibleBrowser" />]]>
</example>
</tag>
</taglib>step06:
建立一個 fck.jsp 來測試, 程式碼如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="/WEB-INF/FCKeditor.tld" prefix="FCK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="show.jsp" method="post" target="_blank">
<FCK:editor instanceName="fckEdit" width="700" height="500" toolbarSet="Default" />
<input type="submit" value="Submit">
</form>
</body>
</html>fckeditor-java-2.4.1 的設定比較簡便, 就連上載檔案, 也只要在step04: 設定好 servlet : Connector 就可以了. 不需要太繁瑣. 不過若要上載檔案, 不要忘了要在 classpath 建立一個檔案 fckeditor.properties , 內容如下:
fckeditor.toolbarSet=Basic
connector.userActionImpl=net.fckeditor.requestcycle.impl.UserActionImpl
connector.userFilesPath=/userfiles
connector.resourceType.file.extensions.allowed=txt|doc
connector.resourceType.image.extensions.allowed=jpg|png這樣才不會有問題. 參數說明:
connector.userFilesPath : 是伺服器上要存放檔案的地方.如 /userfiles
connector.resourceType.file.extensions.allowed : 可以限制上傳到/userfiles/file 目錄的附加檔名有那些
connector.resourceType.image.extensions.allowed: 可以限制上傳到/userfiles/image 目錄的附加檔名有那些
因為上載的檔案只能存在這個 application WEBcontent 的目錄內, 所以若將 java 打包成 war 檔在 JBoss 上使用, 則會有問題.
2010年12月10日 星期五
非英數名稱圖片顯示處理方案
這幾天在利用之前寫得上傳套件上傳圖片時遇到了問題(感謝專案組員抓到這個bug)
那就是上傳的圖片若以中文、日文等非英文、數字的名稱上傳遇到麻煩
在WebApplication會顯示找不到檔案的錯誤訊息
我先試著將上傳圖片名稱改成URL上的UTF-8編碼
String fileName = java.net.URLEncoder.encode( file.getName(), "UTF-8");
file.renameTo (new File(file.getParent(), fileName));
雖然檔名的確與URL上經過編碼的檔案名稱相符,不過還是一樣找不到檔案... = =
既然上傳有問題,那剩下的方法就是禁止上傳檔名有編碼問題的檔案囉
最後做法是用正規表示法協助,讀取上傳檔案的檔名加以判斷
有問題的檔案就直接刪除了,並回覆訊息請求上傳有正確檔名的檔案
// 判斷是否含有非英數編碼的字元
String stringEncodeJudge = "^.*[^0-9a-zA-Z~!@#\\$%\\^&\\*\\(\\)_\\+\\{\\}\\|:\\\"<>\\?\\[\\];',\\.\\/].*$";
if(!filePart.getFileName().matches(stringEncodeJudge)){
session.setAttribute(name, saveDirectory + "/" + filePart.getFileName());
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}else{
tempMessage = fileName + "請改成英文、數字組成的檔名!!<br />";
Message += (tempMessage);
File disposedFile = new File(saveDirectory + "/" + filePart.getFileName());
disposedFile.delete();
}
那就是上傳的圖片若以中文、日文等非英文、數字的名稱上傳遇到麻煩
在WebApplication會顯示找不到檔案的錯誤訊息
我先試著將上傳圖片名稱改成URL上的UTF-8編碼
String fileName = java.net.URLEncoder.encode( file.getName(), "UTF-8");
file.renameTo (new File(file.getParent(), fileName));
雖然檔名的確與URL上經過編碼的檔案名稱相符,不過還是一樣找不到檔案... = =
既然上傳有問題,那剩下的方法就是禁止上傳檔名有編碼問題的檔案囉
最後做法是用正規表示法協助,讀取上傳檔案的檔名加以判斷
有問題的檔案就直接刪除了,並回覆訊息請求上傳有正確檔名的檔案
// 判斷是否含有非英數編碼的字元
String stringEncodeJudge = "^.*[^0-9a-zA-Z~!@#\\$%\\^&\\*\\(\\)_\\+\\{\\}\\|:\\\"<>\\?\\[\\];',\\.\\/].*$";
if(!filePart.getFileName().matches(stringEncodeJudge)){
session.setAttribute(name, saveDirectory + "/" + filePart.getFileName());
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}else{
tempMessage = fileName + "請改成英文、數字組成的檔名!!<br />";
Message += (tempMessage);
File disposedFile = new File(saveDirectory + "/" + filePart.getFileName());
disposedFile.delete();
}
2010年12月7日 星期二
[轉載] 使用response.sendRedirect的注意事項
首先我們要明白用response.sendRedirect做轉向的原理
它其實是向瀏覽器發送一個特殊的Header,然後由瀏覽器來做轉向,轉到指定的頁面
所以用sendRedirect時,瀏覽器的地址欄上可以看到地址的變化
用<jsp:forward page=""/>則不同,它是直接在server做的
瀏覽器並不知道,也不和瀏覽器打交道,這從瀏覽器的地址並不變化可以看出。
所以使用response.sendRedirect時就需要注意以下兩點:
1,在使用response.sendRedirect時,前面不能有HTML輸出
這並不是絕對的,不能有HTML輸出其實是指不能有HTML被送到了瀏覽器
事實上現在的server都有cache機制,一般在8K(我是說JSP SERVER)
這就意味著,除非你關閉了cache,或者你使用了out.flush()強制刷新
那麼在使用sendRedirect之前,有少量的 HTML輸出也是允許的
如果報錯說,「一些信息已經被submitted」(原文忘了)
那麼,你就要注意看了,前面是不是有過多的HTML輸出了
2,在response.sendRedirect之後,應該緊跟一句return;
我們已經知道response.sendRedirect是通過瀏覽器來做轉向的
所以只有在頁面處理完成後,才會有實際的動作
既然你已經要做轉向了,那麼後的輸出還有什麼意義呢?
而且有可能會因為後面的輸出導致轉向失敗
它其實是向瀏覽器發送一個特殊的Header,然後由瀏覽器來做轉向,轉到指定的頁面
所以用sendRedirect時,瀏覽器的地址欄上可以看到地址的變化
用<jsp:forward page=""/>則不同,它是直接在server做的
瀏覽器並不知道,也不和瀏覽器打交道,這從瀏覽器的地址並不變化可以看出。
所以使用response.sendRedirect時就需要注意以下兩點:
1,在使用response.sendRedirect時,前面不能有HTML輸出
這並不是絕對的,不能有HTML輸出其實是指不能有HTML被送到了瀏覽器
事實上現在的server都有cache機制,一般在8K(我是說JSP SERVER)
這就意味著,除非你關閉了cache,或者你使用了out.flush()強制刷新
那麼在使用sendRedirect之前,有少量的 HTML輸出也是允許的
如果報錯說,「一些信息已經被submitted」(原文忘了)
那麼,你就要注意看了,前面是不是有過多的HTML輸出了
2,在response.sendRedirect之後,應該緊跟一句return;
我們已經知道response.sendRedirect是通過瀏覽器來做轉向的
所以只有在頁面處理完成後,才會有實際的動作
既然你已經要做轉向了,那麼後的輸出還有什麼意義呢?
而且有可能會因為後面的輸出導致轉向失敗
2010年12月2日 星期四
JSP & Servlet 檔案上傳範例-使用Cos套件
Java EE規格雖然是針對網路應用開發的,但直到 Servlet 3.0 前都沒有提供檔案上傳的功能
在目前還沒打算轉換到Servlet 3.0 的打算下,先研究使用套件的解決方案
時下較為流行的上傳套件共有 Cos、FileUpload、SmartUpload
Cos套件是 O'reilly 公司提供,可至 http://www.servlets.com/cos 下載套件(cos-26Dec2008.zip)
FileUpload 的下載位置: http://commons.apache.org/fileupload/
FileUpload 使用須具備的套件:http://commons.apache.org/io/
SmartUpload 的網站已關閉,大概未來也不會繼續開發了
FileUpload 具有較多的功能,也有提供方便用來做 Ajax 應用的 listener
不過純以上傳效率來說Cos是最優秀的,贏過其他套件不少
這次實作選擇使用Cos
先提到上傳的介紹
撰寫上傳的功能必須先撰寫好一個頁面
並由表單以POST的形式將資料傳送到上傳處理的頁面
表單的編碼方式也與一般有所不同,enctype屬性共有三種值
1.application/x-www-form-urlencoded 是預設的編碼方式,它只處理表單域裡的value值
採用這種編碼方式的表單會將表單域的值處理成URL編碼方式
2.multipart/form-data 編碼會以二進制流的方式來處理表單數據
它把文件域指定文件的內容也封裝到請求參數里
一旦設置了這種方式,就無法透過HttpServletRequest.getParameter()請求獲取請求參數
3.text/plain 編碼方式當表單的action屬性為mailto:URL的形式時比較方便
這種方式主要適用於直接通過表單發送郵件的方式
這次實作的內容為撰寫一個提供圖片及附件上傳的功能
先寫好提供表單的網頁
designUploader.jsp======
.....
<form action="UploadHandler" method="POST" enctype="multipart/form-data">
<label>正面設計圖:</label><input type="file" id="f_pic" name="f_pic" value="" width="20" /><br />
<label>背面設計圖:</label><input type="file" id="b_pic" name="b_pic" value="" width="20" /><br />
<label>左側設計圖:</label><input type="file" id="l_pic" name="l_pic" value="" width="20" /><br />
<label>右側設計圖:</label><input type="file" id="r_pic" name="r_pic" value="" width="20" /><br />
<label>附件:</label><input type="file" id="appendix0" name="appendix0" value="" width="20" /><br />
<input type="submit" value="Upload" /><br />
<span>${uploadInfo.message}</span>
</form>
....
解壓縮下載好的 Cos套件,並將 lib 裡的 cos.jar 複製到 WEB_INF 目錄底下的 lib 裡
接著撰寫處理上傳的頁面
使用Cos時,可以使用兩個類別來進行上傳工作:
1. MultipartRequest 2. MultipartParser
一般情況下使用MultipartRequest即可,不需要複雜的設定就可以輕鬆上傳物件
實際上MultipartRequest封裝了MultipartParser
在構造MultipartRequest實例時,建構了MultipartParser實例
建構 parser 的過程取得了上傳的 InputStream,但並不會真正讀取
然後透過 MultipartParser的readNextPart() method,從request流中讀取數據
區別出流中的參數域和文件域
如果是參數的話用ParamPart類封裝;如果是文件的話用FilePart封裝
此時如果設置了重命名策略的話,則在Server端新建一個新命名的空白物件
接著用FilePart的writeTo(saveDir)方法將流數據寫到硬碟中,文件上傳完成
MultipartRequest 範例:
UploadHandler.java=========
package tw.vencs;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.http.*;
import com.oreilly.servlet.MultipartRequest;
public class UploadHandler extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
//uploaded files' store dictionary
String saveDirectory = .....
//limit of file capacity
int maxPostSize=5*1024*1024;
String FileName=null;
//declare file type
String ContentType=null;
//count numbers of file uploaded
int count = 0;
MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8");
//取得所有上傳之檔案輸入型態名稱及敘述
Enumeration filename=multi.getFileNames();
Enumeration filesdc=multi.getParameterNames();
while(filename.hasMoreElements()){
String name=(String)filename.nextElement();
String dc=(String)filesdc.nextElement();
FileName=multi.getFilesystemName(name);
ContentType=multi.getContentType(name);
if(FileName!=null){
count++;
}
}
............
}
}
當 MultipartRequest 建立好的時候就是檔案上傳完畢的時候
不設定檔案上傳大小時,最大上傳限制預設是 1mb
如果上傳檔案大小超過設定的大小時會丟出例外
雖然看API的說明是會丟出 ExceededSizeException,不過結果似乎是裝在IOException
命名策略是指上傳的檔案遇到了檔名重複時的處理方式
有自訂處理方式可以自行撰寫,如下:
RandomFileRenamePolicy.java===========
package tw.vencs;
import java.io.File;
import java.util.Date;
import com.oreilly.servlet.multipart.FileRenamePolicy;
public class RandomFileRenamePolicy implements FileRenamePolicy {
public File rename(File file) {
String body="";
String ext="";
Date date = new Date();
int pot=file.getName().lastIndexOf(".");
if(pot!=-1){
body= date.getTime() +"";
ext=file.getName().substring(pot);
}else{
body=(new Date()).getTime()+"";
ext="";
}
String newName=body+ext;
file=new File(file.getParent(),newName);
return file;
}
}
這個命名規則會取得時間來做為隨機命名的依據,接著將MultipartRequest改寫成:
RandomFileRenamePolicy rfrp=new RandomFileRenamePolicy();
MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8",rfrp);
因為我實作的需求是希望能限制上傳檔案的大小
不管是怎麼樣的上傳套件都沒辦法再接收資料前得知檔案大小
畢竟那等同於Server端直接入侵Client讀取資料,權限上不會被允許
JavaScript可以做到檢查檔案大小的功能
<script language="JavaScript">
//這裡控制要檢查的項目,true表示要檢查,false表示不檢查
var isCheckImageType = true; //是否檢查圖片副檔名
var isCheckImageWidth = true; //是否檢查圖片寬度
var isCheckImageHeight = true; //是否檢查圖片高度
var isCheckImageSize = true; //是否檢查圖片檔案大小
var ImageSizeLimit = 100000; //上傳上限,單位:byte
var ImageWidthLimit = 1200; //圖片寬度上限
var ImageHeightLimit = 1000; //圖片高度上限
function checkFile() {
var f = document.FileForm;
var re = /\.(jpg|gif)$/i; //允許的圖片副檔名
if (isCheckImageType && !re.test(f.file1.value)) {
alert("只允許上傳JPG或GIF影像檔");
} else {
var img = new Image();
img.onload = checkImage;
img.src = f.file1.value;
}
}
function checkImage() {
if (isCheckImageWidth && this.width > ImageWidthLimit) {
showMessage('寬度','px',this.width,ImageWidthLimit);
} else if (isCheckImageHeight && this.height > ImageHeightLimit) {
showMessage('高度','px',this.height,ImageHeightLimit);
} else if (isCheckImageSize && this.fileSize > ImageSizeLimit) {
showMessage('檔案大小','kb',this.fileSize/1000,ImageSizeLimit/1000);
} else {
document.FileForm.submit();
}
}
function showMessage(kind,unit,real,limit) {
var msg = "您所選擇的圖片kind為 real unit\n超過了上傳上限 limit unit\n不允許上傳!"
alert(msg.replace(/kind/,kind).replace(/unit/g,unit).replace(/real/,real).replace(/limit/,limit));
}
</script>
不過預防Client端沒開啟JavaScript的情形,我再查了些資料
java.io套件裡的 FileInputStream 有available() method可以檢查request送來的資料流大小
而不必等到資料流全傳輸完畢
可惜的是看過 FileInputStream 的API後知道available()的限制
它處理的是沒遭遇到network blocking時所送來的一串資料流,只是現實上常有blocking發生...
MultipartRequest最多是在全部的物件上傳完後再來檢查
既然傳輸資料的過程已經免不了了,那我希望至少是當一個檔案上傳時就先檢查
而省掉全傳輸完所耗費的資源及時間
所以將 UploadHandler 改寫成以 MultipartParser 處理
UploadHandler.java=========
package tw.vencs;
import java.io.File;
import java.io.IOException;
import javax.servlet.http.*;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.Part;
public class UploadHandler extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
HttpSession session = request.getSession();
//uploaded files' store dictionary
String saveDirectory = null;
//try &catch to prevent login session has been canceled
File dir = new File(saveDirectory);
int pictureSize = Integer.parseInt(getServletConfig().getInitParameter("pictureSize"));
int appendixSize = Integer.parseInt(getServletConfig().getInitParameter("appendixSize"));
int maxPostSize=pictureSize*4 + appendixSize; //limit of file capacity
String Message = "";
MultipartParser fileMap = null;
String fileName = null;
try{
fileMap = new MultipartParser(request, maxPostSize);
fileMap.setEncoding("UTF-8");
Part part = null;
while ((part = fileMap.readNextPart()) != null){
if(part.isFile()){ //if this inputstream is from HTML file upload element
FilePart filePart = (FilePart) part;
filePart.setRenamePolicy(new DefaultFileRenamePolicy());
String name = part.getName(); //get HTML elements' name of form
fileName = filePart.getFileName(); //get name of uploaded object
if(fileName != null && !fileName.equals("")){
String fileType = fileName.substring(
fileName.lastIndexOf('.')+1).toLowerCase(); //get file type
String tempMessage = null;
if(name.substring(2).equals("pic")){
File dir = new File(saveDirectory);
dir.mkdirs();
//write data into designated file directory
long size = filePart.writeTo(dir);
if(fileType.equals("jpg") || fileType.equals("jpeg") ||
fileType.equals("png") || fileType.equals("bmp") ||
fileType.equals("gif") || fileType.equals("ai")){
if(size > (long)pictureSize){
tempMessage = fileName + "大小超過限制!!<br />";
Message += (tempMessage);
File disposedFile = new File(
saveDirectory + "/" + filePart.getFileName());
disposedFile.delete();
}else{
session.setAttribute(
name, saveDirectory + "/" + filePart.getFileName());
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}
}else{
tempMessage = fileName + "不是圖檔格式!!<br />";
Message += tempMessage;
File disposedFile = new File(saveDirectory + "/" + fileName);
disposedFile.delete();
}
}else{ //appendix check
File appendDir = new File(saveDirectory + "/appendix");
appendDir.mkdirs();
int size = (int)filePart.writeTo(appendDir);
int uAppendSize = 0; //store uploaded data size info
if(session.getAttribute("append") != null &&
!session.getAttribute("append").equals("")){
File[] fList = appendDir.listFiles();
for (int j = 0; j < fList.length; j++){
FileInputStream in = new FileInputStream(fList[j]);
uAppendSize += in.available();
in.close(); //must close FileInputStream after use it
}
}
if(size > (appendixSize - uAppendSize)){
tempMessage = fileName + "大小超過限制!!<br />";
Message += tempMessage;
File disposedFile = new File(
saveDirectory + "/appendix/" + filePart.getFileName());
disposedFile.delete();
}else{
String temp = "";
if(session.getAttribute("append")!= null &&
!session.getAttribute("append").equals("")){
temp = session.getAttribute("append").toString();
}
temp += (saveDirectory +
"/appendix/" + fileName + "=" + filePart.getFileName() + ",");
session.setAttribute("append", temp);
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}
}
}
}
}
}catch(IOException e){
session.setAttribute("targetPage", "designUploader.jsp");
session.setAttribute("Message", "上傳檔案總大小超過限制!!<br />");
response.sendRedirect("designUploader.jsp");
return;
}
session.setAttribute("targetPage", "designUploader.jsp");
session.setAttribute("Message", Message);
response.sendRedirect("designUploader.jsp");
return;
}
}
}
預設的命名規則是遇到覆蓋已存在的同名檔案
因為我想處理的只有上傳域的物件
假使表單裡還有其他文件域的資訊,如下處理:
if (part.isParam()){ //if this inputstream is from normal HTML input element
ParamPart paramPart = (ParamPart) part;
String value = paramPart.getStringValue(); //get param's value
在目前還沒打算轉換到Servlet 3.0 的打算下,先研究使用套件的解決方案
時下較為流行的上傳套件共有 Cos、FileUpload、SmartUpload
Cos套件是 O'reilly 公司提供,可至 http://www.servlets.com/cos 下載套件(cos-26Dec2008.zip)
FileUpload 的下載位置: http://commons.apache.org/fileupload/
FileUpload 使用須具備的套件:http://commons.apache.org/io/
SmartUpload 的網站已關閉,大概未來也不會繼續開發了
FileUpload 具有較多的功能,也有提供方便用來做 Ajax 應用的 listener
不過純以上傳效率來說Cos是最優秀的,贏過其他套件不少
這次實作選擇使用Cos
先提到上傳的介紹
撰寫上傳的功能必須先撰寫好一個頁面
並由表單以POST的形式將資料傳送到上傳處理的頁面
表單的編碼方式也與一般有所不同,enctype屬性共有三種值
1.application/x-www-form-urlencoded 是預設的編碼方式,它只處理表單域裡的value值
採用這種編碼方式的表單會將表單域的值處理成URL編碼方式
2.multipart/form-data 編碼會以二進制流的方式來處理表單數據
它把文件域指定文件的內容也封裝到請求參數里
一旦設置了這種方式,就無法透過HttpServletRequest.getParameter()請求獲取請求參數
3.text/plain 編碼方式當表單的action屬性為mailto:URL的形式時比較方便
這種方式主要適用於直接通過表單發送郵件的方式
這次實作的內容為撰寫一個提供圖片及附件上傳的功能
先寫好提供表單的網頁
designUploader.jsp======
.....
<form action="UploadHandler" method="POST" enctype="multipart/form-data">
<label>正面設計圖:</label><input type="file" id="f_pic" name="f_pic" value="" width="20" /><br />
<label>背面設計圖:</label><input type="file" id="b_pic" name="b_pic" value="" width="20" /><br />
<label>左側設計圖:</label><input type="file" id="l_pic" name="l_pic" value="" width="20" /><br />
<label>右側設計圖:</label><input type="file" id="r_pic" name="r_pic" value="" width="20" /><br />
<label>附件:</label><input type="file" id="appendix0" name="appendix0" value="" width="20" /><br />
<input type="submit" value="Upload" /><br />
<span>${uploadInfo.message}</span>
</form>
....
解壓縮下載好的 Cos套件,並將 lib 裡的 cos.jar 複製到 WEB_INF 目錄底下的 lib 裡
接著撰寫處理上傳的頁面
使用Cos時,可以使用兩個類別來進行上傳工作:
1. MultipartRequest 2. MultipartParser
一般情況下使用MultipartRequest即可,不需要複雜的設定就可以輕鬆上傳物件
實際上MultipartRequest封裝了MultipartParser
在構造MultipartRequest實例時,建構了MultipartParser實例
建構 parser 的過程取得了上傳的 InputStream,但並不會真正讀取
然後透過 MultipartParser的readNextPart() method,從request流中讀取數據
區別出流中的參數域和文件域
如果是參數的話用ParamPart類封裝;如果是文件的話用FilePart封裝
此時如果設置了重命名策略的話,則在Server端新建一個新命名的空白物件
接著用FilePart的writeTo(saveDir)方法將流數據寫到硬碟中,文件上傳完成
MultipartRequest 範例:
UploadHandler.java=========
package tw.vencs;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.http.*;
import com.oreilly.servlet.MultipartRequest;
public class UploadHandler extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
//uploaded files' store dictionary
String saveDirectory = .....
//limit of file capacity
int maxPostSize=5*1024*1024;
String FileName=null;
//declare file type
String ContentType=null;
//count numbers of file uploaded
int count = 0;
MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8");
//取得所有上傳之檔案輸入型態名稱及敘述
Enumeration filename=multi.getFileNames();
Enumeration filesdc=multi.getParameterNames();
while(filename.hasMoreElements()){
String name=(String)filename.nextElement();
String dc=(String)filesdc.nextElement();
FileName=multi.getFilesystemName(name);
ContentType=multi.getContentType(name);
if(FileName!=null){
count++;
}
}
............
}
}
當 MultipartRequest 建立好的時候就是檔案上傳完畢的時候
不設定檔案上傳大小時,最大上傳限制預設是 1mb
如果上傳檔案大小超過設定的大小時會丟出例外
雖然看API的說明是會丟出 ExceededSizeException,不過結果似乎是裝在IOException
命名策略是指上傳的檔案遇到了檔名重複時的處理方式
有自訂處理方式可以自行撰寫,如下:
RandomFileRenamePolicy.java===========
package tw.vencs;
import java.io.File;
import java.util.Date;
import com.oreilly.servlet.multipart.FileRenamePolicy;
public class RandomFileRenamePolicy implements FileRenamePolicy {
public File rename(File file) {
String body="";
String ext="";
Date date = new Date();
int pot=file.getName().lastIndexOf(".");
if(pot!=-1){
body= date.getTime() +"";
ext=file.getName().substring(pot);
}else{
body=(new Date()).getTime()+"";
ext="";
}
String newName=body+ext;
file=new File(file.getParent(),newName);
return file;
}
}
這個命名規則會取得時間來做為隨機命名的依據,接著將MultipartRequest改寫成:
RandomFileRenamePolicy rfrp=new RandomFileRenamePolicy();
MultipartRequest multi = new MultipartRequest(request,saveDirectory,maxPostSize, "UTF-8",rfrp);
因為我實作的需求是希望能限制上傳檔案的大小
不管是怎麼樣的上傳套件都沒辦法再接收資料前得知檔案大小
畢竟那等同於Server端直接入侵Client讀取資料,權限上不會被允許
JavaScript可以做到檢查檔案大小的功能
<script language="JavaScript">
//這裡控制要檢查的項目,true表示要檢查,false表示不檢查
var isCheckImageType = true; //是否檢查圖片副檔名
var isCheckImageWidth = true; //是否檢查圖片寬度
var isCheckImageHeight = true; //是否檢查圖片高度
var isCheckImageSize = true; //是否檢查圖片檔案大小
var ImageSizeLimit = 100000; //上傳上限,單位:byte
var ImageWidthLimit = 1200; //圖片寬度上限
var ImageHeightLimit = 1000; //圖片高度上限
function checkFile() {
var f = document.FileForm;
var re = /\.(jpg|gif)$/i; //允許的圖片副檔名
if (isCheckImageType && !re.test(f.file1.value)) {
alert("只允許上傳JPG或GIF影像檔");
} else {
var img = new Image();
img.onload = checkImage;
img.src = f.file1.value;
}
}
function checkImage() {
if (isCheckImageWidth && this.width > ImageWidthLimit) {
showMessage('寬度','px',this.width,ImageWidthLimit);
} else if (isCheckImageHeight && this.height > ImageHeightLimit) {
showMessage('高度','px',this.height,ImageHeightLimit);
} else if (isCheckImageSize && this.fileSize > ImageSizeLimit) {
showMessage('檔案大小','kb',this.fileSize/1000,ImageSizeLimit/1000);
} else {
document.FileForm.submit();
}
}
function showMessage(kind,unit,real,limit) {
var msg = "您所選擇的圖片kind為 real unit\n超過了上傳上限 limit unit\n不允許上傳!"
alert(msg.replace(/kind/,kind).replace(/unit/g,unit).replace(/real/,real).replace(/limit/,limit));
}
</script>
不過預防Client端沒開啟JavaScript的情形,我再查了些資料
java.io套件裡的 FileInputStream 有available() method可以檢查request送來的資料流大小
而不必等到資料流全傳輸完畢
可惜的是看過 FileInputStream 的API後知道available()的限制
它處理的是沒遭遇到network blocking時所送來的一串資料流,只是現實上常有blocking發生...
MultipartRequest最多是在全部的物件上傳完後再來檢查
既然傳輸資料的過程已經免不了了,那我希望至少是當一個檔案上傳時就先檢查
而省掉全傳輸完所耗費的資源及時間
所以將 UploadHandler 改寫成以 MultipartParser 處理
UploadHandler.java=========
package tw.vencs;
import java.io.File;
import java.io.IOException;
import javax.servlet.http.*;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.Part;
public class UploadHandler extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
HttpSession session = request.getSession();
//uploaded files' store dictionary
String saveDirectory = null;
//try &catch to prevent login session has been canceled
File dir = new File(saveDirectory);
int pictureSize = Integer.parseInt(getServletConfig().getInitParameter("pictureSize"));
int appendixSize = Integer.parseInt(getServletConfig().getInitParameter("appendixSize"));
int maxPostSize=pictureSize*4 + appendixSize; //limit of file capacity
String Message = "";
MultipartParser fileMap = null;
String fileName = null;
try{
fileMap = new MultipartParser(request, maxPostSize);
fileMap.setEncoding("UTF-8");
Part part = null;
while ((part = fileMap.readNextPart()) != null){
if(part.isFile()){ //if this inputstream is from HTML file upload element
FilePart filePart = (FilePart) part;
filePart.setRenamePolicy(new DefaultFileRenamePolicy());
String name = part.getName(); //get HTML elements' name of form
fileName = filePart.getFileName(); //get name of uploaded object
if(fileName != null && !fileName.equals("")){
String fileType = fileName.substring(
fileName.lastIndexOf('.')+1).toLowerCase(); //get file type
String tempMessage = null;
if(name.substring(2).equals("pic")){
File dir = new File(saveDirectory);
dir.mkdirs();
//write data into designated file directory
long size = filePart.writeTo(dir);
if(fileType.equals("jpg") || fileType.equals("jpeg") ||
fileType.equals("png") || fileType.equals("bmp") ||
fileType.equals("gif") || fileType.equals("ai")){
if(size > (long)pictureSize){
tempMessage = fileName + "大小超過限制!!<br />";
Message += (tempMessage);
File disposedFile = new File(
saveDirectory + "/" + filePart.getFileName());
disposedFile.delete();
}else{
session.setAttribute(
name, saveDirectory + "/" + filePart.getFileName());
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}
}else{
tempMessage = fileName + "不是圖檔格式!!<br />";
Message += tempMessage;
File disposedFile = new File(saveDirectory + "/" + fileName);
disposedFile.delete();
}
}else{ //appendix check
File appendDir = new File(saveDirectory + "/appendix");
appendDir.mkdirs();
int size = (int)filePart.writeTo(appendDir);
int uAppendSize = 0; //store uploaded data size info
if(session.getAttribute("append") != null &&
!session.getAttribute("append").equals("")){
File[] fList = appendDir.listFiles();
for (int j = 0; j < fList.length; j++){
FileInputStream in = new FileInputStream(fList[j]);
uAppendSize += in.available();
in.close(); //must close FileInputStream after use it
}
}
if(size > (appendixSize - uAppendSize)){
tempMessage = fileName + "大小超過限制!!<br />";
Message += tempMessage;
File disposedFile = new File(
saveDirectory + "/appendix/" + filePart.getFileName());
disposedFile.delete();
}else{
String temp = "";
if(session.getAttribute("append")!= null &&
!session.getAttribute("append").equals("")){
temp = session.getAttribute("append").toString();
}
temp += (saveDirectory +
"/appendix/" + fileName + "=" + filePart.getFileName() + ",");
session.setAttribute("append", temp);
tempMessage = fileName + "上傳成功!!<br />";
Message += tempMessage;
}
}
}
}
}
}catch(IOException e){
session.setAttribute("targetPage", "designUploader.jsp");
session.setAttribute("Message", "上傳檔案總大小超過限制!!<br />");
response.sendRedirect("designUploader.jsp");
return;
}
session.setAttribute("targetPage", "designUploader.jsp");
session.setAttribute("Message", Message);
response.sendRedirect("designUploader.jsp");
return;
}
}
}
預設的命名規則是遇到覆蓋已存在的同名檔案
因為我想處理的只有上傳域的物件
假使表單裡還有其他文件域的資訊,如下處理:
if (part.isParam()){ //if this inputstream is from normal HTML input element
ParamPart paramPart = (ParamPart) part;
String value = paramPart.getStringValue(); //get param's value
2010年12月1日 星期三
[轉載] HttpClient 4.x 表單檔案上傳
最近手邊的專案漸漸由 HttpClient 3.x 轉移至改用 HttpClient 4.x 實作 Http 操作。
使用 Commons 專案的東西,捨棄原始的 HttpURLConnection 就是為了較輕鬆地處理 Http 操作的功能。
我們可以用下列方式透過網頁表單上傳檔案:
HttpClient client = new DefaultHttpClient();
HttpPost post =new HttpPost("http://example.com/upload.do");
MultipartEntity e = new MultipartEntity();
try{
e.addPart("field1", new StringBody("some value"));
e.addPart("file", new FileBody(new File("your_file_path")));
}catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
post.setEntity(e);
client.execute(post);
它的寫法是相當容易的!不過有個小缺點,那就是 HttpClient 4.x 某個版本將 Multipart Entity 實作獨立至 HttpMime 專案
http://hc.apache.org/httpcomponents-client/httpmime/index.html
所以,您要使用單純的表單上傳功能,就需要同時再引用 HttpMime 相關的 Library
C:\http-mime>dir
磁碟區 C 中的磁碟沒有標籤。
磁碟區序號: A8B9-DC3D
C:\http-mime 的目錄
2010/01/01 下午 01:20 <DIR> .
2010/01/01 下午 01:20 <DIR> ..
2009/12/31 下午 02:22 345,048 apache-mime4j-0.6.jar
2009/12/31 下午 02:22 25,993 httpmime-4.0.jar
2009/12/31 下午 02:22 2,255 jcip-annotations-1.0.jar
3 個檔案 373,296 位元組
2 個目錄 70,042,320,896 位元組可用
雖然,這些 Library 僅 300 多 kb,對一般的應用程式開發來說並不算什麼。
但總覺得是否能讓它單純一些呢?我們並不需要完整而強大的 Http Mime,
有時我們只需要足夠的功能罷了!隨著這個想法,試著實作一個極簡版本的 ThinMultipartEntity。
可以不需引用 HttpMime 就上傳檔案。
package com.google.jplurk.net;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
public class ThinMultipartEntity implements HttpEntity {
static Log logger = LogFactory.getLog(ThinMultipartEntity.class);
private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
private String boundary = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean isSetLast = false;
boolean isSetFirst = false;
public ThinMultipartEntity() {
StringBuffer buf = new StringBuffer();
Random rand = new Random();
for (int i = 0; i < 30; i++) {
buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
this.boundary = buf.toString();
}
public void writeFirstBoundary(){
if(!isSetFirst){
try {
out.write(("--" + boundary + "\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
isSetFirst = true;
}
public void writeLastBoundary() {
if(isSetLast){
return ;
}
try {
out.write(("\r\n--" + boundary + "--\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
isSetLast = true;
}
public void addPart(String key, String value) {
writeFirstBoundary();
try {
out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes());
out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes());
out.write(value.getBytes());
out.write(("\r\n--" + boundary + "\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
public void addPart(String key, File value) {
writeFirstBoundary();
try {
out.write(("Content-Disposition: form-data; name=\""
+ key+"\"; filename=\"" + value.getName()+ "\"\r\n").getBytes());
out.write("Content-Type: application/octet-stream\r\n".getBytes());
out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
FileInputStream fin = new FileInputStream(value);
int data = fin.read();
while(data !=-1){
out.write(data);
data = fin.read();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
public long getContentLength() {
writeLastBoundary();
return out.toByteArray().length;
}
public Header getContentType() {
return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
}
public boolean isChunked() {
return false;
}
public boolean isRepeatable() {
return false;
}
public boolean isStreaming() {
return false;
}
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(out.toByteArray());
}
public Header getContentEncoding() {
return null;
}
public void consumeContent() throws IOException,
UnsupportedOperationException {
if (isStreaming()) {
throw new UnsupportedOperationException(
"Streaming entity does not implement #consumeContent()");
}
}
public InputStream getContent() throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException(
"Multipart form entity does not implement #getContent()");
}
}
PS. 因為是極簡版的,有許多細節是被省略的
PS. 只試過上傳一個檔 XD
使用 Commons 專案的東西,捨棄原始的 HttpURLConnection 就是為了較輕鬆地處理 Http 操作的功能。
我們可以用下列方式透過網頁表單上傳檔案:
HttpClient client = new DefaultHttpClient();
HttpPost post =new HttpPost("http://example.com/upload.do");
MultipartEntity e = new MultipartEntity();
try{
e.addPart("field1", new StringBody("some value"));
e.addPart("file", new FileBody(new File("your_file_path")));
}catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
post.setEntity(e);
client.execute(post);
它的寫法是相當容易的!不過有個小缺點,那就是 HttpClient 4.x 某個版本將 Multipart Entity 實作獨立至 HttpMime 專案
http://hc.apache.org/httpcomponents-client/httpmime/index.html
所以,您要使用單純的表單上傳功能,就需要同時再引用 HttpMime 相關的 Library
C:\http-mime>dir
磁碟區 C 中的磁碟沒有標籤。
磁碟區序號: A8B9-DC3D
C:\http-mime 的目錄
2010/01/01 下午 01:20 <DIR> .
2010/01/01 下午 01:20 <DIR> ..
2009/12/31 下午 02:22 345,048 apache-mime4j-0.6.jar
2009/12/31 下午 02:22 25,993 httpmime-4.0.jar
2009/12/31 下午 02:22 2,255 jcip-annotations-1.0.jar
3 個檔案 373,296 位元組
2 個目錄 70,042,320,896 位元組可用
雖然,這些 Library 僅 300 多 kb,對一般的應用程式開發來說並不算什麼。
但總覺得是否能讓它單純一些呢?我們並不需要完整而強大的 Http Mime,
有時我們只需要足夠的功能罷了!隨著這個想法,試著實作一個極簡版本的 ThinMultipartEntity。
可以不需引用 HttpMime 就上傳檔案。
package com.google.jplurk.net;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
public class ThinMultipartEntity implements HttpEntity {
static Log logger = LogFactory.getLog(ThinMultipartEntity.class);
private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
private String boundary = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean isSetLast = false;
boolean isSetFirst = false;
public ThinMultipartEntity() {
StringBuffer buf = new StringBuffer();
Random rand = new Random();
for (int i = 0; i < 30; i++) {
buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
this.boundary = buf.toString();
}
public void writeFirstBoundary(){
if(!isSetFirst){
try {
out.write(("--" + boundary + "\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
isSetFirst = true;
}
public void writeLastBoundary() {
if(isSetLast){
return ;
}
try {
out.write(("\r\n--" + boundary + "--\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
isSetLast = true;
}
public void addPart(String key, String value) {
writeFirstBoundary();
try {
out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes());
out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes());
out.write(value.getBytes());
out.write(("\r\n--" + boundary + "\r\n").getBytes());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
public void addPart(String key, File value) {
writeFirstBoundary();
try {
out.write(("Content-Disposition: form-data; name=\""
+ key+"\"; filename=\"" + value.getName()+ "\"\r\n").getBytes());
out.write("Content-Type: application/octet-stream\r\n".getBytes());
out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
FileInputStream fin = new FileInputStream(value);
int data = fin.read();
while(data !=-1){
out.write(data);
data = fin.read();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
public long getContentLength() {
writeLastBoundary();
return out.toByteArray().length;
}
public Header getContentType() {
return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
}
public boolean isChunked() {
return false;
}
public boolean isRepeatable() {
return false;
}
public boolean isStreaming() {
return false;
}
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(out.toByteArray());
}
public Header getContentEncoding() {
return null;
}
public void consumeContent() throws IOException,
UnsupportedOperationException {
if (isStreaming()) {
throw new UnsupportedOperationException(
"Streaming entity does not implement #consumeContent()");
}
}
public InputStream getContent() throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException(
"Multipart form entity does not implement #getContent()");
}
}
PS. 因為是極簡版的,有許多細節是被省略的
PS. 只試過上傳一個檔 XD
2010年11月27日 星期六
[轉載] Apache HttpClient 4.x 使用 GET, POST 範例
先前我曾經發了一篇介紹如何抓取網頁的教學:
利用Jakarta.Commons.HttpClient抓取網頁、網站(Parser),不過當時用的是 HttpClient 3.1。
經過時間的演進 Apache 已經在 14 August 2009 發佈:HttpComponents HttpClient 4.0 (GA)。
由 3.1 到 4.0 因為底層幾乎全部重新改寫,所以也使有些舊的程式無法使用。
這篇就是我自己寫的一個簡單範例。
在看範例之前先把一些重要連結整理給大家:
想知道這次到底更動了哪些東西可以看:Apache HttpClient 首頁
官方的 Tutorial 在:Apache HttpClient Tutorial
而 API DOC、說明文件則在:Apache HttpClient apidocs
相關的程式碼、jar 檔在:HttpComponents HttpClient 4.0 (GA)
注意,在寫程式前必需先將四個 jar 檔正確匯入,最後兩個(*)是選用,
請參考:http://hc.apache.org/httpcomponents-client-ga/quickstart.html:
第一個是傳回在 google 查詢 httpclient 的結果。
第二則是傳回台大圖書館查詢 Head First Java 的結果。
HttpClient 4 HTTP request
HttpClient 4 使用POST方式提交普通表單數據的例子
關鍵字:Apache、HttpClient、4、教學、示範、範例、設定
參考資料:
利用Jakarta.Commons.HttpClient抓取網頁、網站(Parser),不過當時用的是 HttpClient 3.1。
經過時間的演進 Apache 已經在 14 August 2009 發佈:HttpComponents HttpClient 4.0 (GA)。
由 3.1 到 4.0 因為底層幾乎全部重新改寫,所以也使有些舊的程式無法使用。
這篇就是我自己寫的一個簡單範例。
在看範例之前先把一些重要連結整理給大家:
想知道這次到底更動了哪些東西可以看:Apache HttpClient 首頁
官方的 Tutorial 在:Apache HttpClient Tutorial
而 API DOC、說明文件則在:Apache HttpClient apidocs
相關的程式碼、jar 檔在:HttpComponents HttpClient 4.0 (GA)
注意,在寫程式前必需先將四個 jar 檔正確匯入,最後兩個(*)是選用,
請參考:http://hc.apache.org/httpcomponents-client-ga/quickstart.html:
- commons-logging-x.x.x.jar
- commons-codec-x.x.x.jar
- httpcore-x.x.x.jar
- httpclient-x.x.x.jar
- apache-mime4j-x.x.x.jar (*)
- httpmime-x.x.x.jar (*)
第一個是傳回在 google 查詢 httpclient 的結果。
第二則是傳回台大圖書館查詢 Head First Java 的結果。
package demo.httpclient;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
* Apache HttpClient 4.x 使用 GET, POST 查詢網頁的範例
*
* @author werdna at http://werdna1222coldcodes.blogspot.com/
*/
public class HttpClientDemo extends DefaultHttpClient {
public static void main(String[] args) throws IOException {
DefaultHttpClient demo = new DefaultHttpClient();
demo.getParams().setParameter("http.protocol.content-charset", "UTF-8");
// Get Request Example,取得 google 查詢 httpclient 的結果
HttpGet httpGet = new HttpGet("http://www.google.com.tw/search?q=httpclinet");
HttpResponse response = demo.execute(httpGet);
String responseString = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 如果回傳是 200 OK 的話才輸出
System.out.println(responseString);
} else {
System.out.println(response.getStatusLine());
}
// Post Request Example,查詢台大圖書館書籍
ArrayList<NameValuePair> pairList = new ArrayList<NameValuePair>();
pairList.add(new BasicNameValuePair("searchtype", "t"));
pairList.add(new BasicNameValuePair("searchscope", "keyword"));
pairList.add(new BasicNameValuePair("searcharg", "Head First Java"));
pairList.add(new BasicNameValuePair("SORT", "D"));
HttpPost httpPost = new HttpPost("http://tulips.ntu.edu.tw:1081/search*cht/a?");
StringEntity entity = new StringEntity(URLEncodedUtils.format(pairList, "UTF-8"));
httpPost.setEntity(entity);
response = demo.execute(httpPost);
responseString = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 如果回傳是 200 OK 的話才輸出
System.out.println(responseString);
} else {
System.out.println(response.getStatusLine());
}
}
}更多的程式範例可以參考其教學: HttpClient 4 HTTP request
HttpClient 4 使用POST方式提交普通表單數據的例子
關鍵字:Apache、HttpClient、4、教學、示範、範例、設定
參考資料:
2010年11月7日 星期日
自訂編碼標籤範例
如果不將Eclipse的環境編碼改成UTF-8,似乎轉碼會有問題
因應這種情況寫了自訂標籤來協助處理
encrypt.tld=========
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.2</tlib-version>
<short-name>encryptlib</short-name>
<uri>encrypt-tags</uri>
<tag>
<description>URL get/post encrypting</description>
<name>encrypt</name>
<tag-class>tw.vencs.EncrptTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>encryptValue</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
EncrptTag.java===============
package tw.vencs;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class EncrptTag extends SimpleTagSupport{
private String encryptValue;
public void doTag() throws JspException {
try {
encryptValue = new String(encryptValue.getBytes("iso-8859-1"),"UTF-8");
getJspContext().getOut().write(encryptValue);
} catch (IOException e) {
Logger.getLogger(EncrptTag.class.getName()).log(Level.SEVERE, null, e);
throw new RuntimeException();
}
}
public void setEncryptValue(String encryptValue) {
this.encryptValue = encryptValue;
}
}
Demo.jsp===============
.....
<%@ taglib prefix="e" uri="encrypt-tags" %>
.....
<body>
.....
....=<e:encrypt encryptValue="${param.itemName}"/>
</body>
.....
因應這種情況寫了自訂標籤來協助處理
encrypt.tld=========
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.2</tlib-version>
<short-name>encryptlib</short-name>
<uri>encrypt-tags</uri>
<tag>
<description>URL get/post encrypting</description>
<name>encrypt</name>
<tag-class>tw.vencs.EncrptTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>encryptValue</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
EncrptTag.java===============
package tw.vencs;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class EncrptTag extends SimpleTagSupport{
private String encryptValue;
public void doTag() throws JspException {
try {
encryptValue = new String(encryptValue.getBytes("iso-8859-1"),"UTF-8");
getJspContext().getOut().write(encryptValue);
} catch (IOException e) {
Logger.getLogger(EncrptTag.class.getName()).log(Level.SEVERE, null, e);
throw new RuntimeException();
}
}
public void setEncryptValue(String encryptValue) {
this.encryptValue = encryptValue;
}
}
Demo.jsp===============
.....
<%@ taglib prefix="e" uri="encrypt-tags" %>
.....
<body>
.....
....=<e:encrypt encryptValue="${param.itemName}"/>
</body>
.....
2010年11月5日 星期五
[轉錄]Tomcat Post / Get 中文編碼處理方法 (中文亂碼問題)
最近在把原本佈署在 Resin 的 Java Web Application移植到 Tomcat 時,有許多原本正常的中文get / post 功能,都變的異常,只要是透過 get / post 得到的資料,就會變亂碼
剛好也驗證了 Resin 在Character Encoding的部分做的較好
而看看網路上大家的問題,也可以知道 Tomcat 在編碼處理的部分 真是讓很多人頭痛
主要的原因是,TOMCAT 預設都是用 ISO-8859-1 的編碼方式來傳遞資訊
這個問題,可以解決的方式整理如下:
1. JSP 頁面的編碼宣告需與實際儲存檔案時用的編碼一致
這是個很好玩的問題~ 有些人 在頁面宣告用 big5 or UTF-8 or... 字集
但是,檔案實際儲存的方式與該編碼不同,則 頁面當然會出現亂碼的問題
2. 自行轉碼
form method=POST or GET 時 因為Tomcat預設編碼是ISO-8859-1的關係
直接用 <%=request.getParameter("firstname")%> 取值,中文字會變亂碼
(你傳的明明是UTF-8, 但是讀取時 確用 ISO-8859-1來解譯.. 當然有問題囉)
要使用 <%=new String(request.getParameter("firstname").getBytes( "ISO-8859-1"), "UTF-8")%>這種方式來轉換編碼才會正常 (假設,頁面用的編碼為UTF-8時)
3. 透過設定 server.xml URIEncoding的方式 來處理 GET 亂碼問題
在 tomcat 的設定檔內 ./conf/server.xml 修改下列設定的話,則透過GET的方式傳遞參數
的話,Tomcat會用你指定的編碼方式來對待用get方法傳遞的參數,而不是使用預設的ISO-8859-1 (get 的方式是透過 url parameter傳遞參數)
但是,因為只處理 URIEncoding, 所以若使用 Post 傳遞,還是會有問題
URIEncoding: This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
P.S. 若使用 GET Method, 且設定了URIEncoding 的話就不用再做上述二的自行轉碼動作,如果多加該處理,一樣會有亂碼的問題
P.S.2 如果整合apache和Tomcar的話,需要將兩者溝通用的8009 port也設置URIEncoding
4. 使用 request.setCharacterEncoding 來處理 GET / POST 的亂碼問題
在處理的頁面,加上下列的處理即可,明確的告訴Tomcat request的編碼為何,不要一廂情願的使用ISO-8859-1來解讀~
<%request.setCharacterEncoding("UTF-8");%>
javax.servlet.servletRequest.setCharacterEncoding(string env)
Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader(). Otherwise, it has no effect.
5. 使用 filter 來處理 GET / POST 的亂碼問題
這個方法是最方便的,只要在 web.xml 裡面 透過 filter 的設定,讓每個網頁都能
透過 filter 的處理一體適用所有編碼問題的解決
方法如下:
a. 目前 Tomcat 提供的範例裡面,就有現成的 Encoding Filter, 位置如下:
.\Tomcat 6.0\webapps\examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class
b. 所以,只要把該 class 檔,copy 到你的 web application 的
.\WEB-INF\classes\filter 下,再搭配 web.xml 的filter 設定,
就可以讓所有符合 url pattern 的網頁,都能自動apply所想要的編碼設定(透過 encoding這個 parameter來設定),是不是很方便呢 ?
(這個功能 就像是自動把上述第四點的 setCharacterEncoding 語句 自動apply到各頁面囉)
------------
以上~ 就從 Resin Porting 到 Tomcat 的經驗,當然是使用 filter 的方式 一次解決~ 不然的話 就得每個網頁每個網頁去改囉~
延伸資料:
FAQ/CharacterEncoding
http://jim.blogsome.com/2005/05/27/jsp-chinese-character-solution/
http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=752
剛好也驗證了 Resin 在Character Encoding的部分做的較好
而看看網路上大家的問題,也可以知道 Tomcat 在編碼處理的部分 真是讓很多人頭痛
主要的原因是,TOMCAT 預設都是用 ISO-8859-1 的編碼方式來傳遞資訊
這個問題,可以解決的方式整理如下:
1. JSP 頁面的編碼宣告需與實際儲存檔案時用的編碼一致
這是個很好玩的問題~ 有些人 在頁面宣告用 big5 or UTF-8 or... 字集
但是,檔案實際儲存的方式與該編碼不同,則 頁面當然會出現亂碼的問題
2. 自行轉碼
form method=POST or GET 時 因為Tomcat預設編碼是ISO-8859-1的關係
直接用 <%=request.getParameter("firstname")%> 取值,中文字會變亂碼
(你傳的明明是UTF-8, 但是讀取時 確用 ISO-8859-1來解譯.. 當然有問題囉)
要使用 <%=new String(request.getParameter("firstname").getBytes( "ISO-8859-1"), "UTF-8")%>這種方式來轉換編碼才會正常 (假設,頁面用的編碼為UTF-8時)
3. 透過設定 server.xml URIEncoding的方式 來處理 GET 亂碼問題
在 tomcat 的設定檔內 ./conf/server.xml 修改下列設定的話,則透過GET的方式傳遞參數
的話,Tomcat會用你指定的編碼方式來對待用get方法傳遞的參數,而不是使用預設的ISO-8859-1 (get 的方式是透過 url parameter傳遞參數)
但是,因為只處理 URIEncoding, 所以若使用 Post 傳遞,還是會有問題
URIEncoding: This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
P.S. 若使用 GET Method, 且設定了URIEncoding 的話就不用再做上述二的自行轉碼動作,如果多加該處理,一樣會有亂碼的問題
P.S.2 如果整合apache和Tomcar的話,需要將兩者溝通用的8009 port也設置URIEncoding
4. 使用 request.setCharacterEncoding 來處理 GET / POST 的亂碼問題
在處理的頁面,加上下列的處理即可,明確的告訴Tomcat request的編碼為何,不要一廂情願的使用ISO-8859-1來解讀~
<%request.setCharacterEncoding("UTF-8");%>
javax.servlet.servletRequest.setCharacterEncoding(string env)
Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader(). Otherwise, it has no effect.
5. 使用 filter 來處理 GET / POST 的亂碼問題
這個方法是最方便的,只要在 web.xml 裡面 透過 filter 的設定,讓每個網頁都能
透過 filter 的處理一體適用所有編碼問題的解決
方法如下:
a. 目前 Tomcat 提供的範例裡面,就有現成的 Encoding Filter, 位置如下:
.\Tomcat 6.0\webapps\examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class
b. 所以,只要把該 class 檔,copy 到你的 web application 的
.\WEB-INF\classes\filter 下,再搭配 web.xml 的filter 設定,
就可以讓所有符合 url pattern 的網頁,都能自動apply所想要的編碼設定(透過 encoding這個 parameter來設定),是不是很方便呢 ?
(這個功能 就像是自動把上述第四點的 setCharacterEncoding 語句 自動apply到各頁面囉)
------------
以上~ 就從 Resin Porting 到 Tomcat 的經驗,當然是使用 filter 的方式 一次解決~ 不然的話 就得每個網頁每個網頁去改囉~
延伸資料:
FAQ/CharacterEncoding
http://jim.blogsome.com/2005/05/27/jsp-chinese-character-solution/
http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=752
2010年10月13日 星期三
非 Servlet 的Java程式碼取得JSP request 物件
在JSP中常有藉有 request 物件來獲得所需物件(如parameter、cookie)的需求
在Servlet裡我們會將 request、response 物件傳入所要覆寫的傳輸協定裡,如下:
public void doPost(HttpServletRequest request, HttpServletResponse response){
....
}
那假如要在非Servlet取得request,我們就自行將request物件傳入即可,例如:
public void setPageInfo(HttpServletRequest request){
if(request.getParameter("page") != null){
page = Integer.parseInt(request.getParameter("page"));
}
request.setAttribute("page", page);
}
之後在目標JSP頁面中就可以用標籤或 EL呼叫method:
<jsp:setProperty property="pageInfo" name="view" value="${pageContext.request}" />
當然不只可以用標籤來進行就是了,<%=xxx.xxx(request)%> 這樣子的處理也OK
不過需要注意一下
如果要做 set 或 動態性的處理似乎只能在有傳入request的該method才能做
推測是傳入request物件表示在請求過程的處理
如果是回應已經送出的話,可能就只能使用 getParameter() 之類的method
在Servlet裡我們會將 request、response 物件傳入所要覆寫的傳輸協定裡,如下:
public void doPost(HttpServletRequest request, HttpServletResponse response){
....
}
那假如要在非Servlet取得request,我們就自行將request物件傳入即可,例如:
public void setPageInfo(HttpServletRequest request){
if(request.getParameter("page") != null){
page = Integer.parseInt(request.getParameter("page"));
}
request.setAttribute("page", page);
}
之後在目標JSP頁面中就可以用標籤或 EL呼叫method:
<jsp:setProperty property="pageInfo" name="view" value="${pageContext.request}" />
當然不只可以用標籤來進行就是了,<%=xxx.xxx(request)%> 這樣子的處理也OK
不過需要注意一下
如果要做 set 或 動態性的處理似乎只能在有傳入request的該method才能做
推測是傳入request物件表示在請求過程的處理
如果是回應已經送出的話,可能就只能使用 getParameter() 之類的method
2010年10月12日 星期二
[轉載] JSTL fmt 格式標籤庫使用介紹
在該標籤庫中的標籤一共有 12 個,被分為了兩類,分別是:
國際化核心標籤:
<fmt:setLocale> 、 <fmt:bundle> 、 <fmt:setBundle> 、 <fmt:message> 、 <fmt:param> 、 <fmt:requestEncoding> 。
格式化標籤:
<fmt:timeZone> 、 <fmt:setTimeZone> 、 <fmt:formatNumber> 、 <fmt:parseNumber> 、 <fmt:formatDate> 、 <fmt:parseDate> 。
下面只選擇其中常見的一些標籤和屬性進行介紹。
下面看一個示例:
<fmt:setLocale value="zh_TW"/>
表示設置本地環境為繁體中文。
根據 Locale 環境的不同將查找不同後綴的資源配置文件,這點在國際化的任何技術上都是一致的,通常來說,這兩種標籤單獨使用是沒有意義的,它們都會與 I18N formatting 標籤庫中的其他標籤配合使用。它們的屬性和描述如下所示:
下面看一個示例
<fmt:setLocale value="zh_CN"/>
<fmt:setBundle basename="applicationMessage" var="applicationBundle"/>
該示例將會查找一個名為 applicationMessage_zh_CN.properties 的資源配置文件,來作為顯示的 Resource 綁定。
下面看一個示例:
<fmt:setBundle basename="applicationMessage" var="applicationBundle"/>
<fmt:bundle basename="applicationAllMessage">
<fmt:message key="userName" />
<fmt:message key="passWord" bundle="${applicationBundle}" />
</fmt:bundle>
該示例使用了兩種資源配置文件的綁定的做法,「 applicationMessage 」資源配置文件利用 <fmt:setBundle> 標籤被賦於了變量「 applicationBundle 」,而作為 <fmt:bundle> 標籤定義的「 applicationAllMessage 」資源配置文件作用於其標籤體內的顯示。
第一個 <fmt:message> 標籤將使用「 applicationAllMessage 」資源配置文件中「鍵」為「 userName 」的信息顯示。
第二個 <fmt:message> 標籤雖然被定義在 <fmt:bundle> 標籤體內,但是它使用了 bundle 屬性,因此將指定之前由 <fmt:setBundle> 標籤保存的「 applicationMessage 」資源配置文件,該「鍵」為「 passWord 」的信息顯示。
<fmt:param> 標籤有兩種使用版本,一種是直接將參數值寫在 value 屬性中,另一種是將參數值寫在標籤體內。
<fmt:formatNumber> 標籤實際是對應 java.util.NumberFormat 類, type 屬性的可能值包括 currency (貨幣)、 number (數字)和 percent (百分比)。
下面看一個示例。
<fmt:formatNumber value="1000.888" type="currency" var="money"/>
<fmt:formatNumber value="12" type="currency" pattern="$.00"/> //$12.00
<fmt:formatNumber value="12" type="currency" pattern="$.0#"/> //$12.0
<fmt:formatNumber value="1234567890" type="currency"/> //$1,234,567,890.00(那個貨幣的符號和當前web服務器的 local 設定有關)
<fmt:formatNumber value="123456.7891" pattern="#,#00.0#"/> // 123,456.79
<fmt:formatNumber value="123456.7" pattern="#,#00.0#"/> // 123,456.7
<fmt:formatNumber value="123456.7" pattern="#,#00.00#"/> // 123,456.70
<fmt:formatNumber value="12" type="percent" /> // 1,200% (type 可以是currency、 number、 和percent)。
它的屬性和描述如下表所示:
下面看一個示例。
<fmt:parseNumber value="15%" type="percent" var="num"/>
解析之後的結果為「 0.15 」。
<fmt:formatDate> 標籤與 <fmt:timeZone> 、 <fmt:setTimeZone> 兩組標籤的關係密切。若沒有指定 timeZone屬性, 也可以通過 <fmt:timeZone> 、 <fmt:setTimeZone> 兩組標籤設定的時區來格式化最後的結果。
它的屬性和描述如下表所示:
<fmt:parseNumber> 和 <fmt:parseDate> 兩組標籤都實現解析字符串為一個具體對象實例的工作,因此,這兩組解析標籤對 var 屬性的字符串參數要求非常嚴格。就 JSP 頁面的表示層前段來說,處理這種解析本不屬於份內之事,因此 <fmt:parseNumber> 和 <fmt:parseDate> 兩組標籤應該儘量少用,替代工作的地方應該在服務器端表示層的後段,比如在 Servlet 中。
國際化核心標籤:
<fmt:setLocale> 、 <fmt:bundle> 、 <fmt:setBundle> 、 <fmt:message> 、 <fmt:param> 、 <fmt:requestEncoding> 。
格式化標籤:
<fmt:timeZone> 、 <fmt:setTimeZone> 、 <fmt:formatNumber> 、 <fmt:parseNumber> 、 <fmt:formatDate> 、 <fmt:parseDate> 。
下面只選擇其中常見的一些標籤和屬性進行介紹。
用於設置本地化環境的 <fmt:setLocale> 標籤
<fmt:setLocale> 標籤用於設置 Locale 環境。它的屬性和描述如下表所示:| 屬性 | 描述 |
| value | Locale 環境的指定,可以是 java.util.Locale 或 String 類型的實例 |
| scope | Locale 環境變量的作用範圍(可選) |
<fmt:setLocale value="zh_TW"/>
表示設置本地環境為繁體中文。
用於資源文件綁定的 <fmt:bundle> 、 <fmt:setBundle> 標籤
這兩組標籤用於資源配置文件的綁定,唯一不同的是 <fmt:bundle> 標籤將資源配置文件綁定於它標籤體中的顯示,<fmt:setBundle> 標籤則允許將資源配置文件保存為一個變量,在之後的工作可以根據該變量來進行。根據 Locale 環境的不同將查找不同後綴的資源配置文件,這點在國際化的任何技術上都是一致的,通常來說,這兩種標籤單獨使用是沒有意義的,它們都會與 I18N formatting 標籤庫中的其他標籤配合使用。它們的屬性和描述如下所示:
| 屬性 | 描述 |
| basename | 資源配置文件的指定,只需要指定文件名而無須擴展名,二組標籤共有的屬性 |
| var | <fmt:setBundle> 獨有的屬性,用於保存資源配置文件為一個變量 |
| scope | 變量的作用範圍 |
<fmt:setLocale value="zh_CN"/>
<fmt:setBundle basename="applicationMessage" var="applicationBundle"/>
該示例將會查找一個名為 applicationMessage_zh_CN.properties 的資源配置文件,來作為顯示的 Resource 綁定。
用於顯示資源配置文件信息的 <fmt:message> 標籤
用於信息顯示的標籤,將顯示資源配置文件中定義的信息。它的屬性和描述如下所示:| 屬性 | 描述 |
| key | 資源配置文件的「鍵」指定 |
| bundle | 若使用 <fmt:setBundle> 保存了資源配置文件,該屬性就可以從保存的資源配置文件中進行查找 |
| var | 將顯示信息保存為一個變量 |
| scope | 變量的作用範圍 |
<fmt:setBundle basename="applicationMessage" var="applicationBundle"/>
<fmt:bundle basename="applicationAllMessage">
<fmt:message key="userName" />
<fmt:message key="passWord" bundle="${applicationBundle}" />
</fmt:bundle>
該示例使用了兩種資源配置文件的綁定的做法,「 applicationMessage 」資源配置文件利用 <fmt:setBundle> 標籤被賦於了變量「 applicationBundle 」,而作為 <fmt:bundle> 標籤定義的「 applicationAllMessage 」資源配置文件作用於其標籤體內的顯示。
第一個 <fmt:message> 標籤將使用「 applicationAllMessage 」資源配置文件中「鍵」為「 userName 」的信息顯示。
第二個 <fmt:message> 標籤雖然被定義在 <fmt:bundle> 標籤體內,但是它使用了 bundle 屬性,因此將指定之前由 <fmt:setBundle> 標籤保存的「 applicationMessage 」資源配置文件,該「鍵」為「 passWord 」的信息顯示。
用於參數傳遞的 <fmt:param> 標籤
<fmt:param> 標籤應該位於 <fmt:message> 標籤內,將為該消息標籤提供參數值。它只有一個屬性 value 。<fmt:param> 標籤有兩種使用版本,一種是直接將參數值寫在 value 屬性中,另一種是將參數值寫在標籤體內。
用於為請求設置字符編碼的 <fmt:requestEncoding> 標籤
<fmt:requestEncoding> 標籤用於為請求設置字符編碼。它只有一個屬性 value ,在該屬性中可以定義字符編碼。用於設定時區的 <fmt:timeZone> 、 <fmt:setTimeZone> 標籤
這兩組標籤都用於設定一個時區。唯一不同的是 <fmt:timeZone> 標籤將使得在其標籤體內的工作可以使用該時區設置, <fmt:setBundle> 標籤則允許將時區設置保存為一個變量,在之後的工作可以根據該變量來進行。它們的屬性和描述如下表所示:| 屬性 | 描述 |
| value | 時區的設置 |
| var | <fmt:setTimeZone> 獨有的屬性,用於保存時區為一個變量 |
| scope | 變量的作用範圍 |
用於格式化數字的 <fmt:formatNumber> 標籤
<fmt: formatNumber > 標 簽用於格式化數字。它的屬性和描述如下表所示:| 屬性 | 描述 |
| value | 格式化的數字,該數值可以是 String 類型或 java.lang.Number 類型的實例 |
| type | 格式化的類型 |
| pattern | 格式化模式 |
| var | 結果保存變量 |
| scope | 變量的作用範圍 |
| maxIntegerDigits | 指定格式化結果的最大值 |
| minIntegerDigits | 指定格式化結果的最小值 |
| maxFractionDigits | 指定格式化結果的最大值,帶小數 |
| minFractionDigits | 指定格式化結果的最小值,帶小數 |
下面看一個示例。
<fmt:formatNumber value="1000.888" type="currency" var="money"/>
該結果將被保存在「 money 」變量中,將根據 Locale 環境顯示當地的貨幣格式。
<fmt:formatNumber value="12" type="currency" pattern="$.00"/> //$12.00
<fmt:formatNumber value="12" type="currency" pattern="$.0#"/> //$12.0
<fmt:formatNumber value="1234567890" type="currency"/> //$1,234,567,890.00(那個貨幣的符號和當前web服務器的 local 設定有關)
<fmt:formatNumber value="123456.7891" pattern="#,#00.0#"/> // 123,456.79
<fmt:formatNumber value="123456.7" pattern="#,#00.0#"/> // 123,456.7
<fmt:formatNumber value="123456.7" pattern="#,#00.00#"/> // 123,456.70
<fmt:formatNumber value="12" type="percent" /> // 1,200% (type 可以是currency、 number、 和percent)。
用於解析數字的 <fmt:parseNumber> 標籤
<fmt:parseNumber> 標籤用於解析一個數字,並將結果作為 java.lang.Number 類的實例返回。 <fmt:parseNumber> 標籤看起來和 <fmt:formatNumber> 標籤的作用正好相反。它的屬性和描述如下表所示:
| 屬性 | 描述 |
| value | 將被解析的字符串 |
| type | 解析格式化的類型 |
| pattern | 解析格式化模式 |
| var | 結果保存變量,類型為 java.lang.Number |
| scope | 變量的作用範圍 |
| parseLocale | 以本地化的形式來解析字符串,該屬性的內容應為 String 或 java.util.Locale 類型的實例 |
<fmt:parseNumber value="15%" type="percent" var="num"/>
解析之後的結果為「 0.15 」。
用於格式化日期的 <fmt:formatDate> 標籤
<fmt:formatDate> 標籤用於格式化日期。它的屬性和描述如下表所示:| 屬性 | 描述 |
| value | 格式化的日期,該屬性的內容應該是 java.util.Date 類型的實例 |
| type | 格式化的類型 |
| pattern | 格式化模式 |
| var | 結果保存變量 |
| scope | 變量的作用範圍 |
| timeZone | 指定格式化日期的時區 |
用於解析日期的 <fmt:parseDate> 標籤
<fmt:parseDate> 標籤用於解析一個日期,並將結果作為 java.lang.Date 類型的實例返回。 <fmt:parseDate> 標籤看起來和 <fmt:formatDate> 標籤的作用正好相反。它的屬性和描述如下表所示:
| 屬性 | 描述 |
| value | 將被解析的字符串 |
| type | 解析格式化的類型 |
| pattern | 解析格式化模式 |
| var | 結果保存變量,類型為 java.lang.Date |
| scope | 變量的作用範圍 |
| parseLocale | 以本地化的形式來解析字符串,該屬性的內容為 String 或 java.util.Locale 類型的實例 |
| timeZone | 指定解析格式化日期的時區 |
JSP 頁面透過JSTL格式標籤庫取得整數
在一般的JSTL頁面,若是以 EL 進行運算會取得有小數點的 Float 數值
例如 ${6/9} 這行EL會在JSP頁面中轉換成0.6666666666666666
在不寫Java程式碼於JSP頁面的前提下,我們可以考慮使用JSTL的格式標籤庫
使用時先引入JSTL的格式標籤庫:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
這樣就可以在JSP頁面中使用formatNumber標籤
<fmt:formatNumber type="number" value="${requestScope.page / 10}" maxFractionDigits="0" var="num" />
maxFractionDigits指定成0,表示取0個小數點,也就是取整數的意思
這樣就能夠在網頁中用EL: ${var} 取得運算後的整數值
例如 ${6/9} 這行EL會在JSP頁面中轉換成0.6666666666666666
在不寫Java程式碼於JSP頁面的前提下,我們可以考慮使用JSTL的格式標籤庫
使用時先引入JSTL的格式標籤庫:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
這樣就可以在JSP頁面中使用formatNumber標籤
<fmt:formatNumber type="number" value="${requestScope.page / 10}" maxFractionDigits="0" var="num" />
maxFractionDigits指定成0,表示取0個小數點,也就是取整數的意思
這樣就能夠在網頁中用EL: ${var} 取得運算後的整數值
2010年9月14日 星期二
Tomcat 6.0 環境配置 DBCP 與 JNDI 使用
取得資料庫連線的過程必須經過網路連線、協定交換等多個步驟
過程中的耗費在單機使用者上也許感覺不太出來
但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費
改善連線效能的方法之一就是建立儲存了連線實例的連接池
盡量使用已經開啟的連線,重複使用以減低資源上的浪費
Tomcat的DBCP連接池機制提供了連接池建立的功能
JNDI 處理 Java EE 平台上資源的分散式運算的協調與資源上查詢
JNDI 更詳細的介紹可以在 SUN 的文件裡看到
接著搭配兩者來示範建立連接池
Java EE將資料庫處理的相關行為規範在 javax.sql.DataSource 介面
取得Connecion實例等的處理會由實作介面的物件來處理,我們只需取得DataSource實例即可
範例以建立一個能被封裝散佈的WAR檔的Web Application為例:
context.xml=========
<?xml version="1.0" encoding="UTF-8"?>
<Resource name="jdbc/sql" auth="Container" type="javax.sql.DataSource"
maxActive="60" maxIdle="20" maxWait="10000"
username="***" password="***" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/***?useUnicode=true&characterEncoding=UTF8" />
</Context>
web.xml===========
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
.......
<resource-ref>
<res-ref-name>jdbc/sql</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
....
</web-app>
DatabaseBean.java====
package tw.vencs;
import java.sql.*;
import java.util.logging.*;
import javax.naming.*;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource;
public DatabaseBean(){
try{
Context initContext = new InitialContext();
//java:comp/env 表示應用程式環境項目
Context envContext = (Context)initContext.lookup("java:/comp/env");
//查找 jdbc/sql 對應的 DataSource 物件
dataSource = (DataSource)envContext.lookup("jdbc/sql");
}catch(NamingException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
public boolean isConnectedOK(){
boolean ok = false;
Connection conn = null;
try{
// 透過 DataSource 物件取得連線
conn = dataSource.getConnection();
if(!conn.isClosed()){
ok = true;
}
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}finally{
if(conn != null){
try{
conn.close();
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return ok;
}
}
conn.jsp========
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="db" class="tw.vencs.DatabaseBean" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>db Test</title>
</head>
<body>
<c:choose>
<c:when test="${db.connectedOK }">連線成功</c:when>
<c:otherwise>失敗</c:otherwise>
</c:choose>
</body>
</html>
context.xml需要放置在Web應用程式根目錄的META-INF目錄裡
<Context>裡的 path 填上相對於容器網路根目錄的應用程式目錄位置
<Resource>標籤裡有許多與連線設定相關的屬性
name 存取辨識用的 JNDI 名稱
auth 認證方式,一般為Container
type 數據集的型別,使用標準的javax.sql.DataSource
maxActive 連接池中最多的Connection實例數量,0為不限制
maxIdle 最大空閒連接數,也就是一個連接池裡最少要有的Connection實例數量
maxWait 等待可用的Connection實例,最大的等待時間,-1為不限制
username 資料庫使用者名稱
password 資料庫密碼
driverClassName JDBC Driver 類別名稱
url JDBC URL,注意因資訊寫在xml裡,"&"需改成"&"才符合XML規範
web.xml裡的設定提供 JNDI 查找時需要的環境資訊
減少物件建立所需要設置的參數的麻煩
DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
那些資訊由context.xml設定,只須給資料庫管理人員管理
程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來
過程中的耗費在單機使用者上也許感覺不太出來
但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費
改善連線效能的方法之一就是建立儲存了連線實例的連接池
盡量使用已經開啟的連線,重複使用以減低資源上的浪費
Tomcat的DBCP連接池機制提供了連接池建立的功能
JNDI 處理 Java EE 平台上資源的分散式運算的協調與資源上查詢
JNDI 更詳細的介紹可以在 SUN 的文件裡看到
接著搭配兩者來示範建立連接池
Java EE將資料庫處理的相關行為規範在 javax.sql.DataSource 介面
取得Connecion實例等的處理會由實作介面的物件來處理,我們只需取得DataSource實例即可
範例以建立一個能被封裝散佈的WAR檔的Web Application為例:
context.xml=========
<?xml version="1.0" encoding="UTF-8"?>
<!-- 應用程式目錄是Demo -->
<Context antiJARLocking="true" path="/Demo"><Resource name="jdbc/sql" auth="Container" type="javax.sql.DataSource"
maxActive="60" maxIdle="20" maxWait="10000"
username="***" password="***" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/***?useUnicode=true&characterEncoding=UTF8" />
</Context>
web.xml===========
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
.......
<resource-ref>
<res-ref-name>jdbc/sql</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
....
</web-app>
DatabaseBean.java====
package tw.vencs;
import java.sql.*;
import java.util.logging.*;
import javax.naming.*;
import javax.sql.DataSource;
public class DatabaseBean {
private DataSource dataSource;
public DatabaseBean(){
try{
Context initContext = new InitialContext();
//java:comp/env 表示應用程式環境項目
Context envContext = (Context)initContext.lookup("java:/comp/env");
//查找 jdbc/sql 對應的 DataSource 物件
dataSource = (DataSource)envContext.lookup("jdbc/sql");
}catch(NamingException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
throw new RuntimeException();
}
}
public boolean isConnectedOK(){
boolean ok = false;
Connection conn = null;
try{
// 透過 DataSource 物件取得連線
conn = dataSource.getConnection();
if(!conn.isClosed()){
ok = true;
}
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}finally{
if(conn != null){
try{
conn.close();
}catch(SQLException ex){
Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return ok;
}
}
conn.jsp========
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="db" class="tw.vencs.DatabaseBean" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>db Test</title>
</head>
<body>
<c:choose>
<c:when test="${db.connectedOK }">連線成功</c:when>
<c:otherwise>失敗</c:otherwise>
</c:choose>
</body>
</html>
context.xml需要放置在Web應用程式根目錄的META-INF目錄裡
<Context>裡的 path 填上相對於容器網路根目錄的應用程式目錄位置
<Resource>標籤裡有許多與連線設定相關的屬性
name 存取辨識用的 JNDI 名稱
auth 認證方式,一般為Container
type 數據集的型別,使用標準的javax.sql.DataSource
maxActive 連接池中最多的Connection實例數量,0為不限制
maxIdle 最大空閒連接數,也就是一個連接池裡最少要有的Connection實例數量
maxWait 等待可用的Connection實例,最大的等待時間,-1為不限制
username 資料庫使用者名稱
password 資料庫密碼
driverClassName JDBC Driver 類別名稱
url JDBC URL,注意因資訊寫在xml裡,"&"需改成"&"才符合XML規範
web.xml裡的設定提供 JNDI 查找時需要的環境資訊
減少物件建立所需要設置的參數的麻煩
DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
那些資訊由context.xml設定,只須給資料庫管理人員管理
程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來
2010年9月8日 星期三
Eclipse 環境下Tomcat 發生did not find a matching property 錯誤解決方法
環境配置:
eclipse-jee-helios-win32-x86_64.zip
apache-tomcat-6.0.29
錯誤訊息大致如下:
[SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:demo' did not find a matching property.
解決方法:
1.對 Servers 欄位右鍵取得 properties-> General
點擊Switch Location 讓 Location = /Servers/Tomcat v6.0 Server at localhost.server
2.Project Explorer -> Servers 打開此目錄下的 Tomcat v6.0 Server at localhost.server
3.Server Options -> Check "Publish module contexts to separate XML files"
eclipse-jee-helios-win32-x86_64.zip
apache-tomcat-6.0.29
錯誤訊息大致如下:
[SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:demo' did not find a matching property.
解決方法:
1.對 Servers 欄位右鍵取得 properties-> General
點擊Switch Location 讓 Location = /Servers/Tomcat v6.0 Server at localhost.server
2.Project Explorer -> Servers 打開此目錄下的 Tomcat v6.0 Server at localhost.server
3.Server Options -> Check "Publish module contexts to separate XML files"
2010年8月23日 星期一
JSP & Servlet 自訂標籤設計(2)
這篇筆記介紹 Tag File 的使用,為之前的自訂標籤的第二篇
Tag File 是自訂標籤的一部份
可以用來呼叫重複利用的內容,做到類似嵌入的效果
它提供非 Java 開發人員不需要撰寫Java類別以及TLD就建立自訂標籤的選擇
Tag File 與其他的方法上比較有所差異的部分在觀念上
以概念來說,Web Application中不應該出現在請求之外的傳遞參數
所以之前提過的頁面客製化的作法在概念上並不是那麼的合理
使用方法為以下步驟:
1.將被嵌入的檔案的副檔名改成.tag
2.將Tag File (副檔名是.tag的檔案)放在WEB-INF下名為 "tags" 的目錄或其子目錄裡
3.在JSP頁面加上 taglib 指令並使用 tagdir 屬性,接著如下範例呼叫標籤:
<%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>
<html><body>
<fragment:Header />
</body></html>
範例會在頁面中呼叫Header.tag 檔
傳遞參數的方法則是使用標籤的屬性,例如<fragment:Header subTitle="Hello" />
接著可以在被引入的檔案裡用 ${subTitle} 讀取此參數
也因為是標籤的屬性的緣故,傳遞的參數只能在引用的頁面裡面使用
不像其它的作法,若使用<jsp:param>會讓傳遞的參數被其他的部份看到
在設計的理念上,Tag File 是比較合理的
Tag File 與其他標籤程式庫不同
它不需要撰寫 tld 檔,除非要將它部屬在 JAR 檔中
那就必須用TLD,以<path>元素替它宣告實體位置
所以有一些專門用在 Tag File 上的指令會在下面說明
在 .tag 檔案裡可以可以加入 attribute 指令
作用類似後續會在自訂標籤筆記提到的 tld 裡的<attribute>元素
如:<%@ attribute name="subTitle" required="true" rtexprvalue="true" %>
required屬性設定成 true 表示這個屬性是必要的
rtexprvalue 設定成 true 讓傳遞時可以用字串實字或表達式來輸入參數值
當屬性值很龐大時,有主體可以讓閱讀上比較方便
程式碼:
Header.tag======
<p><jsp:doBody /></p>
show.jsp=======
<%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>
<html><body>
<fragment:Header>
這裡是主體內的內容
這裡是主體內的內容
</fragment:Header>
</body></html>
<jsp:doBody />的用意是不管標籤主體內容為何,將評算結果放進來
而body-content的宣告需要透過 tag 指令來下
如:<%@ tag body-content="tagdependent" %>
上面的程式碼設定標籤主體的內容做為純文字來處理
這個屬性的預設值是 scriptless,另外還可以設定empty
不過不管怎樣都不能在 Tag File 中使用Scriptlet
Tag File 是JSP的一部分,所以也是可以存取隱含物件
只是沒有 ServletContext 能用,須改用 JspContext 物件來存取
前一篇提過的 SkipPageException 在嵌入頁面的使用上
它會讓讓被嵌入的頁面中止,但呼叫它的頁面會繼續輸出
注意使用時的區別
Tag File 是自訂標籤的一部份
可以用來呼叫重複利用的內容,做到類似嵌入的效果
它提供非 Java 開發人員不需要撰寫Java類別以及TLD就建立自訂標籤的選擇
Tag File 與其他的方法上比較有所差異的部分在觀念上
以概念來說,Web Application中不應該出現在請求之外的傳遞參數
所以之前提過的頁面客製化的作法在概念上並不是那麼的合理
使用方法為以下步驟:
1.將被嵌入的檔案的副檔名改成.tag
2.將Tag File (副檔名是.tag的檔案)放在WEB-INF下名為 "tags" 的目錄或其子目錄裡
3.在JSP頁面加上 taglib 指令並使用 tagdir 屬性,接著如下範例呼叫標籤:
<%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>
<html><body>
<fragment:Header />
</body></html>
範例會在頁面中呼叫Header.tag 檔
傳遞參數的方法則是使用標籤的屬性,例如<fragment:Header subTitle="Hello" />
接著可以在被引入的檔案裡用 ${subTitle} 讀取此參數
也因為是標籤的屬性的緣故,傳遞的參數只能在引用的頁面裡面使用
不像其它的作法,若使用<jsp:param>會讓傳遞的參數被其他的部份看到
在設計的理念上,Tag File 是比較合理的
Tag File 與其他標籤程式庫不同
它不需要撰寫 tld 檔,除非要將它部屬在 JAR 檔中
那就必須用TLD,以<path>元素替它宣告實體位置
所以有一些專門用在 Tag File 上的指令會在下面說明
在 .tag 檔案裡可以可以加入 attribute 指令
作用類似後續會在自訂標籤筆記提到的 tld 裡的<attribute>元素
如:<%@ attribute name="subTitle" required="true" rtexprvalue="true" %>
required屬性設定成 true 表示這個屬性是必要的
rtexprvalue 設定成 true 讓傳遞時可以用字串實字或表達式來輸入參數值
當屬性值很龐大時,有主體可以讓閱讀上比較方便
程式碼:
Header.tag======
<p><jsp:doBody /></p>
show.jsp=======
<%@taglib prefix="fragment" tagdir="/WEB-INF/tags" %>>
<html><body>
<fragment:Header>
這裡是主體內的內容
這裡是主體內的內容
</fragment:Header>
</body></html>
<jsp:doBody />的用意是不管標籤主體內容為何,將評算結果放進來
而body-content的宣告需要透過 tag 指令來下
如:<%@ tag body-content="tagdependent" %>
上面的程式碼設定標籤主體的內容做為純文字來處理
這個屬性的預設值是 scriptless,另外還可以設定empty
不過不管怎樣都不能在 Tag File 中使用Scriptlet
Tag File 是JSP的一部分,所以也是可以存取隱含物件
只是沒有 ServletContext 能用,須改用 JspContext 物件來存取
前一篇提過的 SkipPageException 在嵌入頁面的使用上
它會讓讓被嵌入的頁面中止,但呼叫它的頁面會繼續輸出
注意使用時的區別
訂閱:
文章 (Atom)