期待更好更穩定的開源FrameWork的出現,讓我們一起努力吧! |
|
|||
日歷
統計
導航常用鏈接留言簿(1)隨筆分類隨筆檔案(42)
文章檔案(37)
相冊搜索積分與排名
最新隨筆
最新評論
閱讀排行榜
評論排行榜
|
Jakarta Commons項目組介紹 method="post" enctype="multipart/form-data"> 輸入你的名字: 圖形: 文件: value="Submit your files"/> 接下來創建JSP頁面。 // … // ① 檢查輸入請求是否為multipart的表單數據。 boolean isMultipart = FileUpload. isMultipartContent(request); // … // ② 為該請求創建一個句柄,通過它來解析請求。執行 // 解析后,所有的表單項目都保存在一個List中。 DiskFileUpload upload = new DiskFileUpload(); // 通過句柄解析請求,解析得到的項目保存在一個List 中 List items = upload.parseRequest(request); // … // ③ 通過循環依次獲得List里面的文件項目。要區分表示 // 文件的項目和普通的表單輸入項目,使用isFormField() // 方法。根據處理請求的要求,我們可以保存上載的文 // 件,或者一個字節一個字節地處理文件內容,或者打 // 開文件的輸入流。 Iterator itr = items.iterator(); while(itr.hasNext()) { FileItem item = (FileItem) itr.next(); // 檢查當前的項目是普通的表單元素,還是一個上載的文件 if(item.isFormField()) { // 獲得表單域的名字 String fieldName = item.getFieldName(); // 如果表單域的名字是name… if(fieldName.equals("name")) request.setAttribute("msg", "Thank You: " + item.getString()); } else { // 該項目是一個上載的文件,把它保存到磁盤。 // 注意item.getName() // 會返回上載文件在客戶端的完整路徑名稱,這似乎是一個BUG。 // 為解決這個問題,這里使用了fullFile.getName()。 File fullFile = new File(item.getName()); File savedFile = new File (getServletContext().getRealPath("/"), fullFile.getName()); item.write(savedFile); } } 我們可以通過上載句柄的upload.setSizeMax 來限制上載文件的大小。當上載文件的大小超過允許的 值時,程序將遇到異常。在上面的例子中,文件大小的限制值是-1,表示允許上載任意大小的文件。 還有其他一些略有變化的使用形式,正如前面所指出的,我們可以在上載的文件上打開一個輸入流, 或者讓它們駐留在內存中直至空間占用達到一定的限制值,或者在判斷文件類型的基礎上,以String 或 Byte 數組的形式獲取其內容,或者直接刪除文件。這一切都只要使用FileItem 類提供的方法就可以方便 地做到(DefaultFileItem 是FileItem的一個實現)。 3.2 HttpClient ■ 概況:這個API 擴展了java.net包,提供了模擬瀏覽器的功能。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你要構造Web 瀏覽器的功能;當你的應用需要一種高效的辦法進行HTTP/HTTPS通信時。 ■ 示例應用:HttpClientDemo.java。要求CLASSPATH中有commons-httpclient.jar, common-logging.jar。要求使用JDK 1.4 或更高版本。 ■ 說明: HttpClient 擴展和增強了標準java.net 包,是一個內容廣泛的代碼庫,功能極其豐富,能夠構造出 各種使用HTTP 協議的分布式應用,或者也可以嵌入到現有應用,為應用增加訪問HTTP 協議的能力。 在Commons 穩定版中,HttpClient 的文檔似乎要比其他包更完善一些,而且還帶有幾個實例。下面我們 通過 一個簡單的例子來了解如何提取一個Web 頁面,HttpClient 文檔中也有一個類似的例子,我們將擴充那 個例子使其支持SSL。注意本例需要JDK 1.4 支持,因為它要用到Java Secure Socket Connection庫, 而這個庫只有JDK 1.4 及更高的版本才提供。 ① 首先確定一個可以通過HTTPS 下載的頁面,本例使用的是https://www.paypal.com/。同時確保 %JAVA_HOME%/jre/lib/security/java.security文件包含了下面這行代碼: security.provider.2=com.sun.net.ssl.internal.ssl.Provider。 除了這些設置之外,HTTPS連接的處理方式沒有其他特別的地方--至少對于本例來說如此。不過,如 果遠程網站使用的根證書不被你使用的Java 認可,則首先必須導入它的證書。 ② 創建一個HttpClient的實例。HttpClient 類可以看成是應用的主驅動程序,所有針對網絡的功 能都依賴于它。HttpClient 類需要一個Connection Manager來管理連接。 HttpConnectionManager允許 我們創建自己的連接管理器,或者,我們也可以直接使用內建的SimpleHttpConnectionManager或 MultiThreadedHttpConnectionManager類。如果在創建HttpClient 時沒有指定連接管理器,HttpClient 默認使用SimpleHttpConnectionManager。 // 創建一個HttpClient 的實例 HttpClient client = new HttpClient(); ③ 創建一個HttpMethod的實例,即確定與遠程服務器的通信要采用哪種傳輸方式,HTTP 允許采用 的傳輸方式包括:GET,POST,PUT,DELETE,HEAD,OPTIONS,以及TRACE。這些傳輸方式分別作為一個 獨立的類實現,但所有這些類都實現HttpMethod接口。在本例中,我們使用的是GetMethod,創建GetMeth od 實例時在參數中指定我們想要GET 的URL。 // 創建一個HttpMethod 的實例 HttpMethod method = new GetMethod(url); ④ 執行HttpMethod 定義的提取操作。執行完畢后,executeMethod方法將返回遠程服務器報告的狀態 代碼。注意executeMethod屬于HttpClient,而不是HttpMethod。 // 執行HttpMethod定義的提取操作 statusCode = client.executeMethod(method); ⑤ 讀取服務器返回的應答。如果前面的連接操作失敗,程序將遇到HttpException或IOException, 其中IOException 一般意味著網絡出錯,繼續嘗試也不太可能獲得成功。服務器返回的應答可以按照多種 方式讀取,例如作為一個字節數組,作為一個輸入流,或者作為一個String。獲得服務器返回的應答后, 我們就可以按照自己的需要任意處置它了。 byte[] responseBody = method.getResponseBody(); ⑥ 最后要做的就是釋放連接。 method.releaseConnection(); 以上只是非常簡單地介紹了一下HttpClient 庫,HttpClient 實際的功能要比本文介紹的豐富得多, 不僅健壯而且高效,請參閱API 文檔了解詳情。 3.3 Net ■ 概況:一個用于操作Internet基礎協議的底層API。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你想要訪問各種Internet底層協議之時(Finger,Whois,TFTP,Telnet,POP3,FTP,NNTP ,以及SMTP)。 ■ 示例應用:NetDemo.java。要求CLASSPATH中包含commons-net-1.0.0.jar。 ■ 說明: Net 包是一個強大、專業的類庫,類庫里的類最初屬于一個叫做NetComponents 的商業產品。 Net 包不僅支持對各種低層次協議的訪問,而且還提供了一個高層的抽象。大多數情況下,Net包提 供的抽象已能滿足一般需要,它使得開發者不再需要直接面對各種協議的Socket 級的低層命令。使用高 層抽象并不減少任何功能,Net API 在這方面做得很出色,既提供了足夠的功能,又不至于在特色方面作 過多的妥協。 SocketClient 是支持所有協議的基礎類,它是一個抽象類,聚合了各種協議都需要的公用功能。各種 不同協議的使用過程其實很相似,首先利用connect方法建立一個指向遠程服務器的連接,執行必要的 操作,最后終止與服務器的連接。下面通過實例介紹具體的使用步驟。 // … // ① 創建一個客戶端。我們將用NNTPClient // 從新聞服務器下載新聞組清單。 client = new NNTPClient(); // … // ② 利用前面創建的客戶端連接到新聞服務器。 // 這里選用的是一個新聞組較少的服務器。 client.connect("aurelia.deine.net"); // … // ③ 提取新聞組清單。下面的命令將返回一個 // NewsGroupInfo 對象的數組。如果指定的服 // 務器上不包含新聞組,返回的數組將是空的, // 如果遇到了錯誤,則返回值是null。 list = client.listNewsgroups(); //... // ④ 最后終止與服務器的連接。 if (client.isConnected()) client.disconnect(); 必須說明的是,listNewsgroups命令可能需要較長的時間才能返回,一方面是因為網絡速度的影響, 另外也可能是由于新聞組清單往往是很龐大的。NewsGroupInfo對象包含有關新聞組的詳細信息,并提供 了一些操作新聞組的命令,比如提取文章總數、最后發布的文章、發布文章的權限,等等。 其他客戶端,例如FingerClient、POP3Client、TelnetClient 等,用法也差不多。 結束語:有關Web相關類和其他類的介紹就到此結束。在下一篇文章中,我們將探討XML類和包裝類, 最后一篇文章則介紹工具類。 希望讀者有興趣試試本文提供的程序實例。很多時候Jakarta Commons 給人以混亂的感覺,希望本文 使你加深了對Jakarta Commons 了解,或者至少引起了你對Commons 子項目以及它提供的各種實用API 和 庫的興趣。 第二部分XML 類和包裝類 上一篇文章中,我們將Jakarta Commons的組件分成了五類,并介紹了其中的Web類和其他類,本文接著 介紹XML 類和包裝類,接下來的最后一篇文章將介紹工具類。注意Commons本身并不進行這種分類,這里 進行分類純粹是為組織方便起見。 一、包裝類 這一類包含Codec 和Modeler 兩個組件。 1.1 Codec ■ 概況:提供常用的編碼器和解碼器。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你需要Base64 和Hex編碼功能的標準實現之時。 ■ 示例應用:CodecDemo.java。要求CLASSPATH必須包含commons-codec-1.1.jar。 ■ 說明: Codec 里面的類分成兩個包,其中一個包實現的是常用的Base64 和Hex 編碼機制,另一個包是語言、 語音方面的編碼。兩個包的用法相似,鑒于語言、語音的編碼并不是很常用,所以下面主要介紹第一個包 。 Base64編碼主要用于Email 傳輸。定義MIME 文檔傳輸的RFC 規定了Base 64 編碼,從而使得任何二進制 數據都可以轉換成可打印的ASCII字符集安全地傳輸。例如,假設要通過Email 傳輸一個圖形文件, Email 客戶端軟件就會利用Base64 編碼把圖形文件的二進制數據轉換成ASCII 碼。在Base64編碼中, 每三個8 位的字節被編碼成一個4 個字符的組,每個字符包含原來24 位中的6 位,編碼后的字符串大小 是 原來的1.3倍,文件的末尾追加"="符號。除了MIME文檔之外,Base64 編碼技術還用于BASIC認證機制 中HTTP 認證頭的"用戶:密碼"字符串。 Base64類的使用相當簡單,最主要的兩個靜態方法是:Base64.encodeBase64(byte[] byteArray), 用于對字節數組中指定的內容執行Base64 編碼;Base64.decodeBase64(byte[] byteArray),用于對字節 數組中指定的內容執行Base64解碼。另外,Base64還有一個靜態方法 Base64.isArrayByteBase64(byte[] byteArray),用于檢測指定的字節數組是否可通過Base64 測試(即是否包含了經過Base64編碼的數據, 如前所述,Base64 編碼的結果只包含可打印的ASCII字符)。 byte[] encodedBytes=Base64.encodeBase64(testString.getBytes()); String decodedString=new String(Base64.decodeBase64(encodedBytes)); System.err.println("\'^\'是一個合法的Base64 字符嗎?" + Base64.isArrayByteBase64(invalidBytes)); Hex 編碼/解碼就是執行字節數據和等價的十六進制表示形式之間的轉換。Hex 編碼的編碼、解碼過程 和Base64 相似,此處不再贅述。 1.2 Modeler ■ 概況:根據JMX(Java Management Extensions)規范的定義,支持對Model MBean(Managed Bean) 的配置和實例化。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你想要創建和管理Model MBean,以便利用標準的管理API來管理應用之時。 ■ 示例應用:ModelerDemo.java,DemoManagedBean.java和mbeans-descriptors.xml。要求 CLASSPATH 中包含commons-modeler-1.0.jar、commons-logging.jar、 commons-digester.jar、 commons-collections.jar、commons-beanutils.jar,以及Sun的JMX參考實現jmxri.jar。 ■ 說明: 下面的說明要求讀者對JMX 有一定的了解。 Managed Bean 簡稱MBean,是一種關聯到應用程序中被管理組件的Bean,是一種對資源抽象。Model MBean 是一種特殊的MBean,具有高度動態和可配置的特點,但Model MBean 的這種能力是有代價的, 程序員需要設置大量的元信息來告訴JMX如何創建Model MBean,這些元信息包括組件的屬性、操作和其它 信息。Modeler 的目的就是降低程序員實現Model MBean 的工作量,它提供的一組函數為處理元數據信息 帶來了方便。另外,Modeler還提供了注冊工具和一個基本的Model MBean。 Modeler 允許以XML文件的形式定義元數據信息,該XML文件應當遵從隨同Modeler 提供的DTD 定義。 元數據信息用來在運行時創建注冊信息,注冊信息是所有Model MBean 的中心知識庫,實際上相當于一個 創建這類Bean 的工廠。 下面我們首先為一個Managed Bean(DemoManagedBean)創建這個XML文件。DemoManagedBean有一 個name 屬性,可讀寫。 "-//Apache Software Foundation //DTD Model MBeans Configuration File" "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd"> type="ManagedBean"> type="java.lang.String" /> 操作(不過本例沒有顯示),這就是所謂的元數據信息。如果你打算擴展隨同Modeler 提供的標準MBean (稱為BaseModelMBean),可以在mbean元素中以屬性的形式指定Model MBean的類名稱:。在前面的 例子中,標準的Model MBean只是簡單地把所有調用直接傳遞給ManagedBean 類。 接下來,我們要注冊上述信息。注意通過描述文件裝入注冊信息之后,我們通過一個靜態方法提取格 式化的注冊信息: // 創建一個Registry Registry registry = null; try { URL url = ModelerDemo.class.getResource ("mbeans-descriptors.xml"); InputStream stream = url.openStream(); Registry.loadRegistry(stream); stream.close(); registry = Registry.getRegistry(); } catch (Throwable t) { t.printStackTrace(System.out); System.exit(1); } 創建好Registry之后,我們要創建一個Model MBean,并將它注冊到默認的管理服務器。這樣,任何 JMX 客戶程序都可以通過Model MBean 調用Managed Bean 的功能了。 // 獲得一個Managed Bean 實例的句柄 DemoManagedBean mBean = new DemoManagedBean(); // 創建一個Model MBean,并將它注冊到MBean服務器 MBeanServer mServer = registry.getServer(); ManagedBean managed = registry.findManagedBean("ManagedBean"); try { ModelMBean modelMBean = managed.createMBean(mBean); String domain = mServer.getDefaultDomain(); ObjectName oName = new ObjectName(domain + ":type=ManagedBean"); mServer.registerMBean(modelMBean, oName); } catch(Exception e) { System.err.println(e); System.exit(0); } try { ObjectName name = new ObjectName(mServer.getDefaultDomain() + ":type=ManagedBean"); ModelMBeanInfo info = (ModelMBeanInfo) mServer. getMBeanInfo(name); System.err.println(" className="+info.getClassName()); System.err.println(" description="+info.getDescription()); System.err.println(" mbeanDescriptor="+info.getMBeanDescriptor()); System.err.println("==== 測試===="); System.err.println("Name 的原始值: " + mServer.getAttribute(name, "name")); mServer.setAttribute(name, new Attribute("name", "Vikram")); System.err.println("Name 的新值: " + mServer.getAttribute(name, "name")); } catch(Exception e) { System.err.println(e); System.exit(0); } 雖然這個例子比較簡單,但它仍舊清楚地說明了使用Modeler帶來的方便,不妨將它與不使用Modeler 的情況下創建一個類似的Model MBean相比較。通過XML文件來描述ModelMBeanInfo不僅靈活方便,而 且也很容易擴展,比手工編寫這類信息改進不少。 二、XML類 XML 類包含了與Java、XML技術相關的類,包括:Betwixt,Digester,Jelly,和JXPath。 2.1 Betwixt ■ 概況:實現XML 和JavaBean 的映射。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你想要以靈活的方式實現XML和Bean 的映射,需要一個數據綁定框架之時。 ■示例應用:BetwixtDemo.java,Mortgage.java,mortgage.xml。要求CLASSPATH 中必須包含 commons-betwixt-1.0-alpha-1.jar、commons-logging.jar、commons-beanutils.jar、 commons-collections.jar、以及commons-digester.jar。 ■ 說明: 如果你以前曾經用Castor綁定數據,一定會欣賞Betwixt的靈活性。Castor 適合在一個預定義模式 (Schema)的基礎上執行Bean和XML 之間的轉換;但如果你只想執行數據和XML之間的轉換,最好的選 擇就是Betwixt。Betwixt 的特點就是靈活,能夠方便地將數據輸出成為人類可閱讀的XML。 Betwixt的用法相當簡單。如果要把Bean 轉換成XML,首先創建一個BeanWriter 的實例,設置其屬性, 然后輸出;如果要把XML 轉換成Bean,首先創建一個BeanReader的實例,設置其屬性,然后用Digester 執行轉換。 將Bean轉換成XML: // 用Betwixt 將Bean轉換成XML 必須有BeanWriter的實例。 // 由于BeanWriter的構造函數要求有一個寫入器對象, // 所以我們從創建一個StringWriter開始 StringWriter outputWriter = new StringWriter(); // 注意輸出結果并不是格式良好的,所以需要在開始位置 // 寫入下面的內容: outputWriter.write(""); // 創建一個BeanWriter BeanWriter writer = new BeanWriter(outputWriter); // 我們可以設置該寫入器的各種屬性。 // 下面的第一行禁止寫入ID, // 第二行允許格式化輸出 writer.setWriteIDs(false); writer.enablePrettyPrint(); // 創建一個Bean 并將其輸出 Mortgage mortgage = new Mortgage(6.5f, 25); // 將輸出結果寫入輸出設備 try { writer.write("mortgage", mortgage); System.err.println(outputWriter.toString()); } catch(Exception e) { System.err.println(e); } 將XML 轉換成Bean: // 用Betwixt 來讀取XML 數據并以此為基礎創建 // Bean,必須用到BeanReader 類。注意BeanReader 類擴展了 // Digester包的Digester 類。 BeanReader reader = new BeanReader(); // 注冊類 try { reader.registerBeanClass(Mortgage.class); // 并解析它… Mortgage mortgageConverted = (Mortgage)reader.parse(new File("mortgage.xml")); // 檢查轉換得到的mortgage 是否包含文件中的值 System.err.println("Rate: " + mortgageConverted.getRate() + ", Years: " + mortgageConverted.getYears()); } catch(Exception ee) { ee.printStackTrace(); } 注意,通過BeanReader 注冊類時,如果頂層元素的名稱和類的名稱不同,必須用另一個方法注冊并 指定準確的路徑,如reader.registerBeanClass("toplevelelementname", Mortgage.class)。 2.2 Digester ■ 概況:提供友好的、事件驅動的高級XML 文檔處理API。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你想要處理XML 文檔,而且希望能夠根據XML 文檔中特定的模式所觸發的一組規則 來執行某些操作時。 ■ 示例應用:DigesterDemo.java、Employee.java、Company.java、rules.xml以及company.xml。 要求CLASSPATH 中必須包含commons-digester.jar、commons-logging.jar、 commons-beanutils.jar 以及commons-collections.jar。 ■ 說明: Digester 在解析配置文件的時候最為有用。實際上,Digester最初就是為讀取Struts 配置文件而開 發的,后來才移到Commons 包。 Digester是一個強大的模式匹配工具,允許開發者在一個比SAX 或DOM API更高的層次上處理XML 文檔,當找到特定的模式(或找不到模式)時能夠觸發一組規則。使用Digester 的基本思路是:首先創 建一個Digester 的實例,然后用它注冊一系列模式和規則,最后將XML文檔傳遞給它。此后,Digester 就會分析XML 文檔,按照注冊次序來觸發規則。如果XML文檔中的某個元素匹配一條以上的規則,所有 的規則會按照注冊次序被依次觸發。 Digester本身帶有12條預定義的規則。當XML文檔中找到一個特定的模式時,想要調用某個方法嗎? 很簡單,使用預定義的CallMethodRule!另外,你不一定要使用預定的規則,Digester 允許用戶通過擴 展Rule 類定義自己的規則。 在指定模式時,元素必須用絕對名稱給出。例如,根元素直接用名稱指定,下一層元素則通過"/"符 號引出。例如,假設company是根元素,company/employee 就是匹配其中一個子元素的模式。 Digester允許使用通配符,例如*/employee 將匹配XML 文檔內出現的所有employee元素。 找到匹配的模式時,關聯到該匹配模式的規則內有四個回調方法會被調用,它們是:begin,end,body, 和finish。這些方法被調用的時刻正如其名字所示,例如調用begin 和end 的時刻分別是遇到元素的開 始標記和結束標記之時,body是在遇到了匹配模式之內的文本時被調用,finish 則是在全部對匹配模式 的處理工作結束后被調用。 最后,模式可以在一個外部的規則XML 文檔內指定(利用digester-rules.dtd),或者在代碼之內 指定,下面要使用的是第一種辦法,因為這種辦法比較常用。 使用Digester 之前要創建兩個XML文檔。第一個就是數據或配置文件,也就是我們準備對其應用規 則的文件。下面是一個例子(company.xml) BR>可以看到,所有驗證規則都在formset 元素之內聲明。formset 元素之內首先聲明要驗 證的表單,表單之內列出了要驗證的輸入域及其驗證條件。在本例中,我們希望驗證 myFormBean的name屬性,檢查該屬性是否能夠通過containsStar的驗證(也即name 屬性 的值是否包含"*"字符)。 ② 以XML文件為基礎,創建一個Validator 實例并予以初始化。 // 裝入驗證器XML 文件 InputStream in = getClass().getResourceAsStream ("validator.xml"); // 創建一個ValidatorResources ValidatorResources resources = new ValidatorResources(); // 初始化驗證器資源 ValidatorResourcesInitializer.initialize(resources, in); // 創建Validator Validator validator = new Validator(resources, "myFormBean"); validator.addResource(Validator.BEAN_KEY, bean); ③ 驗證Bean。驗證的結果是一個ValidatorResults,其中包含了各個要求驗證的屬性 按照各自的驗證條件執行驗證的結果。 // 執行驗證 ValidatorResults results = validator.validate(); ④ 處理ValidationResults。 //驗證結果對象ValidationResults 可能還包含了驗證其他表單屬性的結果, //對于每一個屬性,我們都可以單獨提取其驗證結果。 ValidatorResult result = results.getValidatorResult("name"); // 對于每一個屬性,我們可以分別檢查各個驗證條件的檢查結果。 // 例如,name 屬性通過了containsStar 驗證嗎? System.err.println("name 屬性包含"*"字符的測試結果:" + result.isValid("containsStar")); 對于每一個ValidationResult 的實例,我們可以查詢它是否通過了某項特定的檢查。 例如,在上面的代碼中,我們用result.isValid('containsStart')表達式來檢查name 屬 性的ValidatorResult 實例,看看它是否通過了containsStar 驗證。 對于Web 應用來說,Validator是一個相當有用的組件,它提供了一組預定義的驗證器, 極大地方便了用戶輸入合法性的驗證。預定義的驗證器可以用來(但不限于)檢查輸入值的 范圍、數據類型、長度,以及email 地址和地理位置檢查。此外,我們還可以自己定義驗證 器并將它加入到Validator框架之中。 中國浙江 之后執行哪些操作: paramcount="0" /> methodname="setAddress" paramcount="0" /> paramcount="0" /> BR>BR>這個文件有哪些含義呢?第一條規則, classname="Company" />,告訴Digester 如果遇到了模式company,則必須遵從object-create-rule, 也就是要創建一個類的實例!那么要創建的是哪一個類的實例呢?classname="Company"屬性指定了類 的名稱。因此,解析company.xml 的時候,當遇到頂級的company元素,等到object-create-rule規則執 行完畢,我們就擁有了一個Digester 創建的Company 類的實例。 現在要理解call-method-rule 規則也應該不那么困難了,這里call-method-rule 的功能是在遇到 company/name 或company/address 模式時調用一個方法(方法的名字通過methodname 屬性指定)。 最后一個模式匹配值得注意,它把規則嵌套到了匹配模式之中。兩種設定規則和模式的方式都是 Digester 接受的,我們可以根據自己的需要任意選擇。在這個例子中,模式里面定義的規則在遇到 company/employee 模式時創建一個Employee 類的對象,設置其屬性,最后用set-next-rule將這個雇員 加入到頂層的Company。 創建好上面兩個XML 文件之后,只要用兩行代碼就可以調用Digester了: Digester digester = DigesterLoader.createDigester(rules.toURL()); Company company = (Company)digester.parse(inputXMLFile); 第一行代碼裝入規則文件,創建一個Digester。第二行代碼利用該Digester 來應用規則。請參見本文 后面提供的DigesterDemo.java 完整源代碼。 2.3 Jelly ■ 概況:一種基于Java和XML 的腳本語言。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:簡單地說,當你想要一種靈活的、可擴展的XML 腳本工具之時。 ■ 示例應用:JellyDemo.java,jellydemo.xml以及TrivialTag.java。要求CLASSPATH 中必須有 commons-jelly-1.0-dev.jar、dom4j.jar、commons-logging.jar、commons-beanutils.jar以及 commons-collections.jar。 ■ 說明: 要說清楚Jelly到底是什么以及它扮演著哪種角色是件很不容易的事情。Jelly 試圖提供一個通用的 XML 腳本引擎,這種腳本引擎是可以由開發者通過定制動作和標記擴展的,XML文檔之中的元素映射到 JavaBean,而XML 元素的屬性映射到JavaBean的屬性。從某種意義上說,Jelly是一種結合了Betwixt 和Digester的工具,但Jelly更強大,具有更好的可擴展性。 一個Jelly 系統由多個組件構成。第一個組件是Jelly 腳本,它是一種由Jelly引擎解析的XML文檔, 經過解析的XML 文檔元素被綁定到Jelly 標記動態處理。第二個組件是Jelly標記,它是一種實現了Jelly 的Tag 接口的JavaBean,凡是Jelly 標記都可以實現doTag 方法,這個doTag 方法就是當腳本引擎遇到 XML 文檔中的特定元素時所執行的方法。Jelly正是通過這一機制實現動態的腳本處理能力,從某種意義 上看,有點類似于Digester 的工作機制。 Jelly 帶有許多預定義的標記,其中部分標記提供核心Jelly支持,其他標記用來提供解析、循環、 條件執行代碼等方面的支持。另外,Jelly 還為Ant任務提供了廣泛的支持。 要在Java應用程序中使用Jelly,首先要創建一個JellyContext的實例,例如:JellyContext context = new JellyContext();。我們可以把JellyContext對象看成是一個編譯和運行Jelly腳本的運行環境。 有了JellyContext 就可以運行Jelly 腳本。JellyContext的輸出實際上是一個XMLOutput類的實例: context.runScript(new File("jellydemo.xml"), output);。 創建自定義標記時,我們既可以覆蓋上面提到的doTag方法(如下面的例子所示),或者提供一個執 行方法,如invoke()或run(): public void doTag(XMLOutput output) throws Exception { // 在這里加入要執行的操作, // 例如設置屬性、訪問文件系統等… this.intProp = 3; } 下面提供了一個定義Jelly 腳本的XML 文件示例: xmlns:tr="trivialTag"> 這個例子用到jelly:define 和jelly:core標記,以及一個trivialTag 標記。當遇到trivial標記 實例時,Jelly創建相應的JavaBean 的實例,執行doTag 方法(或者也可以是一個run 或invoke之類可 調用的方法)。 Jelly 還有許多其他功能,它既可以直接從命令行或Ant腳本運行,也可以嵌入到應用程序的代碼之 內,請參見Jelly 文檔了解詳情。 2.4 JXPath ■ 概況:Java中的XPath 解釋器。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你想要在JavaBean、DOM或其他對象構成的結構中應用XPath 查詢之時。 ■ 示例應用:JXPathDemo.java,Book.java,Author.java。要求CLASSPATH必須包含 commons-jxpath-1.1.jar。 ■ 說明: 下面的說明要求讀者已具備基本的XPath 知識。 XPath是一種查詢XML文檔的語言,JXPath將同一概念應用到了其他Java對象的查詢,諸如JavaBean、 Collection、Array和Map 等。 JXPathContext是JXPath中的核心類,它利用一個工廠方法來定位和創建一個上下文的實例。由于 有了這一機制,必要時開發者可以插入一個新的JXPath 的實現。要使用JXPathContext,只要簡單地向 它傳遞一個JavaBean、Collection 或Map,例如:JXPathContext context = JXPathContext.newContext(book);。 利用JXPathContext 可執行許多任務。例如訪問屬性或嵌套屬性,當然還可以設置屬性: System.err.println(context.getValue("title")); System.err.println(context.getValue("author/authorId")); context.setValue("author/authorId", "1001"); 利用JXPath 還可以查找其他類型的對象,不過創建上下文對象的方式都一樣,都是用上面介紹的靜態 方法獲得一個新的上下文,傳入想要查詢的對象。 結束語:有關包裝類和XML 類的介紹就到這里結束。在下一篇也是最后一篇文章中,我們將了解工具 類的包。在這個系列文章的第一篇中,我們把Commons項目包含的組件分成了5類,介紹了Web類和其他 類。第二篇文章論及XML 類和包裝類。這是最后一篇,探討工具類的組件。注意Commons本身并不進行這 種分類,這里進行分類純粹是為說明和組織方便起見。 第三部分、工具類 工具類包含BeanUtils、Logging、DBCP、Pool和Validator 這幾個組件。 一、BeanUtils ■ 概況:提供了動態操作JavaBean 的工具。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你需要動態訪問JavaBean,但對已編譯好的accessor 和 modifier 一無所知之時。被動態訪問的JavaBean 必須遵從JavaBeans specification 定義的命名設計規范。 ■ 示例應用:BeanUtilsDemo.java,AppLayer1Bean.java, AppLayer2Bean.java,SubBean.java。要求CLASSPATH 中必須包含commons-beanutils.jar、 commons-logging.jar 以及commons-collections.jar。 ■ 說明: 在動態Java應用程序設計環境中,我們不一定能夠預先獲知JavaBean 的各種set、get 方法。即使已經 知道了這些方法的名字,為Bean 的每個屬性依次寫出setXXX 或getXXX方法也是一件很麻煩的事情。考 慮一下這種情形:幾個幾乎完全相同的Bean 從應用的一個層傳遞到另一個層,你會為每一個屬性調用 bean1.setXXX(bean2.getXXX())嗎?雖然你可以這么做,但并非一定得這么做,因為你可以讓BeanUtils 為你完成這些繁瑣的操作!BeanUtils可以幫助開發者動態地創建、修改和復制JavaBean。 BeanUtils 能夠操作符合下列條件的JavaBean: ⑴ JavaBean必須提供一個沒有參數的構造函數。 ⑵ JavaBean的屬性必須能夠通過getXXX和setXXX方法訪問和修改。對于Boolean屬性,也允許使用isXXX 和setXXX。JavaBean的屬性可以是只讀或只寫的,也就是說,允許只提供屬性的set或get方法。 ⑶ 如果不采用傳統的命名方式(即用get 和set),改用其它方式命名JavaBean 的accessor和modifier , 那么必須通過與JavaBean 關聯的BeanInfo 類聲明這一點。 下面來看一個簡單的例子。 要獲取和設置JavaBean 的簡單屬性,分別使用PropertyUtils. getSimpleProperty(Object bean, String name)以及PropertyUtils. setSimpleProperty(Object bean, String name, Object value)方法。如下面的例子所示,其中 AppLayer1Bean.java和AppLayer2Bean.java 定義了兩個測試用的JavaBean。 PropertyUtils.setSimpleProperty(app1Bean,"intProp1", new Integer(10)); System.err.println("App1LayerBean, stringProp1: " + PropertyUtils.getSimpleProperty(app1Bean, "stringProp1")); 既然我們可以通過直接調用Bean 的方法(app1Bean.getStringProp1()或 app1Bean.setIntProp1(10))來獲取或設置Bean 的屬性,為什么還要使用setSimpleProperty、 getSimpleProperty方法呢?這是因為,我們不一定能夠預先知道JavaBean 屬性的名字,因此也不一定 知道要調用哪些方法才能獲取/設置對應的屬性。這些屬性的名字可能來自其他過程或外部應用程序設置 的變量。因此,一旦搞清楚了JavaBean的屬性的名字并把它保存到一個變量,你就可以將變量傳遞給 PropertyUtils,再也不必依靠其他開發者才能預先得知正確的方法名字。 那么,如果JavaBean 的屬性不是簡單數據類型,又該怎么辦呢?例如,JavaBean的屬性可能是一個 Collection,也可能是一個Map。在這種情況下,我們要改用PropertyUtils.getIndexedProperty或 PropertyUtils.getMappedProperty。對于集合類屬性值,我們必須指定一個索引值,規定待提取或設置 的值在集合中的位置;對于Map 類屬性,我們必須指定一個鍵,表示要提取的是哪一個值。下面是兩個例 子: PropertyUtils.setIndexedProperty( app1Bean, "listProp1[1]", "新字符串1"); System.err.println("App1LayerBean, listProp1[1]: " + PropertyUtils.getIndexedProperty(app1Bean, "listProp1[1]")); 請注意,對于可索引的屬性,索引值是通過方括號傳遞的。例如上面的例子中,我們把JavaBean (app1Bean)的List中索引為1 的值設置成了"新字符串1",后面的一行代碼又從索引1的位置提取同 一個值。還有另一種方式也可以達到同樣的目標,即使用 PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value)和PropertyUtils.getIndexedProperty(Object bean, String name, int index)方法,在這兩個方法中索引值作為方法的參數傳遞。對于Map類屬性,也有類 似的方法,只要改用鍵(而不是索引)來獲取或設置指定的值。 最后,Bean的屬性可能也是一個Bean。那么,怎樣來獲取或設置那些以屬性的形式從屬于主Bean 的 屬性Bean 呢?只要使用PropertyUtils.getNestedProperty(Object bean, String name)和 PropertyUtils.setNestedProperty(Object bean, String name, Object value)方法就可以了。下面提 供了一個例子。 // 訪問和設置嵌套的屬性 PropertyUtils.setNestedProperty(app1Bean, "subBean.stringProp", "來自SubBean 的信息,通過setNestedProperty 設置。"); System.err.println( PropertyUtils.getNestedProperty(app1Bean,"subBean.stringProp")); 通過上面的例子可以看出,從屬Bean 的屬性是通過一個句點符號訪問的。 上述幾種訪問屬性的方式可以結合在一起使用,嵌套深度不受限制。具體要用到的兩個方法是 PropertyUtils.getProperty(Object bean, String name)和PropertyUtils.setProperty(Object bean, String name, Object value)。例如:PropertyUtils.setProperty(app1Bean, "subBean.listProp[0]", "屬性的值");。 這個例子是把嵌套Bean 對象和可索引屬性結合在一起訪問。 BeanUtils經常用于動態訪問Web 應用中的請求參數。實際上,正是BeanUtils觸發了Struts 項目中 把請求參數動態轉換成系統JavaBean 的靈感:利用代碼把用戶填寫的表單轉換成一個Map,其中參數的 名字變成Map中的鍵,參數的值則來自于用戶在表單中輸入的數據,然后由一個簡單的 BeanUtils.populate調用把這些值轉換成一個系統Bean。 最后,BeanUtils 提供了一個一步到位的方法把數據從一個Bean 復制到另一個Bean: // 把app1Bean 的數據復制到app2Bean BeanUtils.copyProperties(app2Bean, app1Bean); BeanUtils 還有一些這里尚未提及的實用方法。不過不必擔心,BeanUtils 是Commons 中文檔較為完善的 組件之一,建議讀者參閱BeanUtils 包的JavaDoc 文檔了解其余方法的相關信息。 二、Logging ■ 概況:一個封裝了許多流行日志工具的代碼庫,并提供統一的日志訪問接口。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你的應用需要一種以上的日志工具之時,或者預期以后會有這種需要之時。 ■ 示例應用:LoggingDemo.java,commons-logging.properties。要求CLASSPATH中必須包含 commons-logging.jar,有時還需要log4j.jar。 ■ 說明: 日志(Logging)使得我們能夠調試和跟蹤應用程序任意時刻的行為和狀態。在任何規模較大的應用中, Logging 都是不可或缺的組成部分,因此現在已經有許多第三方Logging工具,它們免去了開發者自己編 寫Logging API 之勞。實際上,即使JDK 也帶有構造好了的Logging API。既然已經有這么多選擇(log4j , JDK,Logkit,等等),通常我們總是可以找到最適合自己應用要求的現成API。 不過也有可能出現例外的情形,例如一個熟悉的Logging API 不能和當前的應用程序兼容,或者是由于某 種硬性規定,或者是由于應用的體系結構方面的原因。Commons項目Logging 組件的辦法是將記錄日志的 功能封裝為一組標準的API,但其底層實現卻可以任意修改和變換。開發者利用這個API來執行記錄日志 信息的命令,由API 來決定把這些命令傳遞給適當的底層句柄。因此,對于開發者來說,Logging組件對 于任何具體的底層實現都是中立的。 如果你熟悉log4j,使用Commons 的Logging API 應該不會有什么問題。即使你不熟悉log4j,只要知道 使用Logging必須導入兩個類、創建一個Log 的靜態實例,下面顯示了這部分操作的代碼: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class LoggingDemo { private static Log log = LogFactory.getLog (LoggingDemo.class); // ... } 有必要詳細說明一下調用LogFactory.getLog()時發生的事情。調用該函數會啟動一個發現過程,即 找出必需的底層日志記錄功能的實現,具體的發現過程在下面列出。注意,不管底層的日志工具是怎么找 到的,它都必須是一個實現了Log 接口的類,且必須在CLASSPATH 之中。Commons LoggingAPI直接提供 對下列底層日志記錄工具的支持:Jdk14Logger,Log4JLogger,LogKitLogger,NoOpLogger(直接丟棄 所有日志信息),還有一個SimpleLog。 ⑴ Commons 的Logging 首先在CLASSPATH 中尋找一個commons-logging.properties 文件。這個屬性 文件至少必須定義org.apache.commons.logging.Log 屬性,它的值應該是上述任意Log 接口實現的完整 限定名稱。 ⑵ 如果上面的步驟失敗,Commons 的Logging接著檢查系統屬性 org.apache.commons.logging.Log。 ⑶ 如果找不到org.apache.commons.logging.Log系統屬性,Logging接著在CLASSPATH中尋找log4j 的類。如果找到了,Logging就假定應用要使用的是log4j。不過這時log4j 本身的屬性仍要通過 log4j.properties 文件正確配置。 ⑷ 如果上述查找均不能找到適當的Logging API,但應用程序正運行在JRE 1.4 或更高版本上,則 默認使用JRE 1.4 的日志記錄功能。 ⑸ 最后,如果上述操作都失敗,則應用將使用內建的SimpleLog。SimpleLog 把所有日志信息直接輸 出到System.err。 獲得適當的底層日志記錄工具之后,接下來就可以開始記錄日志信息。作為一種標準的API,Commons Logging API 主要的好處是在底層日志機制的基礎上建立了一個抽象層,通過抽象層把調用轉換成與具體 實現有關的日志記錄命令。 本文提供的示例程序會輸出一個提示信息,告訴你當前正在使用哪一種底層的日志工具。請試著在不 同的環境配置下運行這個程序,例如,在不指定任何屬性的情況下運行這個程序,這時默認將使用 Jdk14Logger;然后指定系統屬性-Jorg.apache.commons.logging.Log=org.apache.commons.logging.imp l.SimpleLog 再運行程序,這時日志記錄工具將是SimpleLog;最后,把Log4J的類放入 CLASSPATH,只要正確設置了log4j 的log4j.properties 配置文件,就可以得到Log4JLogger輸出的信息 。 三、Pool ■ 概況:用來管理對象池的代碼庫。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:當你需要管理一個對象實例池之時。 ■ 示例應用:PoolDemo.java 和MyObjectFactory.java。要求CLASSPATH中必須有commons-pool.jar 和commons-collections.jar。 ■ 說明: Pool 組件定義了一組用于對象池的接口,另外還提供了幾個通用的對象池實現,以及 一些幫助開發者自己創建對象池的基礎類。 對于大多數開發者來說,對象池應該不算什么新概念了。也許許多讀者已經在訪問數據 庫的時候使用過數據庫連接池,對象池的概念其實也相似。對象池允許開發者在緩沖區中創 建一組對象(創建對象的操作可以通過應用的配置文件完成,或者也可以在應用的啟動階段 完成),當應用程序需要用到對象時就可以很快獲得相響應。如果應用程序不再需要對象, 它仍舊把對象返回給緩沖池,下次需要使用對象時再從緩沖池提取。 Pool 組件允許我們創建對象(實例)池,但不限制我們一定要使用某個具體的實現。 Pool 組件本身提供了幾種實現,必要時我們還可以創建自己的實現。 Pool 組件包含三個基本的類:ObjectPool,這是一個定義和維護對象池的接口; ObjectPoolFactory,負責創建ObjectPool 的實例;還有一個PoolableObjectFacotry,它 為那些用于ObjectPool 之內的實例定義了一組生命周期方法。 如前面指出的,Pool組件包含幾種通用的實現,其中一個就是GenericObjectPool,下 面通過一個實例來看看它的用法。 ① 創建一個PoolableObjectFactory。這個工廠類定義對象如何被創建、拆除和驗證。 import org.apache.commons.pool.PoolableObjectFactory; public class MyObjectFactory implements PoolableObjectFactory { private static int counter; // 返回一個新的字符串 public Object makeObject() { return String.valueOf(counter++); } public void destroyObject(Object obj) {} public boolean validateObject(Object obj) { return true; } public void activateObject(Object obj) {} public void passivateObject(Object obj) {} } 本例創建了一個序號不斷增加的String 對象的池,驗證操作(validateObject)總是 返回true。 ② 利用PoolableObjectFactory 創建一個GenericObjectPool,maxActive、maxIdle 等選項都采用默認值。 GenericObjectPool pool = new GenericObjectPool (new MyObjectFactory()); ③ 從對象池"借用"一個對象。 System.err.println("Borrowed: " + pool.borrowObject()); ④ 把對象返回給對象池。 pool.returnObject("0"); 對象池的狀態可以通過多種方法獲知,例如: // 有多少對象已經激活(已被借用)? System.err.println("當前活動的對象數量: " + pool.getNumActive()); 本文后面提供的PoolDemo.java 提供了完整的源代碼。 四、DBCP ■ 概況:數據庫連接池。建立在Pool 組件的基礎上。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:需要訪問關系數據庫之時。 ■ 示例應用:DBCPDemo.java。要求CLASSPATH中必須有commons-dbcp.jar、 commons-pool.jar 以及commons-collections.jar。另外還要能夠訪問數據庫,配置適合該 數據庫的JDBC 驅動程序。示例應用測試的是一個MySQL數據庫連接,驅動程序是MySQL JDBC driver。注意運行這個程序需要二進制文件的nightly 版,當前的正式發行版缺少某些必需的類。 最后,運行這個示例程序時,應當確保已經為JDBC 驅動程序設置了系統屬性(-Djdbc.drivers=com.mysql.jdbc.Driver)。 ■ 說明: DBCP 建立在Pool組件的基礎上,提供了數據庫連接緩沖池機制。與常規的連接池相比, DBCP 的使用要稍微復雜一點,因為它的思路是以偽JDBC 驅動程序的形式提供一個通用的體 系。不過,前面我們已經了解了Pool組件的基本知識,現在要理解DBCP 的用法應該也很簡單了。 // ... // ① 創建一個GenericObjectPool 類的實例。 GenericObjectPool pool = new GenericObjectPool(null); // ... // ② 在前面討論Pool 組件時提到GenericObjectPool // 要求有一個PoolableObjectFactory 來創建需 // 要緩沖的Object 的實例,對于DBCP 來說, // 這一功能現在由PoolableConnectionFactory提 // 供,如下面的例子所示: DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory( "jdbc:mysql://host/db", "username", "password"); PoolableConnectionFactory pcf = new PoolableConnectionFactory( CF, pool, null, "SELECT * FROM mysql.db", false, true); // ... // ③ 現在,我們只要創建并注冊PoolingDriver: new PoolingDriver().registerPool("myPool", pool); 接下來就可以從這個連接池提取連接了。注意創建這個連接池時采用了maxActive、 maxIdle 等選項的默認值,如有必要,你可以在前面步驟1創建GenericObjectPool 類的實 例時自定義這些值。DBCPDemo.java 提供了一個完整的實例。 五、Validator ■ 概況:一個收集了常見用戶輸入驗證功能的API。 ■ 官方資源:主頁,二進制,源代碼。 ■ 何時適用:對JavaBean 執行常規驗證操作之時。 ■ 示例應用:ValidatorDemo.java,MyValidator.java,MyFormBean.java, validation.xml。要求CLASSPATH 中必須有commons-validator.jar, commons-beanutils.jar,commons-collections.jar,commons-digester.jar,以及 commons-logging.jar。 ■ 說明: 如果你曾經用Struts 開發過Web 應用,那么應該已經用過Validator包了。Validator 包極大地簡化了用戶輸入數據的檢驗。不過,Validator 并不局限于Web應用,它還可以方 便地用于其它使用了JavaBean的場合。 Validator 允許為用戶輸入域定義驗證條件,支持錯誤信息國際化,允許創建自定義的 驗證器,此外,Validator 包還提供了一些預定義的可以直接使用的驗證器。 驗證規則和驗證方法用XML文件定義(可以用一個或者多個XML 文件定義,但通常而言, 把它們分開比較好)。驗證方法文件定義了要用到的驗證器,指定各個實際實現驗證器的 Java 類(不要求這些類實現某些特定的接口,也不要求這些類必須從特定的類派生,只需 要遵從方法定義文件中聲明的定義就可以了)。 下面我們就來構造一個自定義的驗證器,它的功能是檢查Bean的一個String屬性是否 包含特定的字符("*")。 import org.apache.commons.validator.*; public class MyValidator { public static boolean validateContainsChar( Object bean, Field field) { // 首先獲得Bean 的屬性(即一個String 值) String val = ValidatorUtil.getValueAsString (bean, field.getProperty()); // 根據屬性中是否包含"*"字符,返回true 或false。 return ((val.indexOf('*') == -1)?false:true); } } ValidatorUtil類提供了許多實用方法,例如ValidatorUtil.getValueAsString用來 提取Bean的屬性值并返回一個String。現在我們要在XML文件中聲明MyValidator驗證器。 classname="MyValidator" method="validateContainsChar" methodParams="java.lang.Object, org.apache.commons.validator.Field" /> BR>可以看到,XML 文件詳細地定義了驗證方法的特征,包括該方法的輸入參數。下面來看 看使用這個驗證器的步驟。 ① 在上面的XML文件中加入我們要實現的驗證規則。 |
![]() |
|
Copyright © BlueSky_itwangxinli | Powered by: 博客園 模板提供:滬江博客 |