了解 Ant 1.6 的新特性以及它們如何影響您組織編譯過程的方式。
雖然 Ant 版本的 1.5.x 系列在任務級方面有很大的改善,但它沒有改變人們使用 Ant 的方式。而 Ant 1.6 卻有所不同。它增加了幾個新特性,以支持大型或非常復雜的編譯情況。但是,要充分利用它們的功能,用戶可能需要稍微調整它們的編譯過程。
本文重點介紹了其中的三種新特性 — <macrodef>、<import>、<subant> 任務,表明使用它們可以有什么收獲,以及它們如何影響您組織編譯設置的方式。
宏
大多數編譯工程師遲早會面臨必須執行相同的任務組合但在幾個地方配置稍微有點不同的情況。一個常見的例子是創建一個web 應用程序存檔,對于開發系統、測試系統和生產系統有著不同的配置。
讓我們假設 web 應用程序擁有依賴于目標系統的不同的 web 部署描述符,并為開發環境使用了一個不同的 JSP 集合以及一個不同的資料庫集合。配置信息將放在屬性中,創建 web 存檔的任務看起來將類似于
<target name="war" depends="jar"> <war destfile="${war.name}" webxml="${web.xml}"> <lib refid="support-libraries"/> <lib file="${jar.name}"/> <fileset dir="${jsps}"/> </war> </target>
其中 support-libraries 是引用一個在其它位置定義的 <fileset> ,該引用指向您的應用程序所需的附加資料庫的一個公共集合。
如果您只想一次創建一個 web 存檔,那么您只需要正確地設置屬性。比如說,您可以從一個您的目標專有的屬性文件中加載它們。
利用 Ant 1.5 創建存檔
現在,假定您想為測試系統和生產系統同時創建存檔,以確保您真正為兩個系統打包了相同的應用程序。利用 Ant 1.5,您可能使用 <antcall> 來調用擁有不同屬性設置的 "war" 目標,類似:
<target name="production-wars"> <antcall target="war"> <param name="war.name" value="${staging.war.name}"/> <param name="web.xml" value="${staging.web.xml}"/> </antcall> <antcall target="war"> <param name="war.name" value="${production.war.name}"/> <param name="web.xml" value="${production.web.xml}"/> </antcall> </target>
當然,這假定兩個目標系統都將使用相同的 jar 和 JSP。
但這種方法有一個主要缺點 — 就是速度慢。<antcall> 重新分析編譯文件,并為每一次調用重新運行調用的目標所依賴的所有目標。在上面的例子中,"jar" 目標將被運行兩次。我們希望這對第二次調用沒有影響,因為 "war" 目標依賴于它。
利用 Ant 1.6 創建存檔
使用 Ant 1.6,您可以忘掉用 <antcall> 來實現宏的方法,相反您可以通過參數化現有的任務來創建一個新的任務。因而上面的例子將變為:
<macrodef name="makewar"> <attribute name="webxml"/> <attribute name="destfile"/> <sequential> <war destfile="@{destfile}" webxml="@{webxml}"> <lib refid="support-libraries"/> <lib file="${jar.name}"/> <fileset dir="${jsps}"/> </war> </sequential> </macrodef>
這定義了一個名稱為 makewar 的任務,該任務可以和任何其它的任務一樣使用。該任務有兩個必需的屬性,webxml 和 destfile。要使屬性可選,我們必需在任務定義中提供一個默認值。這個示例假定 ${jar.name} 和 ${jsps} 在編譯期間為常量,從而它們仍然作為屬性指定。注意,屬性在使用任務時展開而不是在定義宏的地方展開。
所用任務的特性幾乎完全和屬性一樣,它們通過 @{} 而不是 ${} 展開。與屬性不同,它們是可變的,也就是說,它們的值可以(并將)隨著每一次調用而改變。它們也只在您的宏定義程序塊內部可用。這意味著如果您的宏定義還包含了另一個定義了宏的任務,那么您內部的宏將看不到包含的宏的屬性。
于是新的 production-wars 目標將類似于:
<target name="production-wars"> <makewar destfile="${staging.war.name}" webxml="${staging.web.xml}"/> <makewar destfile="${production.war.name}" webxml="${production.web.xml}"/> </target>
這個新的代碼段不僅執行得快一些,而且也更易讀,因為屬性名稱提供了更多的信息。
宏任務還可以定義嵌套的元素。<makewar> 定義中的 <war> 任務的嵌套 <fileset> 可以是這種嵌套元素的一種。可能開發目標需要一些額外的文件或想從不同的位置中挑選 JSP 或資源。以下代碼段將一個可選的嵌套 <morefiles> 元素添加到了 <makewar> 任務中
<macrodef name="makewar"> <attribute name="webxml"/> <attribute name="destfile"/> <element name="morefiles" optional="true"/> <sequential> <war destfile="@{destfile}" webxml="@{webxml}"> <lib refid="support-libraries"/> <lib file="${jar.name}"/> <fileset dir="${jsps}"/> <morefiles/> </war> </sequential> </macrodef>
調用將類似于:
<makewar destfile="${development.war.name}" webxml="${development.web.xml}"> <morefiles> <fileset dir="${development.resources}"/> <lib refid="development-support-libraries"/> </morefiles> </makewar>
這就像 <morefiles> 的嵌套元素直接在 <war> 任務內部使用的效果一樣。
即使迄今為止的示例僅顯示了包裝單個任務的 <macrodef>,但它不限于此。
下面的宏不僅將創建 web 存檔,還將確保包含最終存檔的目錄在試圖寫入之前存在。在一個實際的編譯文件中,您可能在調用任務之前使用一個設置目標來完成這個操作。
<macrodef name="makewar"> <attribute name="webxml"/> <attribute name="destfile"/> <element name="morefiles" optional="true"/> <sequential> <dirname property="@{destfile}.parent" file="@{destfile}"/> <mkdir dir="${@{destfile}.parent}"/> <war destfile="@{destfile}" webxml="@{webxml}"> <lib refid="support-libraries"/> <lib file="${jar.name}"/> <fileset dir="${jsps}"/> <morefiles/> </war> </sequential> </macrodef>
這里注意兩件事情:
首先,特性在屬性展開之前展開,因此結構 ${@{destfile}.parent} 將展開一個名稱包含了 destfile 特性的值和 ".parent" 后綴的屬性。這意味著您可以將特性展開嵌入到屬性展開中,而不是將屬性展開嵌入特性展開中。
其次,這個宏定義了屬性,該屬性的名稱基于一個特性的值,因為 Ant 中的屬性是全局的并且不可改變。第一次嘗試使用
<dirname property="parent" file="@{destfile}"/>
相反將不會在 "production-wars" 目標中的第二次 <makewar> 調用產生期望的結果。第一次調用將定義一個新的名稱為 parent 的屬性,該屬性指向父目錄 ${staging.war.name}。第二次調用將查看這個屬性但不會修改它的值。
預期 Ant 未來的版本將支持某些類型的限定范圍的屬性,這種屬性只在宏執行期間定義。在此之前,使用特性的名稱來構建屬性名稱是一種變通辦法,潛在的副作用是要創建大量的屬性。
提示:如果您查看您的編譯文件時發現使用了 <antcall> 代替宏,那么強烈建議您考慮使用 macrodef 將其轉換成真正的宏。性能影響可能非常顯著,并且還可能產生更易讀和更易于維護的編譯文件。 |
將一個編譯文件分成多個文件有幾個原因。
共享公用功能/在 Ant 1.6 之前包含文件
在 Ant 1.6 之前,您唯一的選擇是實體包含的 XML 方法,類似于:
<!DOCTYPE project [ <!ENTITY common SYSTEM "file:./common.xml"> ]> <project name="test" default="test" basedir="."> <target name="setup"> ... </target> &common; ... </project>
摘自 Ant 常見問題解答。
這種方法有兩個主要的缺點。您不能使用 Ant 屬性指向您想包含的文件,因此被迫在您的編譯文件中對位置進行硬編碼。您想包含的文件只是一個 XML 文件的一部分,它可能沒有一個根元素,因而使用支持 XML 的工具進行維護更加困難。
共享公用功能/使用 Ant 1.6 包含文件
Ant 1.6 自帶了一個名稱為 import 的新任務,您現在可以使用它。上面的示例將變為
<project name="test" default="test" basedir="."> <target name="setup"> ... </target> <import file="common.xml"/> ... </project>
因為它是一個任務,因此您可以使用 Ant 所有的特性來指定文件位置。主要的差異是被導入的文件本身必須是一個有效的 Ant 編譯文件,因而必須有一個名稱為 project 的根元素。如果您想從實體包含轉換到導入,那么您必須在導入的文件的內容首尾放上 <project> 標記;然后 Ant 將在讀取文件時再次劃分它們。
注意文件名稱由 Ant 任務根據編譯文件的位置(而不是指定的基本目錄)確定。如果您沒有設置項目的 basedir 屬性或將其設為 ".",那么您將不會注意到任何差異。如果您需要根據基本目錄解析一個文件,那么您可以使用一個屬性作為變通辦法,類似于:
<property name="common.location" location="common.xml"/> <import file="${common.location}"/>
屬性 common.location 將包含文件 common.xml 的絕對路徑,并已根據導入項目的基本目錄解析。
使用 Ant 1.6,所有的任務都可能放在目標之外或之內,除了兩個例外。<import> 一定不能嵌入到目標中,<antcall> 一定不能在目標外使用(否則它將創建一個無限循環)。
而 <import> 可做的不僅僅是導入另一個文件。
首先,它定義了名稱為 ant.file.NAME 的特殊屬性,其中 NAME 替換為每一個導入文件的 <project> 標記的名稱屬性。這個屬性包含了導入文件的絕對路徑,導入文件可用來根據它自己的位置(而不是導入文件的基本目錄)定位文件和資源。
這意味著 <project> 的名稱屬性在 <import> 任務環境中變得更加重要。它還用來為在被導入的編譯文件中定義的目標提供別名。如果導入了以下文件
<project name="share"> <target name="setup"> <mkdir dir="${dest}"/> </target> </project>
導入編譯文件可以查看作為 "setup" 或 "share.setup" 的目標。后者在目標覆蓋的上下文中變得非常重要。
讓我們假定有一個包含了多個獨立的組件(每個組件擁有它自己的編譯文件)的編譯系統。這些編譯文件幾乎相同,因此我們決定將公用功能轉移到一個共享和已導入的文件中。為了簡單起見,我們只介紹 Java 文件的編譯和創建結果的一個 JAR 存檔。共享的文件將類似于
<project name="share"> <target name="setup" depends="set-properties"> <mkdir dir="${dest}/classes"/> <mkdir dir="${dest}/lib"/> </target> <target name="compile" depends="setup"> <javac srcdir="${src}" destdir="${dest}/classes"> <classpath refid="compile-classpath"/> </javac> </target> <target name="jar" depends="compile"> <jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"/> </target> </project>
這個文件不會作為一個獨立的 Ant 編譯文件進行工作,因為它沒有定義 "setup" 所依賴的 "set-properties" 目標。
組件 A 的編譯文件可能類似于
<project name="A" default="jar"> <target name="set-properties"> <property name="dest" location="../dest/A"/> <property name="src" location="src"/> <property name="jar.name" value="module-A.jar"/> <path id="compile-classpath"/> </target> <import file="../share.xml"/> </project>
它僅設置適當的環境,然后將全部的編譯邏輯交給被導入的文件負責。注意該編譯文件創建了一個空的路徑作為編譯 CLASSPATH,因為它是自包含的。模塊 B 依賴于 A,它的編譯文件將類似于
<project name="B" default="jar"> <target name="set-properties"> <property name="dest" location="../dest/B"/> <property name="src" location="src"/> <property name="jar.name" value="module-B.jar"/> <path id="compile-classpath"> <pathelement location="../dest/A/module-A.jar"/> </path> </target> <import file="../share.xml"/> </project>
您將注意到該編譯文件與 A 的編譯文件幾乎一樣,因此似乎有可能將大多數的 set-properties 目標也推送到 shared.xml 中。實際上,我們可以假定有一個對 dest 和 src 目標一致的命名慣例,以實現這一目的。
<project name="share"> <target name="set-properties"> <property name="dest" location="../dest/${ant.project.name}"/> <property name="src" location="src"/> <property name="jar.name" value="module-${ant.project.name}.jar"/> </target> ... contents of first example above ... </project>
ant.project.name 是一個內置的屬性,它包含了最外面的 <project> 標記的名稱屬性的值。因此,如果模塊 A 的編譯文件導入了 share.xml,那么它將擁有值 A。
注意,所有的文件都與導入編譯文件的基本目錄相關,因此 scr 屬性的實際值依賴于導入文件。
為此,A 的編譯文件將簡單地變為
<project name="A" default="jar"> <path id="compile-classpath"/> <import file="../share.xml"/> </project>
B 的編譯文件將變為
<project name="B" default="jar"> <path id="compile-classpath"> <pathelement location="../dest/A/module-A.jar"/> </path> <import file="../share.xml"/> </project>
現在假定 B 增加了一些 RMI 接口,需要在編譯類之后但在創建 jar 之前運行 <rmic>。這就是目標覆蓋能派上用場的地方。如果我們在導入編譯文件中定義了一個目標,該目標與被導入的編譯文件中的一個目標名稱相同,那么將使用導入編譯文件中的目標。例如,B 可以使用:
<project name="B" default="jar"> <path id="compile-classpath"> <pathelement location="../dest/A/module-A.jar"/> </path> <import file="../share.xml"/> <target name="compile" depends="setup"> <javac srcdir="${src}" destdir="${dest}/classes"> <classpath refid="compile-classpath"/> </javac> <rmic base="${dest}/classes" includes="**/Remote*.class"/> </target> </project>
在上面的示例中將使用 "compile" 目標,而不是 share.xml 中的目標;然而,不幸的是,這只是從共享那里復制 <javac> 任務。一種更好的解決方案是:
<project name="B" default="jar"> <path id="compile-classpath"> <pathelement location="../dest/A/module-A.jar"/> </path> <import file="../share.xml"/> <target name="compile" depends="share.compile"> <rmic base="${dest}/classes" includes="**/Remote*.class"/> </target> </project>
這只是使 B 的 "compile" 在原來的 "compile" 目標使用之后運行 <rmic>。
如果我們想在編譯之前生成一些 Java 源代碼(例如通過 XDoclet),我們可以使用類似下面的方法:
<import file="../share.xml"/> <target name="compile" depends="setup,xdoclet,share.compile"/> <target name="xdoclet"> .. details of XDoclet invocation omitted .. </target>
因此您可以完全覆蓋一個目標或通過在原始目標之前或之后運行任務來增強它。
這里要注意一個危險。目標覆蓋機制使導入編譯文件依賴于在導入文件中使用的名稱屬性。如果任何人修改了導入文件的名稱屬性,那么導入編譯文件將被破壞。Ant 開發社區目前正在討論在 Ant 的一個未來的版本中為此提供一個解決方案。
提示:如果您在編譯文件中發現了非常常見的結構,那么值得嘗試將文件重構為一個(一些)共享文件,并在必要時使用目標覆蓋。這可以使您的編譯系統更加一致,并讓您能夠重用編譯邏輯。 |
在某種意義上,subant 是兩種任務合二為一,因為它了解操作的兩種模式。
如果您使用 <subant> 的 genericantfile 屬性,那么它的工作方式和 <antcall> 一樣,調用包含任務的同一個編譯文件中的目標。與 <antcall> 不同,<subant> 獲取目錄的列表或集合,并將為每一個目錄調用一次目標,以設定項目的基本目錄。如果您想在任意數量的目錄中執行完全一樣的操作,那么這非常有用。
第二種模式不使用 genericantfile 屬性,而獲取一個編譯文件的列表和集合進行迭代,以在每一個編譯文件中調用目標。這種工作方式類似于在一個循環中使用 <ant> 任務。
第二種形式的典型情景是幾個能夠獨立編譯的模塊的一個編譯系統,但是該系統需要一個主編譯文件來一次性編譯所有的模塊。
使用以下資源了解關于 Ant 的更多信息,并開始編譯和部署 Java 項目。 Ant 業界趨勢 閱讀關于 JDeveloper 中的 Ant 集成的更多信息 下載 Oracle JDeveloper 10g 測試驅動:將 Ant 用于編譯 Ant 入門第 1 部分 Ant 入門第 2 部分 在 Linux 上創建 Java 應用程序的命令行方法 閱讀關于 Ant 的更多信息 相關文章與下載 |
在 Ant 1.6 之前構建主編譯文件
在導入部分中討論的例子使用了這樣一個主編譯文件。
<target name="build-all"> <ant dir="module-A" target="jar"/> <ant dir="module-B" target="jar"/> </target>
在 Ant 1.6 之前的 Ant 中。
使用 Ant 1.6 構建主編譯文件
在 Ant 1.6 中使用 <subant>,這可以重寫為
<target name="build-all"> <subant target="jar"> <filelist dir="."> <file name="module-A/build.xml"/> <file name="module-B/build.xml"/> </filelist> </subant> </target>
這看起來并沒有很大的改善,因為您仍然必須單獨指定每一個子編譯文件。相反如果您轉用 <fileset>,那么情況將有所改觀。
<target name="build-all"> <subant target="jar"> <fileset dir="." includes="module-*/build.xml"/> </subant> </target>
這將自動發現所有模塊的編譯文件。如果您增加了一個模塊 C,主編譯文件中的目標不需要修改。
但小心。與 <filelist> 或 <path>(也被 <subant> 支持)不同,<fileset> 是無序的。在我們的例子中,模塊 B 依賴于模塊 A,因此我們需要確保首先編譯模塊 A,而使用 <fileset> 沒有辦法這么做。
如果編譯完全彼此獨立或者它們對于一個給定的操作彼此獨立,那么 <fileset> 仍然有用。模塊 B 的文檔目標可能完全不依賴于模塊 A,同樣還有從您的 SCM 系統中更新源代碼的目標。
如果您想將編譯文件的自動發現與根據編譯的相互依賴性對編譯進行排序結合在一起,那么您將必須編寫一個定制的 Ant 任務。基本的想法是編寫一個使用 <fileset> 的任務(讓我們目前稱之為 <buildlist>),確定依賴關系并計算 <subant> 必須使用的順序。然后它創建一個以正確的順序包含編譯文件的 <path>,然后將對這個路徑的一個引用放到項目中。調用將類似于
<target name="build-all"> <buildlist reference="my-build-path"> <fileset dir="." includes="module-*/build.xml"/> </buildlist> <subant target="jar"> <buildpath refid="my-build-path"/> </subant> </target>
這個假想的 buildlist 任務已經在 Ant 用戶郵件列表和 bug 跟蹤系統中進行了討論。很有可能 Ant 的一個將來的版本中將包含這樣的一個任務。
在 Ant 1.6 中已經增加了大量的新特性。這些新功能中的許多功能使得編譯模板易于創建、構造和定制。特別是 <import> 和 <target> 進行了覆蓋。<import>、<macrodef> 和 <subant> 特性很有可能使得 Ant 編譯可高度重用。<scriptdef>(本文中未討論)對于需要一些腳本但不想用 Java 編寫定制任務的人而言可能非常有吸引力。
代碼 |
#database.jar=${postgresql.jar} #database.type=postgresql #database.name=myApp #database.host=localhost #database URL for creating other databases (doesn't work with pgsql) #database.admin.url=jdbc:${database.type}://${database.host}/template1 #database.admin.username=postgres #database.admin.password=postgres #hibernate.dialect=net.sf.hibernate.dialect.PostgreSQLDialect #database.driver_class=org.postgresql.Driver #database.url=jdbc:${database.type}://${database.host}/${database.name} |
SQL代碼 |
create table app_user ( username varchar(20) not null, version integer not null, password varchar(255), first_name varchar(50), last_name varchar(50), address varchar(150), city varchar(50), province varchar(100), country varchar(100), postal_code varchar(15), email varchar(255) not null unique, phone_number varchar(255), website varchar(255), password_hint varchar(255), primary key (username) ); |
代碼 |
<!-- Logger className="org.apache.catalina.logger.FileLogger" prefix="myApp_log." suffix=".txt" timestamp="true"/ --> |
代碼 |
<role rolename="admin"/> <role rolename="manager"/> |
代碼 |
<user username="admin" password="admin" roles="role1,tomcat,admin,manager"/> |
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class TimerFilter implements Filter { private FilterConfig config = null; public void init(FilterConfig config) throws ServletException { this.config = config; } public void destroy() { config = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long before = System.currentTimeMillis(); chain.doFilter(request, response); long after = System.currentTimeMillis(); String name = ""; if (request instanceof HttpServletRequest) { name = ((HttpServletRequest)request).getRequestURI(); } config.getServletContext().log(name + ": " + (after - before) + "ms"); } } |
<filter> <filter-name>timerFilter</filter-name> <filter-class>TimerFilter</filter-class> </filter> |
<filter-mapping> <filter-name>timerFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*; public class ClickstreamFilter implements Filter { protected FilterConfig filterConfig; private final static String FILTER_APPLIED = "_clickstream_filter_applied"; public void init(FilterConfig config) throws ServletException { this.filterConfig = filterConfig; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 確保該過濾器在每次請求中只被使用一次 if (request.getAttribute(FILTER_APPLIED) == null) { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); HttpSession session = ((HttpServletRequest)request).getSession(); Clickstream stream = (Clickstream)session.getAttribute("clickstream"); stream.addRequest(((HttpServletRequest)request)); } // 傳遞請求 chain.doFilter(request, response); } public void destroy() { } } |
import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class ClickstreamLogger implements ServletContextListener, HttpSessionListener { Map clickstreams = new HashMap(); public ClickstreamLogger() { } public void contextInitialized(ServletContextEvent sce) { sce.getServletContext().setAttribute("clickstreams", clickstreams); } public void contextDestroyed(ServletContextEvent sce) { sce.getServletContext().setAttribute("clickstreams", null); } public void sessionCreated(HttpSessionEvent hse) { HttpSession session = hse.getSession(); Clickstream clickstream = new Clickstream(); session.setAttribute("clickstream", clickstream); clickstreams.put(session.getId(), clickstream); } public void sessionDestroyed(HttpSessionEvent hse) { HttpSession session = hse.getSession(); Clickstream stream = (Clickstream)session.getAttribute("clickstream"); clickstreams.remove(session.getId()); } } |
<filter> <filter-name>clickstreamFilter</filter-name> <filter-class>ClickstreamFilter</filter-class> </filter> |
<filter-mapping> <filter-name>clickstreamFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>clickstreamFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <listener> <listener-class>ClickstreamLogger</listener-class> </listener> |
<%@ page import="java.util.*" %> <%@ page import="Clickstream" %> <% Map clickstreams = (Map)application.getAttribute("clickstreams"); String showbots = "false"; if (request.getParameter("showbots") != null) { if (request.getParameter("showbots").equals("true")) showbots = "true"; else if (request.getParameter("showbots").equals("both")) showbots = "both"; } %> <font face="Verdana" size="-1"> <h1>All Clickstreams</h1> <a href="clickstreams.jsp?showbots=false">No Bots</a> | <a href="clickstreams.jsp?showbots=true">All Bots</a> | <a href="clickstreams.jsp?showbots=both">Both</a> <p> <% if (clickstreams.keySet().size() == 0) { %> No clickstreams in progress <% } %> <% Iterator it = clickstreams.keySet().iterator(); int count = 0; while (it.hasNext()) { String key = (String)it.next(); Clickstream stream = (Clickstream)clickstreams.get(key); if (showbots.equals("false") && stream.isBot()) { continue; } else if (showbots.equals("true") && !stream.isBot()) { continue; } count++; try { %> <%= count %>. <a href="viewstream.jsp?sid=<%= key %>"><b> <%= (stream.getHostname() != null && !stream.getHostname().equals("") ? stream.getHostname() : "Stream") %> </b></a> <font size="-1"><%= stream.getStream().size() %> reqs</font><br> <% } catch (Exception e) { %> An error occurred - <%= e %><br> <% } } %> |
public void init(FilterConfig filterConfig) { config = filterConfig; compressionThreshold = 0; if (filterConfig != null) { String str = filterConfig.getInitParameter("compressionThreshold"); if (str != null) { compressionThreshold = Integer.parseInt(str); } else { compressionThreshold = 0; } } } |
public class CompressionResponseWrapper extends HttpServletResponseWrapper { protected ServletOutputStream stream = null; protected PrintWriter writer = null; protected int threshold = 0; protected HttpServletResponse origResponse = null; public CompressionResponseWrapper(HttpServletResponse response) { super(response); origResponse = response; } public void setCompressionThreshold(int threshold) { this.threshold = threshold; } public ServletOutputStream createOutputStream() throws IOException { return (new CompressionResponseStream(origResponse)); } public ServletOutputStream getOutputStream() throws IOException { if (writer != null) { throw new IllegalStateException("getWriter() has already been " + "called for this response"); } if (stream == null) { stream = createOutputStream(); } ((CompressionResponseStream) stream).setCommit(true); ((CompressionResponseStream) stream).setBuffer(threshold); return stream; } public PrintWriter getWriter() throws IOException { if (writer != null) { return writer; } if (stream != null) { throw new IllegalStateException("getOutputStream() has already " + "been called for this response"); } stream = createOutputStream(); ((CompressionResponseStream) stream).setCommit(true); ((CompressionResponseStream) stream).setBuffer(threshold); writer = new PrintWriter(stream); return writer; } } |
<filter> <filter-name>compressionFilter</filter-name> <filter-class>CompressionFilter</filter-class> <init-param> <param-name>compressionThreshold</param-name> <param-value>10</param-value> </init-param> </filter> <filter-mapping> <filter-name>compressionFilter</filter-name> <servlet-name>compressionTest</servlet-name> </filter-mapping> <servlet> <servlet-name> compressionTest </servlet-name> <servlet-class> CompressionTestServlet </servlet-class> </servlet> |
UI Tags
Validation Examples
Resource bundles are searched in the following order:
1.) ActionClass.properties
2.)BaseClass.properties (all the way to Object.properties)
3.) Interface.properties (every interface and sub-interface)
4.) package.properties (every of every base class, all the way to java/lang/package.properties)
7.
http://forum.javaeye.com/viewtopic.php?t=11073&start=0
http://www.opensymphony.com/webwork/wikidocs/Transparent%20web-app%20I18N.html
http://localhost:8080/webwork_test/hello/hello.action?set_locale=zh_CN
http://localhost:8080/webwork_test/hello/hello.action?set_locale=en_US
測試通過,自定義語種問題解決.
相關鏈接:
http://forum.javaeye.com/viewtopic.php?t=11073&start=0
8.
今天發現5.8.4版本的myeclipse的tag屬性面板挺好用的.good.
9.
有關webwork的javascript驗證能力參看以下帖子:
http://www.hibernate.org.cn/viewtopic.php?t=9126&postdays=0&postorder=asc&start=0
http://forum.javaeye.com/allbloglist.php?page=15
http://scud.blogdriver.com/scud/index.html
在執行 ant setup的時候,在compile-module的時候,總是說找不到valuelist的類,后來發現AppFuse的jar都添加到了builde.xml中,于是我也把valuelist.jar添加過去了,發現問題解決了,方法如下:
AppFuse執行Setup后自動將valuelist.jar復制到Tomcat下:
在appfuse\lib\lib.properties中添加如下內容:
#
# ValueList
#
valuelist.version=0.1.7
valuelist.dir=${lib.dir}
valuelist.jar=${valuelist.dir}/valuelist.jar
將properties.xml中添加:
<!-- Web -->
<path id="web.compile.classpath">
<pathelement location="${dist.dir}/${webapp.name}-dao.jar"/>
<pathelement location="${dist.dir}/${webapp.name}-service.jar"/>
<pathelement location="${struts.jar}"/>
<pathelement location="${strutsmenu.jar}"/>
<pathelement location="${displaytag.jar}"/>
<pathelement location="${jakarta-oro.jar}"/>
<pathelement location="${commons-digester.jar}"/>
<pathelement location="${commons-logging.jar}"/>
<pathelement location="${commons-beanutils.jar}"/>
<pathelement location="${commons-collections.jar}"/>
<pathelement location="${commons-fileupload.jar}"/>
<pathelement location="${commons-lang.jar}"/>
<pathelement location="${commons-validator.jar}"/>
<pathelement location="${servletapi.jar}"/>
<pathelement location="${valuelist.jar}"/> //這是我添加的
<fileset dir="${javamail.dir}" includes="*.jar"/>
<fileset dir="${spring.dir}" includes="*.jar"/>
<fileset dir="${jstl.dir}/lib" includes="jstl.jar"/>
</path>
在builde.xml中添加:
<war destfile="${webapp.dist}/${webapp.war}"
webxml="${webapp.target}/WEB-INF/web.xml" compress="true">
<fileset dir="${webapp.target}" excludes="**/web.xml,**/*-resources.xml"/>
<metainf dir="${webapp.dist}" includes="context.xml"/>
<classes dir="${build.dir}/web/classes">
<exclude name="**/database.properties"/>
</classes>
<lib file="${dist.dir}/${webapp.name}-dao.jar"/>
<lib file="${dist.dir}/${webapp.name}-service.jar"/>
<webinf dir="${struts.dir}" includes="*.xml"/>
<webinf dir="web/WEB-INF" includes="*-resources.xml"/>
<lib file="${clickstream.jar}"/>
<lib dir="${struts.dir}" includes="*.jar"/>
<lib dir="${jstl.dir}/lib">
<include name="jstl.jar"/>
<include name="standard.jar"/>
</lib>
<lib dir="${javamail.dir}" includes="*.jar"/>
<lib file="${log4j.jar}"/>
<lib file="${strutsmenu.jar}"/>
<lib file="${valuelist.jar}"/> //這是我添加的內容
<lib dir="${displaytag.dir}" includes="*.jar"/>
<lib file="${hibernate.jar}"/>
<lib dir="${hibernate.dir}/lib">
<include name="odmg*.jar"/>
<include name="dom4j*.jar"/>
<include name="cglib*.jar"/>
<include name="ehcache*.jar"/>
<include name="oscache*.jar"/>
</lib>
<lib dir="${spring.dir}" includes="*.jar"/>
<lib file="${sitemesh.jar}"/>
<lib dir="${velocity.dir}" includes="*.jar"/>
<lib file="${urlrewrite.jar}"/>
</war>
</target>
兔八哥
關于URLEncoder的解析問題
在http://rabbit8.blogchina.com/blog/article_144619.789425.html后,有個朋友留言,說在百度試驗的結果和我文章中說的不一致,我做了個實驗,證實JDK的幫助沒錯,原因如下:
我的試驗代碼如下:
public static void main(String[] args) {
URLEncoder urle = null;
//得到默認:%A8%B9
System.out.println("默認:" + urle.encode("ü"));
try {
//得到GBK:%A8%B9
System.out.println("GBK:" + urle.encode("ü", "GBK"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
//得到UTF-8:%C3%BC
System.out.println("UTF-8:" + urle.encode("ü", "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
如果用UltraEdit來查看"ü"的ASCII的話,得到的結果如圖:
可見,UltraEdit使用的是操作系統默認的編碼方式(實際上,MS采用的也不是GBK,而是另一種編碼,但效果和GBK差不多),所以它顯示的ASCII的編碼為A8 B9,就是第一和第二種情況的結果。而第三種情況才是JDK幫助中所聲明的情況。
我查看了百度,提交了一下,結果和我預期的是一樣的!
如果你查看頁面的源文件,會看到百度的charset為gb2312,而幫助中明確提到例子使用的是UTF-8編碼,所以出現了不一致的問題,也正是因為這個原因,所以JDK中決定要廢棄public static String encode(String s)方法,因為這個方法的編碼的字符集依賴于程序運行的系統的默認的字符集!
兔八哥
2005-2-15 17:41
關于AppFuse中使用中文編碼的問題
這2個月一直被DisplayTag折騰,如果DisplayTag的鏈接或者排序的列中有漢字,那么排序或者翻頁都會查詢不到結果或報錯(如果你處理的好,可能會顯示沒有可以顯示的數據)。
我一直以為這個問題是因為DisplayTag的開發者們沒有考慮到中文的問題,因為項目進度很緊,所以就把排序的功能給屏蔽了,雖然經過了大量的測試,但都沒有發現翻頁時候也存在這個問題(AppFuse自動生成的頁面的默認顯示25行,如果超過25行會自動分頁),直到前天,測試人員才發現這個問題,按照我的理解要修改DisplayTag的源碼,后來又想通過自己寫過濾器試試,幸運的是在我動手干之前,先和倦兔聊了幾句,結果倦兔說他也遇到過這個問題,指出如果在server.xml中添加URIEncoding="GBK"就可以解決問題,我試了一下,果然可以,但這是為什么呢?
在AppFuse中有一個Spring的過濾器,對請求的編碼進行轉換,這種做法很通用,即使是在以前,我們自己寫這個過濾器也不麻煩,而且這個代碼在網上隨處可見,難道是Spring的過濾器沒有起作用嗎?也不是,如果沒起作用,那么我在Struts中的Action中通過request.getParmeter接收到參數應該是亂碼?而且我記得這種做法在Tomcat4.1.24中沒有任何問題,那是為什么?和倦兔討論了一下,我們都沒有找到答案。
于是,我就通過google搜索"URIEncoding",找到一堆文章,我讀了幾篇文章,其中有一篇提到:Tomcat5中對Post和Get請求不再采用相同的處理策略了,而在Tomcat4中采用的處理策略是相同的。于是我想:"我的提交的請求明明是Post,我又沒有用Get請求,那Spring的過濾器就應該起作用,為什么結果會這樣呢?",我的一個jsp中的Form的代碼如下:
<html:form action="editGj" method="post" styleId="gjForm"
focus="mc" onsubmit="return validateGjForm(this)">
看,我的代碼明明是使用的是Post方法,為什么結果不正確呢?
我的跳轉到下一頁的代碼是:
<a href="?d-449687-p=2&mc=%D7%A8%D2%B5&;
excelname=%D7%A8%D2%B5%C3%FB%B3%C6.xls">下一頁</a>
加粗的部分就是我的翻頁的鏈接。如果你立刻就看出了我的問題,那么你就可以不用往下看了,呵呵。如果你沒有看出問題,請繼續,Come On Baby!!!
我采用的真的是Post方法嗎?我的form的提交采用的是Post方法,這是沒錯的,那么鏈接采用的是Post方法嗎?
不是!!!!它采用的是Get方法進行提交的,這就是問題的答案了!
Post和Get提交有什么區別嗎?
簡單的說:Post是將地址傳送一次,將form的數據單獨提交,而Get則是將地址和參數一起傳送,傳送的不止是form的數據,但傳送的數據的長度有字節限制,另外還有安全問題。如果感興趣,你可以用google搜一下,會找到很多資料。
當我想到這個區別時,我又檢查了一下我的顯示列表的頁面,發現這個頁面沒有form,只有,這說明我的提交的確是Get的,于是,Tomcat5對我的Get請求執行的是另外的處理方式,和Post的處理方式不再一樣了!
這樣我們提交的漢字被認為是ISO-8859-1的編碼,所以在程序中接收時顯示亂碼,如果使用過濾器,在過濾器中調用request.setCharacterEncoding("GBK")的話,那么Post上來的漢字將被認為是GBK編碼,而Tomcat5對于Get請求上來的編碼并不根據過濾器的設定辨認編碼方式,默認的依然是ISO-8859-1,Tomcat5中處理Get請求的源代碼如下:
上面的代碼中,enc代表Tomcat5中server.xml中Connector部分的URIEnocodeing項設定的值,可以看出,如果沒有設定URIEncoding項的話,那么Tomcat5也并沒有使用默認的ISO-8859-1編碼,而是一段fast conversion,所以,即使你的頁面使用默認的編碼方式進行編碼,然后使用ISO-8859-1進行解碼,得到的結果也不對,這可能是Tomcat5的一個Bug。如果想繞過這個Bug,那么只有在server.xml的Connector部分設定URIEncoding的值,根據編碼方式指定自己的值,在我的程序中我使用的是GBK,所以我修改后的部分如下:
acceptCount="100"
這個問題我參考了這篇文章:
http://www.javaworld.com.tw/jute/post/view?age=0&bid=9&ppg=1&sty=1&id=44042&tpg=1
http://www.javaworld.com.tw/jute/post/view?age=0&bid=9&ppg=1&sty=1&id=44042&tpg=1另外,對于編碼解碼的問題,我還有一句要說,理論上,如果編碼的charset和解碼的charset是一致的話,那么就應該沒有亂碼的問題,但是,對于某些特殊的字符來說,如果采用的charset不對,則可能在解碼的時候不能顯示,所以要選擇好字符集,我推薦在處理簡體漢字的時候使用GBK。網上也有推薦UTF-8的,具體使用哪種依情況而定。
以上是我的總結,若有錯誤,請指正,謝謝!
明天,我就放假了,祝我的朋友們春節快樂,萬事勝意!
兔八哥
2005年2月4日14:04
另外,我昨晚看了看Tomcat5自帶的Servlet Example中過濾器的實現,其中就有關于處理編碼的過濾器SetCharacterEncodingFilter,還有另外一個是處理用戶輸入的過濾器,叫HTMLFilter,用于將用戶輸入的字符轉化為HTML格式的字符,如將">"轉換為">",這個過濾器稍加修改就可以用在自己的項目上了,呵呵!
1.在web.xml中添加,/WEB-INF/classes/standardJspApplicationContext.xml。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-*.xml,/WEB-INF/classes/standardJspApplicationContext.xml</param-value>
</context-param>
2.把valuelist.tld拷貝到WEB-INF下。
3.在taglibs.jsp中添加valuelist.tld。
4.將standardJspApplicationContext.xml、i18n.properties、microsoftLook.properties、simpleLook.properties、classicLook.properties文件復制到/WEB-INF/classes下。
5.在applicationContext-resources.xml的<beans></beans>中添加:
<bean id="resourceI18nBundle" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename"><value>i18n</value></property>
</bean>
和
<bean id="valueListHandler" singleton="true"
class="net.mlw.vlh.DefaultValueListHandlerImpl">
<property name="config.adapters">
<map>
<entry key="userlist">
<bean class="net.mlw.vlh.adapter.hibernate.Hibernate20Adapter">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
<property name="defaultNumberPerPage"><value>2</value></property>
<property name="defaultSortColumn"><value>lastName</value></property>
<property name="defaultSortDirection"><value>asc</value></property>
<property name="hsql">
<value>
FROM org.appfuse.model.User AS vo
/~name: WHERE vo.lastName LIKE {name} ~/
/~sortColumn: ORDER BY vo.[sortColumn] [sortDirection]~/
</value>
</property>
</bean>
</entry>
<!--entry key="players2">
<bean class="net.mlw.vlh.adapter.hibernate.Hibernate20Adapter">
<property name="sessionFactory"><ref bean="mySessionFactory"/></property>
<property name="defaultNumberPerPage"><value>20</value></property>
<property name="defaultSortColumn"><value>lastName</value></property>
<property name="defaultSortDirection"><value>asc</value></property>
<property name="namedQuery"><value>playerList</value></property>
</bean>
</entry-->
</map>
</property>
</bean>
上面配置中的"sessionFactory"為原來配置好的。
"defaultNumberPerPage"為每頁顯示的記錄行數。
"defaultSortColumn"排序列。
"org.appfuse.model.User"為POJO的名字。
6.將相關的樣式表的內容合并到原有的樣式表中。
7.將valuelist.jar添加到類路徑下。
8:如果為行添加onclick事件,注意要對"&"進行轉義-document.location='editUser.html?username=<c:out value="${User.username}"/>\&from=list';:
<vlh:root value="valueList" configName="classicLook" url="?" includeParameters="*" >
<table width="600" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="left" nowrap="true">
<c:out value="${list.valueListInfo.totalNumberOfEntries}"/> Total
- Page (<c:out value="${list.valueListInfo.pagingPage}"/> of <c:out value="${list.valueListInfo.totalNumberOfPages}"/>)
</td>
<td align="right">
<vlh:paging pages="5"><c:out value="${page}"/> </vlh:paging>
</td>
</tr>
<tr>
<td colspan="2">
Export to:
<vlh:filter url="example-export-format.jsp?format=excel&">
<img src="images/export_excel.png" border="0"> Excel
</vlh:filter>
<vlh:filter url="example-export-format.jsp?format=csv&">
<img src="images/export_csv.png" border="0"> CSV
</vlh:filter>
<table width="450" class="classicLook" cellspacing="0" cellpadding="0">
<vlh:row bean="User">
<vlh:attribute name="onclick">
document.location='editUser.html?username=<c:out value="${User.username}"/>\&from=list';
</vlh:attribute>
<vlh:column title="username" property="username" sortable="desc" />
<vlh:column title="firstName" property="firstName" sortable="desc" />
<vlh:column title="lastname" property="lastName" sortable="desc" />
<vlh:column title="email" property="email" sortable="desc" />
</vlh:row>
</table>
</td>
</tr>
</table>
</vlh:root>
======================================================================
為ValueList添加高亮
1.添加vlh:attribute:<vlh:attribute name="onmouseout">javascript:mouseout(this);</vlh:attribute>。
添加完attribute后的內容如下:
<vlh:root value="list" url="?" includeParameters="*" >
<vlh:retrieve name="nbaPlayers" />
<table width="650" class="classicLook" cellspacing="0" cellpadding="0">
<vlh:row bean="player">
<vlh:attribute name="onmouseover">javascript:toggle(this);</vlh:attribute>
<vlh:attribute name="onmouseout">javascript:mouseout(this);</vlh:attribute>
<vlh:attribute name="id"><%=("player-"+playerRowNumber)%></vlh:attribute>
<vlh:attribute name="align" value="center" />
<vlh:column title="playerid" property="playerid" sortable="desc"/>
<vlh:column title="teamname" property="teamname" sortable="desc" />
<vlh:column title="firstname" property="firstname" sortable="desc">
<vlh:attribute name="width" value="150"/>
</vlh:column>
<vlh:column title="lastname" property="lastname" sortable="desc" attributes="width='150'"/>
<vlh:column title="status" property="status" sortable="desc" />
<vlh:column title="pos" property="pos" sortable="desc" />
</vlh:row>
</table>
</vlh:root>
2.添加js函數,添加完函數后的內容如下:
<script>
var lastId;
var lastStyle;
var previousClass = null;
function toggle(object)
{
if( lastId != undefined )
{
document.getElementById(lastId).className = lastStyle;
}
lastId = object.id;
lastStyle = object.className;
previousClass=this.className;
object.className = "selected";
}
function mouseout(object){
object.className = previousClass;
}
</script>
DT編譯成功了,Struts Menu從數據庫取數據組建菜單的試驗成功了......
這幾天又開始做技術實驗了,被折磨的有點情緒化,不過一切還算順利,多虧朋友們的幫忙!
昨晚在家讀DisplayTag的源代碼,發現DisplatyTag(以下簡稱DT)翻頁時候不是從緩存中讀取的數據,它又重新向數據庫發出請求,和我調試程序時發現的一樣,它的動作大致是這樣的:
向數據庫發送請求,一次讀取所有數據的所有字段(我強調"所有"字段,并不是select *,而是讀取你的語句中指定的全部的字段,我強調的是相對于lazy-load方式的部分讀取),然后根據pagesize(一頁顯示數據的行數)和當前頁號讀取數據的子集,當翻到其他頁時,又獲取所有數據的所有字段,再過濾出顯示的子集......,這樣的效率不是差,而是很差,我昨天看了一下DT的JIRA的文字,發現這個問題早在2004年的5月份就有人提出了,而那個人就是ValueList的作者,而且你還會發現很多開發者提供的改進方案和參考實現,而且DT的開發組也同意添加提高分頁效率的特性,但到現在也還沒有實現,有些讓人失望!據說ValueList對這方面做的很好,但ValueList的文檔太少了,而且集成到AppFuse中還要自己改寫模板,目前有些騎虎難下,明天開始折騰ValueList,希望一切順利,菩薩保佑,呵呵
今天試了一下從數據庫中讀取數據來生成Struts Menu的菜單,結果發現如果一頁中放入2個Struts Menu的菜單,則會有一個有問題,我看了一下源碼,發現是在一個js文件中的函數引起的,Struts Menu首先初始化菜單,然后擴展,在擴展的時候,把點擊的菜單寫到Cookies中,如果同一個頁面有2個的聲明,則第二個菜單的折疊的部分不能展開,我的一個同事折騰了將近一周,就是因為這個問題。
昨晚終于在倦兔的幫助下,使用Maven編譯DT成功了,這樣我可以放心的修改DT,然后編譯、打包。但我要權衡一下和改用ValueList的工作量,目前暫不打算修改DT了。
Maven把所有下載的jar文件放在Window的Documents and Settings/你的用戶名/.mave目錄下,你可以把其他人的這個目錄下的文件都拷貝過來(注意要保持原來的目錄結構)直接用就行了。
Matt Raible關于DT、ValueList、DataGrid的簡短介紹:http://raibledesigns.com/comments/rd/sunsets/there_s_a_new_sorting
兔八哥
2005-3-1 18:21
今天,同事終于把ValueList的實驗做完了,ValueList0.1.7新添了許多新功能,但是,今天看到"王者之劍"的留言,又讀了一下源碼,發現ValueList也是取回數據庫的全部數據,只是把需要返回頁面的數據返回了,并不是我想的使用Hibernate的分頁方法進行查詢數據庫的
今天,同事終于把ValueList的實驗做完了,ValueList0.1.7新添了許多新功能,但是,今天看到"王者之劍"在我的Blog上的留言,有點不太相信ValueList也是取回所有的數據。
剛才,我抽空看了一下ValueList的Hibernate20Adapter的源碼,結果發現果然是把所有的數據都取回來了,但比DT稍強一些的是,它沒有把所有的Object都放入request中,只是把當前頁要顯示的Object放入了,但這樣也同樣耗費數據庫資源,同樣需要循環,不同的是,耗費的網絡資源少些,因為,它傳回的對象只有一頁的對象。
但結果并不是我想像的,我以為,它會自己像Hibernate那樣,根據不同的數據庫采用分頁算法,使用Criteria Query設定返回在指定范圍的數據。
實驗結果有點讓我失望,但畢竟比DT要強一些,不知道DT的1.1是不是也是這樣解決的,如果是的話,那不用等它的正式版本了,在JIRA上已經有參考實現了。
但學習ValueList還是有所收獲的,看到了另一種顯示實現的方式,而且ValueList的確比DT強,界面雖然不漂亮(0.1.7已經很有DT的意思了,通過自己定制樣式表,可以作出漂亮的外觀),但比較靈活,這個工具值得繼續關注。
使用ValueList需要注意的一點是,如果使用MVC框架(不知道其他的框架是否這樣),從一個Controller轉發到一個View上,如果ValueList的URL屬性為""或者"?",翻頁圖標的URL為上一個操作的URL,這和ValueList中提到的刷新按鈕有點相似,我又看了一下DT的用法,也需要指定URL,對于解決重復提交的問題,可以使用Struts的token來解決,我問了一下倦兔,倦兔說WebWork和Spring都有類似的解決方案,在此也感謝倦兔推薦ValueList!^_^
剛才和天天加班的段兄(SkyHero)聊了一下,感覺實在不行要自己改代碼了,比較郁悶(本來想偷懶的,呵呵。
祝段兄的加班生活早點結束,你的女朋友還在茫茫人海中等你去找她,不要把大好時光都浪費在計算機上!^_^
昨天,和朋友開車去天津,在濱江路上轉了一天,我在94年取過一次,發現十年后再看天津已經不是以前的那個城市了,看來是我的看法變了!不過昨天天津的氣溫有15度,很是舒服,好久沒有這樣放松了,希望每隔2個月,能出去轉轉!
兔八哥
2005-3-7 19:38
和倦兔聊了一下,他告訴我的解決方法竟然如此簡單:在Tomcat的server.xml的Connector部分添加URIEncoding="GBK",我的server.xml添加后內容如下:
<Connector URIEncoding="GBK" port="8080"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true" />
不用讀DisplayTag的源碼了,也可以安心過個年了,謝謝倦兔!!!
以下內容是我翻譯的JDK的幫助。
URLEncoder類:
用于HTML的form中數據編碼的類。
這個類包含將字符串轉換為application/x-www-form-urlencoded MIME 格式的靜態方法.
如果想了解HTML的編碼細則,請參考HTML規范。
編碼規則如下:
字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不被編碼,維持原值,
空格" "被轉換為加號"+"。
所有其他的字符都被認為是不安全的,首先都根據指定的編碼scheme被轉換為1個或者多個字節。[憑什么認為其他的字符都是不安全的?看來這些規范的制訂者中沒有中國人呀!]
然后每個字節都被表示成"%xy"格式的由3個字符組成的字符串,xy是字節的2位16進制的表達(xy is the two-digit hexadecimal representation of the byte),推薦的編碼scheme為UTF-8,然而,出于兼容性的考慮,如果沒有制定編碼的scheme,那么將使用當前操作系統的編碼的scheme。
如:如果編碼scheme是UTF-8,
"The string ü@foo-bar"將被轉換為"The+string+%C3%BC%40foo-bar" 。
因為載UTF-8中字符ü被編碼成2個字節C3 (十六進制) 和BC (十六進制), 字符@被編碼成一個字節40 (十六進制)。
起始于:JDK1.0
這個類共有2個重載方法:
public static String encode(String s, String enc) throws UnsupportedEncodingException。起始于:JDK1.4
和即將被廢棄的方法:public static String encode(String s)。(因為這個方法的編碼的字符集依賴于程序運行的系統的默認的字符集)。
第一個方法的作用是:根據指定的encode scheme 將一個字符串翻譯成application/x-www-form-urlencoded格式。
注意: W3C推薦UTF-8。
參數:
s - 將要被翻譯的字符串。
enc - 編碼用的character。
返回:翻譯后的字符串。
拋出異常: UnsupportedEncodingException - 如果不支持制定的編碼
起始于:1.4
另請參考:URLDecoder.decode(java.lang.String, java.lang.String)
類URLDecoder的作用和URLEncoder的作用相反,方法類似,這里就不再贅述了。
如果你想知道你的字符串被編碼后的值是什么樣,你可以打開www.baidu.com,然后輸入你要編碼后的數值,然后提交,你可以在地址欄看到你被編碼后的字符串,這個方法是Jason告訴我的,呵呵!
如果想解決DisplayTag的問題,就要修改源代碼了,下一步就是讀源代碼,頭疼ing......
兔八哥
2005-2-2下午16:30
在百度提交:The string ü@foo-bar |
請看:http://rabbit8.blogchina.com/blog/article_144619.859489.html |
不錯不錯,附上實現代碼 |
DisplayTag的默認的URL默認為上一次的URL,如果上一次的URL包含中文的話,則會被進行URLEncode,所以在翻頁的時候,又會把進行了URLEncode后的數據再次進行URLEncode,所以翻頁就沒有數據,因為這部分功能被封裝在DisplayTag中,于是,我又開始郁悶了......
如漢字"專業"被編碼后為"%D7%A8%D2%B5",于是DisplayTag就將這個編碼作為連接的關鍵字,如果再次提交,則這個編碼還會被編碼,所以查詢結果一定就不對了!!!
下面的內容是我分析這個問題的由來:
---------------------------------------------------------------------------------------------------
剛才到網上查找了些資料,找到HTML4.0.1的規范中關于URLEncode的部分,我把我關心的內容翻譯了一下:
http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
其中相關內容如下在17.13.3 Processing form data:
提交時,HTML的規范處理如下:
1.標識successful controls (概念可以在上文中查找)。
2.構建form的數據集。
3.根據form的enctype的設置,對form的數據集進行Encode。
4.提交已經Encode的數據集。
HTML規范中指出Content type和Languange code是不區分大小寫的。
詳情參見:http://www.w3.org/TR/html401/types.html#type-content-type
"&" 表示"&"。關于charset的詳細內容見:http://www.w3.org/TR/html401/charset.html#entities
有一個小發現:Frame中的target的值的列表原來是在HTML的規范中制定的,呵呵:
_blank 在一個沒有指定名字的新窗口中打開頁面。(new, unnamed window)
_self 在同一個窗口中打開。(load the document in the same frame as the element that refers to this target)
_parent 在當前窗口的父窗口中打開,如果當前窗口沒有父窗口,那么就等同于_self
_top 在最開始的窗口中轉載,如果當前框架沒有parent,那就等于_self。
form的默認的content type是:application/x-www-form-urlencoded
form提交content type的數據必須用下列規則進行編碼(encode):
空格被封裝為"+",其他的保留字封裝后的值在 [RFC1738]中可以查到。RF1738的規范:http://www.ietf.org/rfc/rfc1738.txt。
其他的非英文字符和非數字的字符都被編碼為"%HH",
HH是將字符的ASCII的編碼轉換為16進制后的字符。行尾是"CR LF" (如:`%0D%0A')。
控件的名字和數值之間使用"="分隔,多個控件之間用"&"分隔。
------------------------------------------------------------------------------------------------------------------
兔八哥
2005-2-2下午
它最大的價值就是為我們提供了一個Web開發的新的方式和思路,盡管這些技術在國外都已進很流行了,但在國內能夠將Hibernate、Struts、Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、Velocity、JUnit、JSTL、WebWork這些技術集成到一個框架中的還不多見,所以即使不使用它的全部功能,它也給我們提供了一個很好的借鑒、學習的機會。
AppFuse的作者 matt raible是當今開源世界一個比較活躍的開發者,它是AppFuse、Struts Menu的作者,也是XDoclet、DisplayTag等一些著名開源項目的積極參與者,《Hibernate In Action》的作者就在感謝的名單里面提到他,XDoclet的下載版本中所帶的Hibernate標簽部分的例子就是他寫的,他還是2004年Apache技術年會的主講人之一。(這些都是我這2個多月來搜集到的,呵呵)
通過關注AppFuse,我們可以看到目前國外的主流開發都使用了哪些技術,開發方式是什么樣的,可能達到什么樣的結果,而在以前,是很少能夠看到這樣完整的例子的。
AppFuse的另一個啟示是:我們可以依靠開源軟件的功能降低開發成本,而且可以閱讀開源軟件的代碼提高所在團隊的整體實力。
但是通過2個月的實際學習和使用,我也遇到一系列的問題,因為AppFuse是將其他的一些類庫或者框架集成在一起的,集成的技術眾多,而且有一些技術在國內甚至很少有人知道,資料也比較少,所以雖然作者經過了一些測試,但都是基于英文編碼的,而對于中文編碼來說,還潛在的存在著一些問題,雖然不是AppFuse的問題,但卻降低了開發速度,下面是我在開發過程中遇到過的問題,有些解決了,有些還沒有解決:
一.Struts
1. AppFuse中默認的MVC框架是Struts,而且使用的是LookupDispatchAction,并且使用的是按鈕(button),在XP下用IE瀏覽效果還可以,但如果在2000或者98下,就使外觀很難看,而且當時我還遇到一個問題:如果按鈕顯示中文,則在DisplayTag中翻頁失靈,而且報錯,后來我把BaseAction的相關方法改變了,才可以使用,因為國內的客戶都比較重視界面,所以后來我將那些按鈕都改成圖片了,當然也要添加一些方法了,有點麻煩!
2. Struts中的標簽如今推薦使用的只有html部分的標簽了,其他的標簽或者可以使用JSTL替代,或者已經不推薦使用了,而且AppFuse中推薦使用JSTL,而JSTL和struts的標簽的聯合使用時,需要的不是標簽<html:標簽>,而是標簽<html-el:標簽>,這個問題曾經困擾了我整整2天。3. Struts的Validation的校驗規則并不完善,比如如果使用客戶端的javascript校驗,則在郵箱中輸入漢字根本校驗不出來,到了服務器端報錯。
4. 最嚴重的問題是AppFuse生成的Struts的validation.xml文件中有許多多余的".",如果你去掉了,常常在執行ant的deploy任務時又恢復原樣。這樣是提交表單的時候經常會報javascript的腳本錯誤或者缺少對象或者缺少value,所以我會手工的修改這個文件,然后把修改后的文件備份,當重新生成有錯誤的文件時,我會用備份的沒有錯誤的文件去覆蓋。
5. Struts的validatioin對于使用同一個FormBean的Action的校驗方式比較復雜。(待解決)。
二.Hibernate
1. Hibernate是現在受到越來越多的人推崇的一個ORM工具(框架、類庫),它將我們從繁瑣的使用JDBC的開發過程中解放出來,但同時也帶來了新的問題,如學習曲線,執行效率,數據庫設計優化,還有最重要的靈活性。Hibernate不是一個很容易上手的東西,要完全駕馭它還需要讀很多資料,但好的資料卻很少。
2. 使用Xdoclet可以很方便的生成Hibernate中的持久類的配置文件(*.hbm.xml),但對一些特殊的映射卻無能為力,如使用序列的id生成規則,序列的名字沒有地方寫,所以也只好先利用它生成主要的內容,然后手工修改。
3. 同樣還是id的生成策略問題,如果使用序列、hilo等需要一些數據庫機制支持的策略時,schemaExport并不能自動生成序列或者保存當前id的表,這項工作仍然要手工解決。
4. Hibernate中提供了幾種關聯,一對一、一對多、多對多,但對于怎樣調整效率卻沒有一個很明確的提示,還要根據情況判定,這就帶來和一些彈性的設計。
5. Hibernate中可以選擇的操作數據庫的方式有3種,其中HQL功能最強大,但有些功能使用標準查詢可能會更方便,但會有一些限制,所以雖然它很靈活,但易用性不如JDBC好。
三.Spring
在AppFuse的過程中,Spring完全隱藏在幕后,除了一些配置外,幾乎感覺不到它的存在,所以我在使用它的過程中并沒有遇到什么麻煩,這里只是簡單的介紹一下它在AppFuse中起到的作用。
1. Spring在AppFuse中起到的主要作用是對Hibernate的Session和事務的管理,利用Spring封裝的Hibernate模板類,我們大大地減少了實現DAO的代碼行數。
2. Spring還起到了連接映射文件和類之間的關聯,及接口和實現類之間的關聯,這些都依賴于Spring的IoC的機制的實現。
3. 對于字符進行編碼和解碼部分用到了Spring自帶的Filter,只需要在配置文件中配置就好了。
四.SiteMesh
SiteMesh是一個基于Decorator模式的技術,它可以修飾返回的網頁文件,它的工作方式受到越來越多的人的推崇,這點從Manning出版的一些技術書籍中可以看出來。
我在使用SiteMesh的過程中并不順利,我參考了《Java Open Source Programming》,這本書中說SiteMesh在默認的情況下不對下載文件進行裝飾,但我在下載文件時發現,我的文件內容被丟棄了,取而代之的是SiteMesh的模板的內容,后來我通過修改SiteMesh的配置文件解決了這個問題,但感覺還有一些不太清楚的地方需要學習。
五.DisplayTag
DisplayTag是一個優秀的顯示內容的標簽,從SourceForge的訪問量來看,它是很活躍的項目,僅次于Ant、Hibernate、Xdoclet等幾個著名的項目,我總結,它的主要功能有4項:顯示、分頁、排序、將顯示的數據寫入指定類型的文件中,然后下載。
1. 據我使用的情況看,我只使用了分頁和顯示的功能,因為當時我沒有很好的解決中文編碼的問題,所以排序會有問題,直到昨天,我在朋友的幫助下解決了這個問題,至此我可以放心使用的功能又增加了排序(我昨天簡單的測試了一下是可以的)。
2. 但對于將顯示的內容生成到一個指定格式的文件中的功能卻有著很多缺陷,如:
(1) 生成的文件中只有顯示的數據,那些沒有顯示在界面上的的數據,則不會被寫到文件中。
(2) 如果修改了DisplayTag的顯示的內容,比如添加一列,在這列中的內容不是字符,而是HTML的標簽,則生成的文件只有這些HTML標簽,而沒有數據。
(3) 即使DisplayTag中沒有我們定制的HTML腳本,生成的文件偶爾也有問題,比如:它會把"007"生成為"7",把字符串自動的轉換為整型值。有時候還生成空白內容的文件。
(4) DisplayTag生成的Excel文件兼容性不好,有時在Excel2003中不能正常打開,或者在XP下打開報錯。
后來,我看了作者寫的《Spring Live》,書中說如果想實現穩定的Excel,推薦使用POI,于是我使用POI生成Excel,穩定性和兼容性都不錯。
六.DBUnit
DBUnit是一個可以被Ant集成的向數據庫中添加數據和備份數據的一個類庫,配置很方便,因為AppFuse已經集成好了,所以使用也很容易。
但是如果你使用EditPlus之類的工具手工修改了AppFuse生成的內容,則執行Ant的setup、setup-db或者deploy的任務時,常常報錯,說無效的格式,這是因為這個被手工修改的文件再次被AppFuse執行后,它的第一行的文件聲明的前幾個字母是無效的,是因為本地的字符集編碼的原因而引起了亂碼,如果把這幾個無效的字母去掉,問題就解決了。
七.Struts Menu
Struts Menu也是AppFuse的作者開發的一個開源軟件,它可以根據配置文件讀取當前用戶可以使用的功能菜單,這個功能是我一直以來都想要的,我也找到了一些代碼,但實現的都不如這個完善,沒什么好說的,使用簡單,配置容易,很好的解決了我的問題。
問題是我只使用了AppFuse提供的2個角色,對于多個角色的實驗我還沒有做。
八.XDoclet
在AppFuse中,使用Xdoclet生成了幾乎一切的配置文件:Struts-config.xml、web.xml、validation.xml、*.hbm.xml等文件,如果使用AppGen的話,還會生成更多的文件,這一切都是使用Xdoclet實現的。
問題是我在Struts部分提到的,生成的Validation.xml文件中會多生成一個".",另外在生成資源文件時也會多生成一個".",目前我沒有很好的閱讀這段代碼,不知道是不是Xdoclet的問題。
九.Ant
Ant并沒有什么問題,但在執行作者寫的Ant任務的時候,有一些任務不能正常執行,比如,運行模擬對象測試的任務,作者也在1.7版本的修復列表中提到以前版本有些ant任務不能執行,在1.7中修改了一些ant任務,使他們能夠正常的執行了。
實際上,我們如果使用AppGen進行開發的話,使用的任務一般不超過8個。
十.JSTL
JSTL是個好東西,我常用的有和部分的標簽,但是如果使用JSTL進行邏輯判斷,我并沒有感覺比使用JSP的代碼塊優雅多少。另外,熟悉JSTL也需要一段時間,我就經歷了面對著JSP頁面不知道該怎么寫JSTL語法的困境。當然,AppFuse中使用的基本都是JSTL,包括向DisplayTag傳遞顯示的數據,使用的都是JSTL語法,這方面的資料挺多,我參考的是電子工業出版社出的《JSP2.0技術》,說的很詳細。
十一.Tomcat
你也許會說:"Tomcat就不用說了吧?",是的,Tomcat一般都會使用,但是―――――――――――――Tomcat5和Tomcat4.X對于中文編碼使用了不同的機制,這個問題困擾了我好久,我解決了頁面上寫入漢字顯示亂碼的問題,我也曾經以為DisplayTag對漢字不能排序,也不能正常分頁是因為DisplayTag的開發者都是老外,是因為他們沒有考慮中文的關系的原因。
直到昨天,我才知道這一切都是因為Tomcat5對漢字編碼的實現的方式和Tomcat4不一樣的原因,如果感興趣,可以看看這個帖子:http://www.javaworld.com.tw/jute/post/view?bid=9&id=44042&sty=1&tpg=1&age=0
再次感謝倦兔!:D
十二.JavaScript
JavaScript簡單易學,但想運用自如就不太容易了。AppFuse中嵌入了幾個js文件,里面包含了許多函數,值得我們好好的研究一下,比如,如果有一個必填字段沒有填寫,AppFuse會自動的聚焦在那個input上,類似的小技巧有很多,你可以自己去翻看。
但AppFuse自帶的JavaScript腳本有一個Bug,就是當DisplatyTag中沒有可以顯示的數據時,你用鼠標單擊,它會報JavaScript錯誤,你仔細研究一下function highlightTableRows(tableId) 就知道了:我的解決辦法是在location.href = link.getAttribute("href");前面添加一行判斷:if (link != null)。十三.資源文件國際化
對于Struts和DisplayTag都涉及到資源文件國際化AppFuse1.6.1很好的解決了Struts資源映射文件國際化的問題,你只需要在對應本國語言的資源文件中寫入漢字,Ant中有一項執行native2ascii的任務,AppFuse自動的為你進行了資源文件的編碼轉換,而對于DisplayTag的資源文件問題,還要自己執行native2ascii命令,為了避免每次都輸入一串命令,我用Delphi寫了個小工具,可視化的選擇資源文件,點擊按鈕自動執行該命令,底層依賴于JDK。
經過2個多月的學習,我感覺這個框架非常不錯,它為我以后的項目開發指出了一個新的方向,但如果想很熟練的使用這個框架進行開發,至少要對以下幾種技術比較熟練:Struts(或者WebWork、Spring及其他的已經整合進來的MVC框架)、Hibernate(或者ibatis)、JSTL,當然其他的技術至少也要知道一點,否則遇到問題都不知道出在哪里。
目前我還沒有解決的問題有:
1. 如何在翻頁的時候才讀取下面的數據?
2. 怎樣對使用同一個FormBean的多個Form進行客戶端校驗?
3. 怎樣優化Hibernate的效率?《Hibernate In Action》中提供了多種策略,有些時候應該使用lazy,有些時候應該使用outer-join。
4. 在什么時機生成導出文件?目前我是在查詢的Action中同時生成了導出文件,否則,到了下一頁,我就不知道查詢條件了,當然,如果把拼裝后的HQL存儲在Session或者Hidden中也可以解決這個問題,但是這樣就破壞了DAO的封裝,要把DAO封裝后的HQL發送給Action,然后發送的到Web界面層,所以目前我還在猶豫生成導出文件的時機選擇在哪里?
5. 什么時候應該自己獲取數據庫連接,執行native SQL?具體需要注意些什么?
6. SiteMesh的模板優化?
7. DisplayTag的底層實現?
每個問題都比較棘手,要一個一個解決!
這個框架的優點是:如果熟悉了開發流程,可以大幅度的提高開發速度,如果業務不是很復雜,使用AppGen可以生成60%左右的代碼,而且程序可維護性好,因為作者使用了多個設計模式對各個層面進行了封裝,所以不同的模塊代碼風格出奇的一致,有利于開發人員快速上手,有利于接收其他開發人員遺留的代碼。
兔八哥
native2ascii在Ant中有任務,可以寫
<target name="makeresource">
<delete file="${resource.dir}/ApplicationResources_zh_CN.properties"/>
<native2ascii src="${resource.dir}" dest="${resource.dir}" includes="ApplicationResources_cn.properties" encoding="GBK">
<mapper refid="resourcemapper"/>
<mapper id="resourcemapper" type="glob" from="*_cn.properties" to="*_zh_CN.properties"/>
</native2ascii>
</target>
Features include Container Managed Authentication (CMA), Remember Me, Self Registration, Password Hint and GZip Compression. The fuse to start your apps.
作者的介紹:
http://today.java.net/pub/a/today/2004/07/15/thefuse.html
官方網站:
http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse
http://appfuse.dev.java.net/
http://jroller.com/page/raible