了解 Ant 1.6 的新特性以及它們?nèi)绾斡绊懩M織編譯過(guò)程的方式。
雖然 Ant 版本的 1.5.x 系列在任務(wù)級(jí)方面有很大的改善,但它沒(méi)有改變?nèi)藗兪褂?Ant 的方式。而 Ant 1.6 卻有所不同。它增加了幾個(gè)新特性,以支持大型或非常復(fù)雜的編譯情況。但是,要充分利用它們的功能,用戶可能需要稍微調(diào)整它們的編譯過(guò)程。
本文重點(diǎn)介紹了其中的三種新特性 — <macrodef>、<import>、<subant> 任務(wù),表明使用它們可以有什么收獲,以及它們?nèi)绾斡绊懩M織編譯設(shè)置的方式。
宏
大多數(shù)編譯工程師遲早會(huì)面臨必須執(zhí)行相同的任務(wù)組合但在幾個(gè)地方配置稍微有點(diǎn)不同的情況。一個(gè)常見的例子是創(chuàng)建一個(gè)web 應(yīng)用程序存檔,對(duì)于開發(fā)系統(tǒng)、測(cè)試系統(tǒng)和生產(chǎn)系統(tǒng)有著不同的配置。
讓我們假設(shè) web 應(yīng)用程序擁有依賴于目標(biāo)系統(tǒng)的不同的 web 部署描述符,并為開發(fā)環(huán)境使用了一個(gè)不同的 JSP 集合以及一個(gè)不同的資料庫(kù)集合。配置信息將放在屬性中,創(chuàng)建 web 存檔的任務(wù)看起來(lái)將類似于
<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 是引用一個(gè)在其它位置定義的 <fileset> ,該引用指向您的應(yīng)用程序所需的附加資料庫(kù)的一個(gè)公共集合。
如果您只想一次創(chuàng)建一個(gè) web 存檔,那么您只需要正確地設(shè)置屬性。比如說(shuō),您可以從一個(gè)您的目標(biāo)專有的屬性文件中加載它們。
利用 Ant 1.5 創(chuàng)建存檔
現(xiàn)在,假定您想為測(cè)試系統(tǒng)和生產(chǎn)系統(tǒng)同時(shí)創(chuàng)建存檔,以確保您真正為兩個(gè)系統(tǒng)打包了相同的應(yīng)用程序。利用 Ant 1.5,您可能使用 <antcall> 來(lái)調(diào)用擁有不同屬性設(shè)置的 "war" 目標(biāo),類似:
<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>
當(dāng)然,這假定兩個(gè)目標(biāo)系統(tǒng)都將使用相同的 jar 和 JSP。
但這種方法有一個(gè)主要缺點(diǎn) — 就是速度慢。<antcall> 重新分析編譯文件,并為每一次調(diào)用重新運(yùn)行調(diào)用的目標(biāo)所依賴的所有目標(biāo)。在上面的例子中,"jar" 目標(biāo)將被運(yùn)行兩次。我們希望這對(duì)第二次調(diào)用沒(méi)有影響,因?yàn)?"war" 目標(biāo)依賴于它。
利用 Ant 1.6 創(chuàng)建存檔
使用 Ant 1.6,您可以忘掉用 <antcall> 來(lái)實(shí)現(xiàn)宏的方法,相反您可以通過(guò)參數(shù)化現(xiàn)有的任務(wù)來(lái)創(chuàng)建一個(gè)新的任務(wù)。因而上面的例子將變?yōu)椋?/SPAN>
<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>
這定義了一個(gè)名稱為 makewar 的任務(wù),該任務(wù)可以和任何其它的任務(wù)一樣使用。該任務(wù)有兩個(gè)必需的屬性,webxml 和 destfile。要使屬性可選,我們必需在任務(wù)定義中提供一個(gè)默認(rèn)值。這個(gè)示例假定 ${jar.name} 和 ${jsps} 在編譯期間為常量,從而它們?nèi)匀蛔鳛閷傩灾付āW⒁猓瑢傩栽谑褂萌蝿?wù)時(shí)展開而不是在定義宏的地方展開。
所用任務(wù)的特性幾乎完全和屬性一樣,它們通過(guò) @{} 而不是 ${} 展開。與屬性不同,它們是可變的,也就是說(shuō),它們的值可以(并將)隨著每一次調(diào)用而改變。它們也只在您的宏定義程序塊內(nèi)部可用。這意味著如果您的宏定義還包含了另一個(gè)定義了宏的任務(wù),那么您內(nèi)部的宏將看不到包含的宏的屬性。
于是新的 production-wars 目標(biāo)將類似于:
<target name="production-wars"> <makewar destfile="${staging.war.name}" webxml="${staging.web.xml}"/> <makewar destfile="${production.war.name}" webxml="${production.web.xml}"/> </target>
這個(gè)新的代碼段不僅執(zhí)行得快一些,而且也更易讀,因?yàn)閷傩悦Q提供了更多的信息。
宏任務(wù)還可以定義嵌套的元素。<makewar> 定義中的 <war> 任務(wù)的嵌套 <fileset> 可以是這種嵌套元素的一種。可能開發(fā)目標(biāo)需要一些額外的文件或想從不同的位置中挑選 JSP 或資源。以下代碼段將一個(gè)可選的嵌套 <morefiles> 元素添加到了 <makewar> 任務(wù)中
<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>
調(diào)用將類似于:
<makewar destfile="${development.war.name}" webxml="${development.web.xml}"> <morefiles> <fileset dir="${development.resources}"/> <lib refid="development-support-libraries"/> </morefiles> </makewar>
這就像 <morefiles> 的嵌套元素直接在 <war> 任務(wù)內(nèi)部使用的效果一樣。
即使迄今為止的示例僅顯示了包裝單個(gè)任務(wù)的 <macrodef>,但它不限于此。
下面的宏不僅將創(chuàng)建 web 存檔,還將確保包含最終存檔的目錄在試圖寫入之前存在。在一個(gè)實(shí)際的編譯文件中,您可能在調(diào)用任務(wù)之前使用一個(gè)設(shè)置目標(biāo)來(lái)完成這個(gè)操作。
<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>
這里注意兩件事情:
首先,特性在屬性展開之前展開,因此結(jié)構(gòu) ${@{destfile}.parent} 將展開一個(gè)名稱包含了 destfile 特性的值和 ".parent" 后綴的屬性。這意味著您可以將特性展開嵌入到屬性展開中,而不是將屬性展開嵌入特性展開中。
其次,這個(gè)宏定義了屬性,該屬性的名稱基于一個(gè)特性的值,因?yàn)?Ant 中的屬性是全局的并且不可改變。第一次嘗試使用
<dirname property="parent" file="@{destfile}"/>
相反將不會(huì)在 "production-wars" 目標(biāo)中的第二次 <makewar> 調(diào)用產(chǎn)生期望的結(jié)果。第一次調(diào)用將定義一個(gè)新的名稱為 parent 的屬性,該屬性指向父目錄 ${staging.war.name}。第二次調(diào)用將查看這個(gè)屬性但不會(huì)修改它的值。
預(yù)期 Ant 未來(lái)的版本將支持某些類型的限定范圍的屬性,這種屬性只在宏執(zhí)行期間定義。在此之前,使用特性的名稱來(lái)構(gòu)建屬性名稱是一種變通辦法,潛在的副作用是要?jiǎng)?chuàng)建大量的屬性。
提示:如果您查看您的編譯文件時(shí)發(fā)現(xiàn)使用了 <antcall> 代替宏,那么強(qiáng)烈建議您考慮使用 macrodef 將其轉(zhuǎn)換成真正的宏。性能影響可能非常顯著,并且還可能產(chǎn)生更易讀和更易于維護(hù)的編譯文件。 |
將一個(gè)編譯文件分成多個(gè)文件有幾個(gè)原因。
共享公用功能/在 Ant 1.6 之前包含文件
在 Ant 1.6 之前,您唯一的選擇是實(shí)體包含的 XML 方法,類似于:
<!DOCTYPE project [ <!ENTITY common SYSTEM "file:./common.xml"> ]> <project name="test" default="test" basedir="."> <target name="setup"> ... </target> &common; ... </project>
摘自 Ant 常見問(wèn)題解答。
這種方法有兩個(gè)主要的缺點(diǎn)。您不能使用 Ant 屬性指向您想包含的文件,因此被迫在您的編譯文件中對(duì)位置進(jìn)行硬編碼。您想包含的文件只是一個(gè) XML 文件的一部分,它可能沒(méi)有一個(gè)根元素,因而使用支持 XML 的工具進(jìn)行維護(hù)更加困難。
共享公用功能/使用 Ant 1.6 包含文件
Ant 1.6 自帶了一個(gè)名稱為 import 的新任務(wù),您現(xiàn)在可以使用它。上面的示例將變?yōu)?/SPAN>
<project name="test" default="test" basedir="."> <target name="setup"> ... </target> <import file="common.xml"/> ... </project>
因?yàn)樗且粋€(gè)任務(wù),因此您可以使用 Ant 所有的特性來(lái)指定文件位置。主要的差異是被導(dǎo)入的文件本身必須是一個(gè)有效的 Ant 編譯文件,因而必須有一個(gè)名稱為 project 的根元素。如果您想從實(shí)體包含轉(zhuǎn)換到導(dǎo)入,那么您必須在導(dǎo)入的文件的內(nèi)容首尾放上 <project> 標(biāo)記;然后 Ant 將在讀取文件時(shí)再次劃分它們。
注意文件名稱由 Ant 任務(wù)根據(jù)編譯文件的位置(而不是指定的基本目錄)確定。如果您沒(méi)有設(shè)置項(xiàng)目的 basedir 屬性或?qū)⑵湓O(shè)為 ".",那么您將不會(huì)注意到任何差異。如果您需要根據(jù)基本目錄解析一個(gè)文件,那么您可以使用一個(gè)屬性作為變通辦法,類似于:
<property name="common.location" location="common.xml"/> <import file="${common.location}"/>
屬性 common.location 將包含文件 common.xml 的絕對(duì)路徑,并已根據(jù)導(dǎo)入項(xiàng)目的基本目錄解析。
使用 Ant 1.6,所有的任務(wù)都可能放在目標(biāo)之外或之內(nèi),除了兩個(gè)例外。<import> 一定不能嵌入到目標(biāo)中,<antcall> 一定不能在目標(biāo)外使用(否則它將創(chuàng)建一個(gè)無(wú)限循環(huán))。
而 <import> 可做的不僅僅是導(dǎo)入另一個(gè)文件。
首先,它定義了名稱為 ant.file.NAME 的特殊屬性,其中 NAME 替換為每一個(gè)導(dǎo)入文件的 <project> 標(biāo)記的名稱屬性。這個(gè)屬性包含了導(dǎo)入文件的絕對(duì)路徑,導(dǎo)入文件可用來(lái)根據(jù)它自己的位置(而不是導(dǎo)入文件的基本目錄)定位文件和資源。
這意味著 <project> 的名稱屬性在 <import> 任務(wù)環(huán)境中變得更加重要。它還用來(lái)為在被導(dǎo)入的編譯文件中定義的目標(biāo)提供別名。如果導(dǎo)入了以下文件
<project name="share"> <target name="setup"> <mkdir dir="${dest}"/> </target> </project>
導(dǎo)入編譯文件可以查看作為 "setup" 或 "share.setup" 的目標(biāo)。后者在目標(biāo)覆蓋的上下文中變得非常重要。
讓我們假定有一個(gè)包含了多個(gè)獨(dú)立的組件(每個(gè)組件擁有它自己的編譯文件)的編譯系統(tǒng)。這些編譯文件幾乎相同,因此我們決定將公用功能轉(zhuǎn)移到一個(gè)共享和已導(dǎo)入的文件中。為了簡(jiǎn)單起見,我們只介紹 Java 文件的編譯和創(chuàng)建結(jié)果的一個(gè) 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>
這個(gè)文件不會(huì)作為一個(gè)獨(dú)立的 Ant 編譯文件進(jìn)行工作,因?yàn)樗鼪](méi)有定義 "setup" 所依賴的 "set-properties" 目標(biāo)。
組件 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>
它僅設(shè)置適當(dāng)?shù)沫h(huán)境,然后將全部的編譯邏輯交給被導(dǎo)入的文件負(fù)責(zé)。注意該編譯文件創(chuàng)建了一個(gè)空的路徑作為編譯 CLASSPATH,因?yàn)樗亲园摹DK 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 的編譯文件幾乎一樣,因此似乎有可能將大多數(shù)的 set-properties 目標(biāo)也推送到 shared.xml 中。實(shí)際上,我們可以假定有一個(gè)對(duì) dest 和 src 目標(biāo)一致的命名慣例,以實(shí)現(xiàn)這一目的。
<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 是一個(gè)內(nèi)置的屬性,它包含了最外面的 <project> 標(biāo)記的名稱屬性的值。因此,如果模塊 A 的編譯文件導(dǎo)入了 share.xml,那么它將擁有值 A。
注意,所有的文件都與導(dǎo)入編譯文件的基本目錄相關(guān),因此 scr 屬性的實(shí)際值依賴于導(dǎo)入文件。
為此,A 的編譯文件將簡(jiǎn)單地變?yōu)?/SPAN>
<project name="A" default="jar"> <path id="compile-classpath"/> <import file="../share.xml"/> </project>
B 的編譯文件將變?yōu)?/SPAN>
<project name="B" default="jar"> <path id="compile-classpath"> <pathelement location="../dest/A/module-A.jar"/> </path> <import file="../share.xml"/> </project>
現(xiàn)在假定 B 增加了一些 RMI 接口,需要在編譯類之后但在創(chuàng)建 jar 之前運(yùn)行 <rmic>。這就是目標(biāo)覆蓋能派上用場(chǎng)的地方。如果我們?cè)趯?dǎo)入編譯文件中定義了一個(gè)目標(biāo),該目標(biāo)與被導(dǎo)入的編譯文件中的一個(gè)目標(biāo)名稱相同,那么將使用導(dǎo)入編譯文件中的目標(biāo)。例如,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" 目標(biāo),而不是 share.xml 中的目標(biāo);然而,不幸的是,這只是從共享那里復(fù)制 <javac> 任務(wù)。一種更好的解決方案是:
<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" 在原來(lái)的 "compile" 目標(biāo)使用之后運(yùn)行 <rmic>。
如果我們想在編譯之前生成一些 Java 源代碼(例如通過(guò) XDoclet),我們可以使用類似下面的方法:
<import file="../share.xml"/> <target name="compile" depends="setup,xdoclet,share.compile"/> <target name="xdoclet"> .. details of XDoclet invocation omitted .. </target>
因此您可以完全覆蓋一個(gè)目標(biāo)或通過(guò)在原始目標(biāo)之前或之后運(yùn)行任務(wù)來(lái)增強(qiáng)它。
這里要注意一個(gè)危險(xiǎn)。目標(biāo)覆蓋機(jī)制使導(dǎo)入編譯文件依賴于在導(dǎo)入文件中使用的名稱屬性。如果任何人修改了導(dǎo)入文件的名稱屬性,那么導(dǎo)入編譯文件將被破壞。Ant 開發(fā)社區(qū)目前正在討論在 Ant 的一個(gè)未來(lái)的版本中為此提供一個(gè)解決方案。
提示:如果您在編譯文件中發(fā)現(xiàn)了非常常見的結(jié)構(gòu),那么值得嘗試將文件重構(gòu)為一個(gè)(一些)共享文件,并在必要時(shí)使用目標(biāo)覆蓋。這可以使您的編譯系統(tǒng)更加一致,并讓您能夠重用編譯邏輯。 |
在某種意義上,subant 是兩種任務(wù)合二為一,因?yàn)樗私獠僮鞯膬煞N模式。
如果您使用 <subant> 的 genericantfile 屬性,那么它的工作方式和 <antcall> 一樣,調(diào)用包含任務(wù)的同一個(gè)編譯文件中的目標(biāo)。與 <antcall> 不同,<subant> 獲取目錄的列表或集合,并將為每一個(gè)目錄調(diào)用一次目標(biāo),以設(shè)定項(xiàng)目的基本目錄。如果您想在任意數(shù)量的目錄中執(zhí)行完全一樣的操作,那么這非常有用。
第二種模式不使用 genericantfile 屬性,而獲取一個(gè)編譯文件的列表和集合進(jìn)行迭代,以在每一個(gè)編譯文件中調(diào)用目標(biāo)。這種工作方式類似于在一個(gè)循環(huán)中使用 <ant> 任務(wù)。
第二種形式的典型情景是幾個(gè)能夠獨(dú)立編譯的模塊的一個(gè)編譯系統(tǒng),但是該系統(tǒng)需要一個(gè)主編譯文件來(lái)一次性編譯所有的模塊。
使用以下資源了解關(guān)于 Ant 的更多信息,并開始編譯和部署 Java 項(xiàng)目。 Ant 業(yè)界趨勢(shì) 閱讀關(guān)于 JDeveloper 中的 Ant 集成的更多信息 下載 Oracle JDeveloper 10g 測(cè)試驅(qū)動(dòng):將 Ant 用于編譯 Ant 入門第 1 部分 Ant 入門第 2 部分 在 Linux 上創(chuàng)建 Java 應(yīng)用程序的命令行方法 閱讀關(guān)于 Ant 的更多信息 相關(guān)文章與下載 Blog: 如何從一個(gè) JDeveloper 項(xiàng)目文件中將屬性動(dòng)態(tài)檢索到一個(gè) ant 編譯文件中? |
在 Ant 1.6 之前構(gòu)建主編譯文件
在導(dǎo)入部分中討論的例子使用了這樣一個(gè)主編譯文件。
<target name="build-all"> <ant dir="module-A" target="jar"/> <ant dir="module-B" target="jar"/> </target>
在 Ant 1.6 之前的 Ant 中。
使用 Ant 1.6 構(gòu)建主編譯文件
在 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>
這看起來(lái)并沒(méi)有很大的改善,因?yàn)槟匀槐仨殕为?dú)指定每一個(gè)子編譯文件。相反如果您轉(zhuǎn)用 <fileset>,那么情況將有所改觀。
<target name="build-all"> <subant target="jar"> <fileset dir="." includes="module-*/build.xml"/> </subant> </target>
這將自動(dòng)發(fā)現(xiàn)所有模塊的編譯文件。如果您增加了一個(gè)模塊 C,主編譯文件中的目標(biāo)不需要修改。
但小心。與 <filelist> 或 <path>(也被 <subant> 支持)不同,<fileset> 是無(wú)序的。在我們的例子中,模塊 B 依賴于模塊 A,因此我們需要確保首先編譯模塊 A,而使用 <fileset> 沒(méi)有辦法這么做。
如果編譯完全彼此獨(dú)立或者它們對(duì)于一個(gè)給定的操作彼此獨(dú)立,那么 <fileset> 仍然有用。模塊 B 的文檔目標(biāo)可能完全不依賴于模塊 A,同樣還有從您的 SCM 系統(tǒng)中更新源代碼的目標(biāo)。
如果您想將編譯文件的自動(dòng)發(fā)現(xiàn)與根據(jù)編譯的相互依賴性對(duì)編譯進(jìn)行排序結(jié)合在一起,那么您將必須編寫一個(gè)定制的 Ant 任務(wù)。基本的想法是編寫一個(gè)使用 <fileset> 的任務(wù)(讓我們目前稱之為 <buildlist>),確定依賴關(guān)系并計(jì)算 <subant> 必須使用的順序。然后它創(chuàng)建一個(gè)以正確的順序包含編譯文件的 <path>,然后將對(duì)這個(gè)路徑的一個(gè)引用放到項(xiàng)目中。調(diào)用將類似于
<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>
這個(gè)假想的 buildlist 任務(wù)已經(jīng)在 Ant 用戶郵件列表和 bug 跟蹤系統(tǒng)中進(jìn)行了討論。很有可能 Ant 的一個(gè)將來(lái)的版本中將包含這樣的一個(gè)任務(wù)。
在 Ant 1.6 中已經(jīng)增加了大量的新特性。這些新功能中的許多功能使得編譯模板易于創(chuàng)建、構(gòu)造和定制。特別是 <import> 和 <target> 進(jìn)行了覆蓋。<import>、<macrodef> 和 <subant> 特性很有可能使得 Ant 編譯可高度重用。<scriptdef>(本文中未討論)對(duì)于需要一些腳本但不想用 Java 編寫定制任務(wù)的人而言可能非常有吸引力。
代碼 |
#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 { // 確保該過(guò)濾器在每次請(qǐng)求中只被使用一次 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)); } // 傳遞請(qǐng)求 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
測(cè)試通過(guò),自定義語(yǔ)種問(wèn)題解決.
相關(guān)鏈接:
http://forum.javaeye.com/viewtopic.php?t=11073&start=0
8.
今天發(fā)現(xiàn)5.8.4版本的myeclipse的tag屬性面板挺好用的.good.
9.
有關(guān)webwork的javascript驗(yàn)證能力參看以下帖子:
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
在執(zhí)行 ant setup的時(shí)候,在compile-module的時(shí)候,總是說(shuō)找不到valuelist的類,后來(lái)發(fā)現(xiàn)AppFuse的jar都添加到了builde.xml中,于是我也把valuelist.jar添加過(guò)去了,發(fā)現(xiàn)問(wèn)題解決了,方法如下:
AppFuse執(zhí)行Setup后自動(dòng)將valuelist.jar復(fù)制到Tomcat下:
在appfuse\lib\lib.properties中添加如下內(nèi)容:
#
# 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}"/> //這是我添加的內(nèi)容
<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>
兔八哥
關(guān)于URLEncoder的解析問(wèn)題
在http://rabbit8.blogchina.com/blog/article_144619.789425.html后,有個(gè)朋友留言,說(shuō)在百度試驗(yàn)的結(jié)果和我文章中說(shuō)的不一致,我做了個(gè)實(shí)驗(yàn),證實(shí)JDK的幫助沒(méi)錯(cuò),原因如下:
我的試驗(yàn)代碼如下:
public static void main(String[] args) {
URLEncoder urle = null;
//得到默認(rèn):%A8%B9
System.out.println("默認(rèn):" + 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來(lái)查看"ü"的ASCII的話,得到的結(jié)果如圖:
可見,UltraEdit使用的是操作系統(tǒng)默認(rèn)的編碼方式(實(shí)際上,MS采用的也不是GBK,而是另一種編碼,但效果和GBK差不多),所以它顯示的ASCII的編碼為A8 B9,就是第一和第二種情況的結(jié)果。而第三種情況才是JDK幫助中所聲明的情況。
我查看了百度,提交了一下,結(jié)果和我預(yù)期的是一樣的!
如果你查看頁(yè)面的源文件,會(huì)看到百度的charset為gb2312,而幫助中明確提到例子使用的是UTF-8編碼,所以出現(xiàn)了不一致的問(wèn)題,也正是因?yàn)檫@個(gè)原因,所以JDK中決定要廢棄public static String encode(String s)方法,因?yàn)檫@個(gè)方法的編碼的字符集依賴于程序運(yùn)行的系統(tǒng)的默認(rèn)的字符集!
兔八哥
2005-2-15 17:41
關(guān)于AppFuse中使用中文編碼的問(wèn)題
這2個(gè)月一直被DisplayTag折騰,如果DisplayTag的鏈接或者排序的列中有漢字,那么排序或者翻頁(yè)都會(huì)查詢不到結(jié)果或報(bào)錯(cuò)(如果你處理的好,可能會(huì)顯示沒(méi)有可以顯示的數(shù)據(jù))。
我一直以為這個(gè)問(wèn)題是因?yàn)?FONT size=+0>DisplayTag的開發(fā)者們沒(méi)有考慮到中文的問(wèn)題,因?yàn)轫?xiàng)目進(jìn)度很緊,所以就把排序的功能給屏蔽了,雖然經(jīng)過(guò)了大量的測(cè)試,但都沒(méi)有發(fā)現(xiàn)翻頁(yè)時(shí)候也存在這個(gè)問(wèn)題(AppFuse自動(dòng)生成的頁(yè)面的默認(rèn)顯示25行,如果超過(guò)25行會(huì)自動(dòng)分頁(yè)),直到前天,測(cè)試人員才發(fā)現(xiàn)這個(gè)問(wèn)題,按照我的理解要修改DisplayTag的源碼,后來(lái)又想通過(guò)自己寫過(guò)濾器試試,幸運(yùn)的是在我動(dòng)手干之前,先和倦兔聊了幾句,結(jié)果倦兔說(shuō)他也遇到過(guò)這個(gè)問(wèn)題,指出如果在server.xml中添加URIEncoding="GBK"就可以解決問(wèn)題,我試了一下,果然可以,但這是為什么呢?
在AppFuse中有一個(gè)Spring的過(guò)濾器,對(duì)請(qǐng)求的編碼進(jìn)行轉(zhuǎn)換,這種做法很通用,即使是在以前,我們自己寫這個(gè)過(guò)濾器也不麻煩,而且這個(gè)代碼在網(wǎng)上隨處可見,難道是Spring的過(guò)濾器沒(méi)有起作用嗎?也不是,如果沒(méi)起作用,那么我在Struts中的Action中通過(guò)request.getParmeter接收到參數(shù)應(yīng)該是亂碼?而且我記得這種做法在Tomcat4.1.24中沒(méi)有任何問(wèn)題,那是為什么?和倦兔討論了一下,我們都沒(méi)有找到答案。
于是,我就通過(guò)google搜索"URIEncoding",找到一堆文章,我讀了幾篇文章,其中有一篇提到:Tomcat5中對(duì)Post和Get請(qǐng)求不再采用相同的處理策略了,而在Tomcat4中采用的處理策略是相同的。于是我想:"我的提交的請(qǐng)求明明是Post,我又沒(méi)有用Get請(qǐng)求,那Spring的過(guò)濾器就應(yīng)該起作用,為什么結(jié)果會(huì)這樣呢?",我的一個(gè)jsp中的Form的代碼如下:
<html:form action="editGj" method="post" styleId="gjForm"
focus="mc" onsubmit="return validateGjForm(this)">
看,我的代碼明明是使用的是Post方法,為什么結(jié)果不正確呢?
我的跳轉(zhuǎn)到下一頁(yè)的代碼是:
<a href="?d-449687-p=2&mc=%D7%A8%D2%B5&;
excelname=%D7%A8%D2%B5%C3%FB%B3%C6.xls">下一頁(yè)</a>
加粗的部分就是我的翻頁(yè)的鏈接。如果你立刻就看出了我的問(wèn)題,那么你就可以不用往下看了,呵呵。如果你沒(méi)有看出問(wèn)題,請(qǐng)繼續(xù),Come On Baby!!!
我采用的真的是Post方法嗎?我的form的提交采用的是Post方法,這是沒(méi)錯(cuò)的,那么鏈接采用的是Post方法嗎?
不是!!!!它采用的是Get方法進(jìn)行提交的,這就是問(wèn)題的答案了!
Post和Get提交有什么區(qū)別嗎?
簡(jiǎn)單的說(shuō):Post是將地址傳送一次,將form的數(shù)據(jù)單獨(dú)提交,而Get則是將地址和參數(shù)一起傳送,傳送的不止是form的數(shù)據(jù),但傳送的數(shù)據(jù)的長(zhǎng)度有字節(jié)限制,另外還有安全問(wèn)題。如果感興趣,你可以用google搜一下,會(huì)找到很多資料。
當(dāng)我想到這個(gè)區(qū)別時(shí),我又檢查了一下我的顯示列表的頁(yè)面,發(fā)現(xiàn)這個(gè)頁(yè)面沒(méi)有form,只有,這說(shuō)明我的提交的確是Get的,于是,Tomcat5對(duì)我的Get請(qǐng)求執(zhí)行的是另外的處理方式,和Post的處理方式不再一樣了!
這樣我們提交的漢字被認(rèn)為是ISO-8859-1的編碼,所以在程序中接收時(shí)顯示亂碼,如果使用過(guò)濾器,在過(guò)濾器中調(diào)用request.setCharacterEncoding("GBK")的話,那么Post上來(lái)的漢字將被認(rèn)為是GBK編碼,而Tomcat5對(duì)于Get請(qǐng)求上來(lái)的編碼并不根據(jù)過(guò)濾器的設(shè)定辨認(rèn)編碼方式,默認(rèn)的依然是ISO-8859-1,Tomcat5中處理Get請(qǐng)求的源代碼如下:
上面的代碼中,enc代表Tomcat5中server.xml中Connector部分的URIEnocodeing項(xiàng)設(shè)定的值,可以看出,如果沒(méi)有設(shè)定URIEncoding項(xiàng)的話,那么Tomcat5也并沒(méi)有使用默認(rèn)的ISO-8859-1編碼,而是一段fast conversion,所以,即使你的頁(yè)面使用默認(rèn)的編碼方式進(jìn)行編碼,然后使用ISO-8859-1進(jìn)行解碼,得到的結(jié)果也不對(duì),這可能是Tomcat5的一個(gè)Bug。如果想繞過(guò)這個(gè)Bug,那么只有在server.xml的Connector部分設(shè)定URIEncoding的值,根據(jù)編碼方式指定自己的值,在我的程序中我使用的是GBK,所以我修改后的部分如下:
acceptCount="100"
這個(gè)問(wèn)題我參考了這篇文章:
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另外,對(duì)于編碼解碼的問(wèn)題,我還有一句要說(shuō),理論上,如果編碼的charset和解碼的charset是一致的話,那么就應(yīng)該沒(méi)有亂碼的問(wèn)題,但是,對(duì)于某些特殊的字符來(lái)說(shuō),如果采用的charset不對(duì),則可能在解碼的時(shí)候不能顯示,所以要選擇好字符集,我推薦在處理簡(jiǎn)體漢字的時(shí)候使用GBK。網(wǎng)上也有推薦UTF-8的,具體使用哪種依情況而定。
以上是我的總結(jié),若有錯(cuò)誤,請(qǐng)指正,謝謝!
明天,我就放假了,祝我的朋友們春節(jié)快樂(lè),萬(wàn)事勝意!
兔八哥
2005年2月4日14:04
另外,我昨晚看了看Tomcat5自帶的Servlet Example中過(guò)濾器的實(shí)現(xiàn),其中就有關(guān)于處理編碼的過(guò)濾器SetCharacterEncodingFilter,還有另外一個(gè)是處理用戶輸入的過(guò)濾器,叫HTMLFilter,用于將用戶輸入的字符轉(zhuǎn)化為HTML格式的字符,如將">"轉(zhuǎn)換為">",這個(gè)過(guò)濾器稍加修改就可以用在自己的項(xiàng)目上了,呵呵!
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文件復(fù)制到/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"為原來(lái)配置好的。
"defaultNumberPerPage"為每頁(yè)顯示的記錄行數(shù)。
"defaultSortColumn"排序列。
"org.appfuse.model.User"為POJO的名字。
6.將相關(guān)的樣式表的內(nèi)容合并到原有的樣式表中。
7.將valuelist.jar添加到類路徑下。
8:如果為行添加onclick事件,注意要對(duì)"&"進(jìn)行轉(zhuǎn)義-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后的內(nèi)容如下:
<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函數(shù),添加完函數(shù)后的內(nèi)容如下:
<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從數(shù)據(jù)庫(kù)取數(shù)據(jù)組建菜單的試驗(yàn)成功了......
這幾天又開始做技術(shù)實(shí)驗(yàn)了,被折磨的有點(diǎn)情緒化,不過(guò)一切還算順利,多虧朋友們的幫忙!
昨晚在家讀DisplayTag的源代碼,發(fā)現(xiàn)DisplatyTag(以下簡(jiǎn)稱DT)翻頁(yè)時(shí)候不是從緩存中讀取的數(shù)據(jù),它又重新向數(shù)據(jù)庫(kù)發(fā)出請(qǐng)求,和我調(diào)試程序時(shí)發(fā)現(xiàn)的一樣,它的動(dòng)作大致是這樣的:
向數(shù)據(jù)庫(kù)發(fā)送請(qǐng)求,一次讀取所有數(shù)據(jù)的所有字段(我強(qiáng)調(diào)"所有"字段,并不是select *,而是讀取你的語(yǔ)句中指定的全部的字段,我強(qiáng)調(diào)的是相對(duì)于lazy-load方式的部分讀取),然后根據(jù)pagesize(一頁(yè)顯示數(shù)據(jù)的行數(shù))和當(dāng)前頁(yè)號(hào)讀取數(shù)據(jù)的子集,當(dāng)翻到其他頁(yè)時(shí),又獲取所有數(shù)據(jù)的所有字段,再過(guò)濾出顯示的子集......,這樣的效率不是差,而是很差,我昨天看了一下DT的JIRA的文字,發(fā)現(xiàn)這個(gè)問(wèn)題早在2004年的5月份就有人提出了,而那個(gè)人就是ValueList的作者,而且你還會(huì)發(fā)現(xiàn)很多開發(fā)者提供的改進(jìn)方案和參考實(shí)現(xiàn),而且DT的開發(fā)組也同意添加提高分頁(yè)效率的特性,但到現(xiàn)在也還沒(méi)有實(shí)現(xiàn),有些讓人失望!據(jù)說(shuō)ValueList對(duì)這方面做的很好,但ValueList的文檔太少了,而且集成到AppFuse中還要自己改寫模板,目前有些騎虎難下,明天開始折騰ValueList,希望一切順利,菩薩保佑,呵呵
今天試了一下從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)來(lái)生成Struts Menu的菜單,結(jié)果發(fā)現(xiàn)如果一頁(yè)中放入2個(gè)Struts Menu的菜單,則會(huì)有一個(gè)有問(wèn)題,我看了一下源碼,發(fā)現(xiàn)是在一個(gè)js文件中的函數(shù)引起的,Struts Menu首先初始化菜單,然后擴(kuò)展,在擴(kuò)展的時(shí)候,把點(diǎn)擊的菜單寫到Cookies中,如果同一個(gè)頁(yè)面有2個(gè)的聲明,則第二個(gè)菜單的折疊的部分不能展開,我的一個(gè)同事折騰了將近一周,就是因?yàn)檫@個(gè)問(wèn)題。
昨晚終于在倦兔的幫助下,使用Maven編譯DT成功了,這樣我可以放心的修改DT,然后編譯、打包。但我要權(quán)衡一下和改用ValueList的工作量,目前暫不打算修改DT了。
Maven把所有下載的jar文件放在Window的Documents and Settings/你的用戶名/.mave目錄下,你可以把其他人的這個(gè)目錄下的文件都拷貝過(guò)來(lái)(注意要保持原來(lái)的目錄結(jié)構(gòu))直接用就行了。
Matt Raible關(guān)于DT、ValueList、DataGrid的簡(jiǎn)短介紹:http://raibledesigns.com/comments/rd/sunsets/there_s_a_new_sorting
兔八哥
2005-3-1 18:21
今天,同事終于把ValueList的實(shí)驗(yàn)做完了,ValueList0.1.7新添了許多新功能,但是,今天看到"王者之劍"的留言,又讀了一下源碼,發(fā)現(xiàn)ValueList也是取回?cái)?shù)據(jù)庫(kù)的全部數(shù)據(jù),只是把需要返回頁(yè)面的數(shù)據(jù)返回了,并不是我想的使用Hibernate的分頁(yè)方法進(jìn)行查詢數(shù)據(jù)庫(kù)的
今天,同事終于把ValueList的實(shí)驗(yàn)做完了,ValueList0.1.7新添了許多新功能,但是,今天看到"王者之劍"在我的Blog上的留言,有點(diǎn)不太相信ValueList也是取回所有的數(shù)據(jù)。
剛才,我抽空看了一下ValueList的Hibernate20Adapter的源碼,結(jié)果發(fā)現(xiàn)果然是把所有的數(shù)據(jù)都取回來(lái)了,但比DT稍強(qiáng)一些的是,它沒(méi)有把所有的Object都放入request中,只是把當(dāng)前頁(yè)要顯示的Object放入了,但這樣也同樣耗費(fèi)數(shù)據(jù)庫(kù)資源,同樣需要循環(huán),不同的是,耗費(fèi)的網(wǎng)絡(luò)資源少些,因?yàn)椋鼈骰氐膶?duì)象只有一頁(yè)的對(duì)象。
但結(jié)果并不是我想像的,我以為,它會(huì)自己像Hibernate那樣,根據(jù)不同的數(shù)據(jù)庫(kù)采用分頁(yè)算法,使用Criteria Query設(shè)定返回在指定范圍的數(shù)據(jù)。
實(shí)驗(yàn)結(jié)果有點(diǎn)讓我失望,但畢竟比DT要強(qiáng)一些,不知道DT的1.1是不是也是這樣解決的,如果是的話,那不用等它的正式版本了,在JIRA上已經(jīng)有參考實(shí)現(xiàn)了。
但學(xué)習(xí)ValueList還是有所收獲的,看到了另一種顯示實(shí)現(xiàn)的方式,而且ValueList的確比DT強(qiáng),界面雖然不漂亮(0.1.7已經(jīng)很有DT的意思了,通過(guò)自己定制樣式表,可以作出漂亮的外觀),但比較靈活,這個(gè)工具值得繼續(xù)關(guān)注。
使用ValueList需要注意的一點(diǎn)是,如果使用MVC框架(不知道其他的框架是否這樣),從一個(gè)Controller轉(zhuǎn)發(fā)到一個(gè)View上,如果ValueList的URL屬性為""或者"?",翻頁(yè)圖標(biāo)的URL為上一個(gè)操作的URL,這和ValueList中提到的刷新按鈕有點(diǎn)相似,我又看了一下DT的用法,也需要指定URL,對(duì)于解決重復(fù)提交的問(wèn)題,可以使用Struts的token來(lái)解決,我問(wèn)了一下倦兔,倦兔說(shuō)WebWork和Spring都有類似的解決方案,在此也感謝倦兔推薦ValueList!^_^
剛才和天天加班的段兄(SkyHero)聊了一下,感覺(jué)實(shí)在不行要自己改代碼了,比較郁悶(本來(lái)想偷懶的,呵呵。
祝段兄的加班生活早點(diǎn)結(jié)束,你的女朋友還在茫茫人海中等你去找她,不要把大好時(shí)光都浪費(fèi)在計(jì)算機(jī)上!^_^
昨天,和朋友開車去天津,在濱江路上轉(zhuǎn)了一天,我在94年取過(guò)一次,發(fā)現(xiàn)十年后再看天津已經(jīng)不是以前的那個(gè)城市了,看來(lái)是我的看法變了!不過(guò)昨天天津的氣溫有15度,很是舒服,好久沒(méi)有這樣放松了,希望每隔2個(gè)月,能出去轉(zhuǎn)轉(zhuǎn)!
兔八哥
2005-3-7 19:38
和倦兔聊了一下,他告訴我的解決方法竟然如此簡(jiǎn)單:在Tomcat的server.xml的Connector部分添加URIEncoding="GBK",我的server.xml添加后內(nèi)容如下:
<Connector URIEncoding="GBK" port="8080"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true" />
不用讀DisplayTag的源碼了,也可以安心過(guò)個(gè)年了,謝謝倦兔!!!
以下內(nèi)容是我翻譯的JDK的幫助。
URLEncoder類:
用于HTML的form中數(shù)據(jù)編碼的類。
這個(gè)類包含將字符串轉(zhuǎn)換為application/x-www-form-urlencoded MIME 格式的靜態(tài)方法.
如果想了解HTML的編碼細(xì)則,請(qǐng)參考HTML規(guī)范。
編碼規(guī)則如下:
字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不被編碼,維持原值,
空格" "被轉(zhuǎn)換為加號(hào)"+"。
所有其他的字符都被認(rèn)為是不安全的,首先都根據(jù)指定的編碼scheme被轉(zhuǎn)換為1個(gè)或者多個(gè)字節(jié)。[憑什么認(rèn)為其他的字符都是不安全的?看來(lái)這些規(guī)范的制訂者中沒(méi)有中國(guó)人呀!]
然后每個(gè)字節(jié)都被表示成"%xy"格式的由3個(gè)字符組成的字符串,xy是字節(jié)的2位16進(jìn)制的表達(dá)(xy is the two-digit hexadecimal representation of the byte),推薦的編碼scheme為UTF-8,然而,出于兼容性的考慮,如果沒(méi)有制定編碼的scheme,那么將使用當(dāng)前操作系統(tǒng)的編碼的scheme。
如:如果編碼scheme是UTF-8,
"The string ü@foo-bar"將被轉(zhuǎn)換為"The+string+%C3%BC%40foo-bar" 。
因?yàn)檩dUTF-8中字符ü被編碼成2個(gè)字節(jié)C3 (十六進(jìn)制) 和BC (十六進(jìn)制), 字符@被編碼成一個(gè)字節(jié)40 (十六進(jìn)制)。
起始于:JDK1.0
這個(gè)類共有2個(gè)重載方法:
public static String encode(String s, String enc) throws UnsupportedEncodingException。起始于:JDK1.4
和即將被廢棄的方法:public static String encode(String s)。(因?yàn)檫@個(gè)方法的編碼的字符集依賴于程序運(yùn)行的系統(tǒng)的默認(rèn)的字符集)。
第一個(gè)方法的作用是:根據(jù)指定的encode scheme 將一個(gè)字符串翻譯成application/x-www-form-urlencoded格式。
注意: W3C推薦UTF-8。
參數(shù):
s - 將要被翻譯的字符串。
enc - 編碼用的character。
返回:翻譯后的字符串。
拋出異常: UnsupportedEncodingException - 如果不支持制定的編碼
起始于:1.4
另請(qǐng)參考:URLDecoder.decode(java.lang.String, java.lang.String)
類URLDecoder的作用和URLEncoder的作用相反,方法類似,這里就不再贅述了。
如果你想知道你的字符串被編碼后的值是什么樣,你可以打開www.baidu.com,然后輸入你要編碼后的數(shù)值,然后提交,你可以在地址欄看到你被編碼后的字符串,這個(gè)方法是Jason告訴我的,呵呵!
如果想解決DisplayTag的問(wèn)題,就要修改源代碼了,下一步就是讀源代碼,頭疼ing......
兔八哥
2005-2-2下午16:30
在百度提交:The string ü@foo-bar |
請(qǐng)看:http://rabbit8.blogchina.com/blog/article_144619.859489.html |
不錯(cuò)不錯(cuò),附上實(shí)現(xiàn)代碼 |
DisplayTag的默認(rèn)的URL默認(rèn)為上一次的URL,如果上一次的URL包含中文的話,則會(huì)被進(jìn)行URLEncode,所以在翻頁(yè)的時(shí)候,又會(huì)把進(jìn)行了URLEncode后的數(shù)據(jù)再次進(jìn)行URLEncode,所以翻頁(yè)就沒(méi)有數(shù)據(jù),因?yàn)檫@部分功能被封裝在DisplayTag中,于是,我又開始郁悶了......
如漢字"專業(yè)"被編碼后為"%D7%A8%D2%B5",于是DisplayTag就將這個(gè)編碼作為連接的關(guān)鍵字,如果再次提交,則這個(gè)編碼還會(huì)被編碼,所以查詢結(jié)果一定就不對(duì)了!!!
下面的內(nèi)容是我分析這個(gè)問(wèn)題的由來(lái):
---------------------------------------------------------------------------------------------------
剛才到網(wǎng)上查找了些資料,找到HTML4.0.1的規(guī)范中關(guān)于URLEncode的部分,我把我關(guān)心的內(nèi)容翻譯了一下:
http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
其中相關(guān)內(nèi)容如下在17.13.3 Processing form data:
提交時(shí),HTML的規(guī)范處理如下:
1.標(biāo)識(shí)successful controls (概念可以在上文中查找)。
2.構(gòu)建form的數(shù)據(jù)集。
3.根據(jù)form的enctype的設(shè)置,對(duì)form的數(shù)據(jù)集進(jìn)行Encode。
4.提交已經(jīng)Encode的數(shù)據(jù)集。
HTML規(guī)范中指出Content type和Languange code是不區(qū)分大小寫的。
詳情參見:http://www.w3.org/TR/html401/types.html#type-content-type
"&" 表示"&"。關(guān)于charset的詳細(xì)內(nèi)容見:http://www.w3.org/TR/html401/charset.html#entities
有一個(gè)小發(fā)現(xiàn):Frame中的target的值的列表原來(lái)是在HTML的規(guī)范中制定的,呵呵:
下面的target的名字是規(guī)范中聲明的有特殊含義的保留字。
_blank 在一個(gè)沒(méi)有指定名字的新窗口中打開頁(yè)面。(new, unnamed window)
_self 在同一個(gè)窗口中打開。(load the document in the same frame as the element that refers to this target)
_parent 在當(dāng)前窗口的父窗口中打開,如果當(dāng)前窗口沒(méi)有父窗口,那么就等同于_self
_top 在最開始的窗口中轉(zhuǎn)載,如果當(dāng)前框架沒(méi)有parent,那就等于_self。
form的默認(rèn)的content type是:application/x-www-form-urlencoded
form提交content type的數(shù)據(jù)必須用下列規(guī)則進(jìn)行編碼(encode):
空格被封裝為"+",其他的保留字封裝后的值在 [RFC1738]中可以查到。RF1738的規(guī)范:http://www.ietf.org/rfc/rfc1738.txt。
其他的非英文字符和非數(shù)字的字符都被編碼為"%HH",
HH是將字符的ASCII的編碼轉(zhuǎn)換為16進(jìn)制后的字符。行尾是"CR LF" (如:`%0D%0A')。
控件的名字和數(shù)值之間使用"="分隔,多個(gè)控件之間用"&"分隔。
------------------------------------------------------------------------------------------------------------------
兔八哥
2005-2-2下午
它最大的價(jià)值就是為我們提供了一個(gè)Web開發(fā)的新的方式和思路,盡管這些技術(shù)在國(guó)外都已進(jìn)很流行了,但在國(guó)內(nèi)能夠?qū)?FONT size=+0>Hibernate、Struts、Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、Velocity、JUnit、JSTL、WebWork這些技術(shù)集成到一個(gè)框架中的還不多見,所以即使不使用它的全部功能,它也給我們提供了一個(gè)很好的借鑒、學(xué)習(xí)的機(jī)會(huì)。
AppFuse的作者 matt raible是當(dāng)今開源世界一個(gè)比較活躍的開發(fā)者,它是AppFuse、Struts Menu的作者,也是XDoclet、DisplayTag等一些著名開源項(xiàng)目的積極參與者,《Hibernate In Action》的作者就在感謝的名單里面提到他,XDoclet的下載版本中所帶的Hibernate標(biāo)簽部分的例子就是他寫的,他還是2004年Apache技術(shù)年會(huì)的主講人之一。(這些都是我這2個(gè)多月來(lái)搜集到的,呵呵)
通過(guò)關(guān)注AppFuse,我們可以看到目前國(guó)外的主流開發(fā)都使用了哪些技術(shù),開發(fā)方式是什么樣的,可能達(dá)到什么樣的結(jié)果,而在以前,是很少能夠看到這樣完整的例子的。
AppFuse的另一個(gè)啟示是:我們可以依靠開源軟件的功能降低開發(fā)成本,而且可以閱讀開源軟件的代碼提高所在團(tuán)隊(duì)的整體實(shí)力。
但是通過(guò)2個(gè)月的實(shí)際學(xué)習(xí)和使用,我也遇到一系列的問(wèn)題,因?yàn)?FONT size=+0>AppFuse是將其他的一些類庫(kù)或者框架集成在一起的,集成的技術(shù)眾多,而且有一些技術(shù)在國(guó)內(nèi)甚至很少有人知道,資料也比較少,所以雖然作者經(jīng)過(guò)了一些測(cè)試,但都是基于英文編碼的,而對(duì)于中文編碼來(lái)說(shuō),還潛在的存在著一些問(wèn)題,雖然不是AppFuse的問(wèn)題,但卻降低了開發(fā)速度,下面是我在開發(fā)過(guò)程中遇到過(guò)的問(wèn)題,有些解決了,有些還沒(méi)有解決:
一.Struts
1. AppFuse中默認(rèn)的MVC框架是Struts,而且使用的是LookupDispatchAction,并且使用的是按鈕(button),在XP下用IE瀏覽效果還可以,但如果在2000或者98下,就使外觀很難看,而且當(dāng)時(shí)我還遇到一個(gè)問(wèn)題:如果按鈕顯示中文,則在DisplayTag中翻頁(yè)失靈,而且報(bào)錯(cuò),后來(lái)我把BaseAction的相關(guān)方法改變了,才可以使用,因?yàn)閲?guó)內(nèi)的客戶都比較重視界面,所以后來(lái)我將那些按鈕都改成圖片了,當(dāng)然也要添加一些方法了,有點(diǎn)麻煩!
2. Struts中的標(biāo)簽如今推薦使用的只有html部分的標(biāo)簽了,其他的標(biāo)簽或者可以使用JSTL替代,或者已經(jīng)不推薦使用了,而且AppFuse中推薦使用JSTL,而JSTL和struts的標(biāo)簽的聯(lián)合使用時(shí),需要的不是標(biāo)簽<html:標(biāo)簽>,而是標(biāo)簽<html-el:標(biāo)簽>,這個(gè)問(wèn)題曾經(jīng)困擾了我整整2天。3. Struts的Validation的校驗(yàn)規(guī)則并不完善,比如如果使用客戶端的javascript校驗(yàn),則在郵箱中輸入漢字根本校驗(yàn)不出來(lái),到了服務(wù)器端報(bào)錯(cuò)。
4. 最嚴(yán)重的問(wèn)題是AppFuse生成的Struts的validation.xml文件中有許多多余的".",如果你去掉了,常常在執(zhí)行ant的deploy任務(wù)時(shí)又恢復(fù)原樣。這樣是提交表單的時(shí)候經(jīng)常會(huì)報(bào)javascript的腳本錯(cuò)誤或者缺少對(duì)象或者缺少value,所以我會(huì)手工的修改這個(gè)文件,然后把修改后的文件備份,當(dāng)重新生成有錯(cuò)誤的文件時(shí),我會(huì)用備份的沒(méi)有錯(cuò)誤的文件去覆蓋。
5. Struts的validatioin對(duì)于使用同一個(gè)FormBean的Action的校驗(yàn)方式比較復(fù)雜。(待解決)。
二.Hibernate
1. Hibernate是現(xiàn)在受到越來(lái)越多的人推崇的一個(gè)ORM工具(框架、類庫(kù)),它將我們從繁瑣的使用JDBC的開發(fā)過(guò)程中解放出來(lái),但同時(shí)也帶來(lái)了新的問(wèn)題,如學(xué)習(xí)曲線,執(zhí)行效率,數(shù)據(jù)庫(kù)設(shè)計(jì)優(yōu)化,還有最重要的靈活性。Hibernate不是一個(gè)很容易上手的東西,要完全駕馭它還需要讀很多資料,但好的資料卻很少。
2. 使用Xdoclet可以很方便的生成Hibernate中的持久類的配置文件(*.hbm.xml),但對(duì)一些特殊的映射卻無(wú)能為力,如使用序列的id生成規(guī)則,序列的名字沒(méi)有地方寫,所以也只好先利用它生成主要的內(nèi)容,然后手工修改。
3. 同樣還是id的生成策略問(wèn)題,如果使用序列、hilo等需要一些數(shù)據(jù)庫(kù)機(jī)制支持的策略時(shí),schemaExport并不能自動(dòng)生成序列或者保存當(dāng)前id的表,這項(xiàng)工作仍然要手工解決。
4. Hibernate中提供了幾種關(guān)聯(lián),一對(duì)一、一對(duì)多、多對(duì)多,但對(duì)于怎樣調(diào)整效率卻沒(méi)有一個(gè)很明確的提示,還要根據(jù)情況判定,這就帶來(lái)和一些彈性的設(shè)計(jì)。
5. Hibernate中可以選擇的操作數(shù)據(jù)庫(kù)的方式有3種,其中HQL功能最強(qiáng)大,但有些功能使用標(biāo)準(zhǔn)查詢可能會(huì)更方便,但會(huì)有一些限制,所以雖然它很靈活,但易用性不如JDBC好。
三.Spring
在AppFuse的過(guò)程中,Spring完全隱藏在幕后,除了一些配置外,幾乎感覺(jué)不到它的存在,所以我在使用它的過(guò)程中并沒(méi)有遇到什么麻煩,這里只是簡(jiǎn)單的介紹一下它在AppFuse中起到的作用。
1. Spring在AppFuse中起到的主要作用是對(duì)Hibernate的Session和事務(wù)的管理,利用Spring封裝的Hibernate模板類,我們大大地減少了實(shí)現(xiàn)DAO的代碼行數(shù)。
2. Spring還起到了連接映射文件和類之間的關(guān)聯(lián),及接口和實(shí)現(xiàn)類之間的關(guān)聯(lián),這些都依賴于Spring的IoC的機(jī)制的實(shí)現(xiàn)。
3. 對(duì)于字符進(jìn)行編碼和解碼部分用到了Spring自帶的Filter,只需要在配置文件中配置就好了。
四.SiteMesh
SiteMesh是一個(gè)基于Decorator模式的技術(shù),它可以修飾返回的網(wǎng)頁(yè)文件,它的工作方式受到越來(lái)越多的人的推崇,這點(diǎn)從Manning出版的一些技術(shù)書籍中可以看出來(lái)。
我在使用SiteMesh的過(guò)程中并不順利,我參考了《Java Open Source Programming》,這本書中說(shuō)SiteMesh在默認(rèn)的情況下不對(duì)下載文件進(jìn)行裝飾,但我在下載文件時(shí)發(fā)現(xiàn),我的文件內(nèi)容被丟棄了,取而代之的是SiteMesh的模板的內(nèi)容,后來(lái)我通過(guò)修改SiteMesh的配置文件解決了這個(gè)問(wèn)題,但感覺(jué)還有一些不太清楚的地方需要學(xué)習(xí)。
五.DisplayTag
DisplayTag是一個(gè)優(yōu)秀的顯示內(nèi)容的標(biāo)簽,從SourceForge的訪問(wèn)量來(lái)看,它是很活躍的項(xiàng)目,僅次于Ant、Hibernate、Xdoclet等幾個(gè)著名的項(xiàng)目,我總結(jié),它的主要功能有4項(xiàng):顯示、分頁(yè)、排序、將顯示的數(shù)據(jù)寫入指定類型的文件中,然后下載。
1. 據(jù)我使用的情況看,我只使用了分頁(yè)和顯示的功能,因?yàn)楫?dāng)時(shí)我沒(méi)有很好的解決中文編碼的問(wèn)題,所以排序會(huì)有問(wèn)題,直到昨天,我在朋友的幫助下解決了這個(gè)問(wèn)題,至此我可以放心使用的功能又增加了排序(我昨天簡(jiǎn)單的測(cè)試了一下是可以的)。
2. 但對(duì)于將顯示的內(nèi)容生成到一個(gè)指定格式的文件中的功能卻有著很多缺陷,如:
(1) 生成的文件中只有顯示的數(shù)據(jù),那些沒(méi)有顯示在界面上的的數(shù)據(jù),則不會(huì)被寫到文件中。
(2) 如果修改了DisplayTag的顯示的內(nèi)容,比如添加一列,在這列中的內(nèi)容不是字符,而是HTML的標(biāo)簽,則生成的文件只有這些HTML標(biāo)簽,而沒(méi)有數(shù)據(jù)。
(3) 即使DisplayTag中沒(méi)有我們定制的HTML腳本,生成的文件偶爾也有問(wèn)題,比如:它會(huì)把"007"生成為"7",把字符串自動(dòng)的轉(zhuǎn)換為整型值。有時(shí)候還生成空白內(nèi)容的文件。
(4) DisplayTag生成的Excel文件兼容性不好,有時(shí)在Excel2003中不能正常打開,或者在XP下打開報(bào)錯(cuò)。
后來(lái),我看了作者寫的《Spring Live》,書中說(shuō)如果想實(shí)現(xiàn)穩(wěn)定的Excel,推薦使用POI,于是我使用POI生成Excel,穩(wěn)定性和兼容性都不錯(cuò)。
六.DBUnit
DBUnit是一個(gè)可以被Ant集成的向數(shù)據(jù)庫(kù)中添加數(shù)據(jù)和備份數(shù)據(jù)的一個(gè)類庫(kù),配置很方便,因?yàn)?FONT size=+0>AppFuse
已經(jīng)集成好了,所以使用也很容易。但是如果你使用EditPlus之類的工具手工修改了AppFuse生成的內(nèi)容,則執(zhí)行Ant的setup、setup-db或者deploy的任務(wù)時(shí),常常報(bào)錯(cuò),說(shuō)無(wú)效的格式,這是因?yàn)檫@個(gè)被手工修改的文件再次被AppFuse執(zhí)行后,它的第一行的文件聲明的前幾個(gè)字母是無(wú)效的,是因?yàn)楸镜氐淖址幋a的原因而引起了亂碼,如果把這幾個(gè)無(wú)效的字母去掉,問(wèn)題就解決了。
七.Struts Menu
Struts Menu也是AppFuse的作者開發(fā)的一個(gè)開源軟件,它可以根據(jù)配置文件讀取當(dāng)前用戶可以使用的功能菜單,這個(gè)功能是我一直以來(lái)都想要的,我也找到了一些代碼,但實(shí)現(xiàn)的都不如這個(gè)完善,沒(méi)什么好說(shuō)的,使用簡(jiǎn)單,配置容易,很好的解決了我的問(wèn)題。
問(wèn)題是我只使用了AppFuse提供的2個(gè)角色,對(duì)于多個(gè)角色的實(shí)驗(yàn)我還沒(méi)有做。
八.XDoclet
在AppFuse中,使用Xdoclet生成了幾乎一切的配置文件:Struts-config.xml、web.xml、validation.xml、*.hbm.xml等文件,如果使用AppGen的話,還會(huì)生成更多的文件,這一切都是使用Xdoclet實(shí)現(xiàn)的。
問(wèn)題是我在Struts部分提到的,生成的Validation.xml文件中會(huì)多生成一個(gè)".",另外在生成資源文件時(shí)也會(huì)多生成一個(gè)".",目前我沒(méi)有很好的閱讀這段代碼,不知道是不是Xdoclet的問(wèn)題。
九.Ant
Ant并沒(méi)有什么問(wèn)題,但在執(zhí)行作者寫的Ant任務(wù)的時(shí)候,有一些任務(wù)不能正常執(zhí)行,比如,運(yùn)行模擬對(duì)象測(cè)試的任務(wù),作者也在1.7版本的修復(fù)列表中提到以前版本有些ant任務(wù)不能執(zhí)行,在1.7中修改了一些ant任務(wù),使他們能夠正常的執(zhí)行了。
實(shí)際上,我們?nèi)绻褂?FONT size=+0>AppGen
進(jìn)行開發(fā)的話,使用的任務(wù)一般不超過(guò)8個(gè)。十.JSTL
JSTL是個(gè)好東西,我常用的有和部分的標(biāo)簽,但是如果使用JSTL進(jìn)行邏輯判斷,我并沒(méi)有感覺(jué)比使用JSP的代碼塊優(yōu)雅多少。另外,熟悉JSTL也需要一段時(shí)間,我就經(jīng)歷了面對(duì)著JSP頁(yè)面不知道該怎么寫JSTL語(yǔ)法的困境。當(dāng)然,AppFuse中使用的基本都是JSTL,包括向DisplayTag傳遞顯示的數(shù)據(jù),使用的都是JSTL語(yǔ)法,這方面的資料挺多,我參考的是電子工業(yè)出版社出的《JSP2.0技術(shù)》,說(shuō)的很詳細(xì)。
十一.Tomcat
你也許會(huì)說(shuō):"Tomcat就不用說(shuō)了吧?",是的,Tomcat一般都會(huì)使用,但是―――――――――――――Tomcat5和Tomcat4.X對(duì)于中文編碼使用了不同的機(jī)制,這個(gè)問(wèn)題困擾了我好久,我解決了頁(yè)面上寫入漢字顯示亂碼的問(wèn)題,我也曾經(jīng)以為DisplayTag對(duì)漢字不能排序,也不能正常分頁(yè)是因?yàn)?FONT size=+0>DisplayTag的開發(fā)者都是老外,是因?yàn)樗麄儧](méi)有考慮中文的關(guān)系的原因。
直到昨天,我才知道這一切都是因?yàn)?FONT size=+0>Tomcat5對(duì)漢字編碼的實(shí)現(xiàn)的方式和Tomcat4不一樣的原因,如果感興趣,可以看看這個(gè)帖子:http://www.javaworld.com.tw/jute/post/view?bid=9&id=44042&sty=1&tpg=1&age=0
再次感謝倦兔!:D
十二.JavaScript
JavaScript簡(jiǎn)單易學(xué),但想運(yùn)用自如就不太容易了。AppFuse中嵌入了幾個(gè)js文件,里面包含了許多函數(shù),值得我們好好的研究一下,比如,如果有一個(gè)必填字段沒(méi)有填寫,AppFuse會(huì)自動(dòng)的聚焦在那個(gè)input上,類似的小技巧有很多,你可以自己去翻看。
但AppFuse自帶的JavaScript腳本有一個(gè)Bug,就是當(dāng)DisplatyTag中沒(méi)有可以顯示的數(shù)據(jù)時(shí),你用鼠標(biāo)單擊,它會(huì)報(bào)JavaScript錯(cuò)誤,你仔細(xì)研究一下function highlightTableRows(tableId) 就知道了:我的解決辦法是在location.href = link.getAttribute("href");前面添加一行判斷:if (link != null)。十三.資源文件國(guó)際化
對(duì)于Struts和DisplayTag都涉及到資源文件國(guó)際化AppFuse1.6.1很好的解決了Struts資源映射文件國(guó)際化的問(wèn)題,你只需要在對(duì)應(yīng)本國(guó)語(yǔ)言的資源文件中寫入漢字,Ant中有一項(xiàng)執(zhí)行native2ascii的任務(wù),AppFuse自動(dòng)的為你進(jìn)行了資源文件的編碼轉(zhuǎn)換,而對(duì)于DisplayTag的資源文件問(wèn)題,還要自己執(zhí)行native2ascii命令,為了避免每次都輸入一串命令,我用Delphi寫了個(gè)小工具,可視化的選擇資源文件,點(diǎn)擊按鈕自動(dòng)執(zhí)行該命令,底層依賴于JDK。
經(jīng)過(guò)2個(gè)多月的學(xué)習(xí),我感覺(jué)這個(gè)框架非常不錯(cuò),它為我以后的項(xiàng)目開發(fā)指出了一個(gè)新的方向,但如果想很熟練的使用這個(gè)框架進(jìn)行開發(fā),至少要對(duì)以下幾種技術(shù)比較熟練:Struts(或者WebWork、Spring及其他的已經(jīng)整合進(jìn)來(lái)的MVC框架)、Hibernate(或者ibatis)、JSTL,當(dāng)然其他的技術(shù)至少也要知道一點(diǎn),否則遇到問(wèn)題都不知道出在哪里。
目前我還沒(méi)有解決的問(wèn)題有:
1. 如何在翻頁(yè)的時(shí)候才讀取下面的數(shù)據(jù)?
2. 怎樣對(duì)使用同一個(gè)FormBean的多個(gè)Form進(jìn)行客戶端校驗(yàn)?
3. 怎樣優(yōu)化Hibernate的效率?《Hibernate In Action》中提供了多種策略,有些時(shí)候應(yīng)該使用lazy,有些時(shí)候應(yīng)該使用outer-join。
4. 在什么時(shí)機(jī)生成導(dǎo)出文件?目前我是在查詢的Action中同時(shí)生成了導(dǎo)出文件,否則,到了下一頁(yè),我就不知道查詢條件了,當(dāng)然,如果把拼裝后的HQL存儲(chǔ)在Session或者Hidden中也可以解決這個(gè)問(wèn)題,但是這樣就破壞了DAO的封裝,要把DAO封裝后的HQL發(fā)送給Action,然后發(fā)送的到Web界面層,所以目前我還在猶豫生成導(dǎo)出文件的時(shí)機(jī)選擇在哪里?
5. 什么時(shí)候應(yīng)該自己獲取數(shù)據(jù)庫(kù)連接,執(zhí)行native SQL?具體需要注意些什么?
6. SiteMesh的模板優(yōu)化?
7. DisplayTag的底層實(shí)現(xiàn)?
每個(gè)問(wèn)題都比較棘手,要一個(gè)一個(gè)解決!
這個(gè)框架的優(yōu)點(diǎn)是:如果熟悉了開發(fā)流程,可以大幅度的提高開發(fā)速度,如果業(yè)務(wù)不是很復(fù)雜,使用AppGen可以生成60%左右的代碼,而且程序可維護(hù)性好,因?yàn)樽髡呤褂昧硕鄠€(gè)設(shè)計(jì)模式對(duì)各個(gè)層面進(jìn)行了封裝,所以不同的模塊代碼風(fēng)格出奇的一致,有利于開發(fā)人員快速上手,有利于接收其他開發(fā)人員遺留的代碼。
兔八哥
native2ascii在Ant中有任務(wù),可以寫
<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
官方網(wǎng)站:
http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse
http://appfuse.dev.java.net/
http://jroller.com/page/raible