程式者的胡言亂語
TWJUG “山頂洞人日記 - 回歸到最純樸的開發” 簡報
上週六(5/17)適逢TWJUG移師至新竹,所以我也準備了一個簡報分享。主題是介紹我自己開發Java應用系統的種種,包括工具、主要的設計方法、以及一個叫做LWDBA(light-weight database access)的輕量級程式庫。簡報的投影片可以在SlideShare上看到。
因為同場還有tempo大濕要分享GWT的一些心得,所以之前我還開玩笑說這是「太空人」與「山頂洞人」的對決,因為我時常使用一些很過時的玩意,相較於對各式新技術十分熟稔的tempo來說(想到十年前,初次看到tempo寫的DDE-Dynamic Data
Exchange的程式時,簡直嚇了一跳呢),這無疑像是太空人和山頂洞人之間的對比。
這當然有點像是為了戲劇效果而說出來的開玩笑的話(沒辦法,tempo實在太愛演了!),而我在簡報時似乎也刻意強調這種反差,不過,這當然不是指我都不使用任何的新技術或框架,而是在於我覺得問題的核心並不在該處。
我在簡報的一開始提到:「慾望總是超過真實需求,威力強大的東西難免複雜,選擇開發工具要在複雜度與威力之間選擇一個適合你的平衡點。」事實上,有許多人的能力不足以駕馭他們想要使用的工具,舉個很簡單的例子來說吧,我相信還是有一堆使用Java IDE的人,搞不懂CLASSPATH究竟是什麼意思吧。
究竟要使用什麼樣的工具,必須匹配到你自身(或團隊開發成員)的能力(或skill set),也必須匹配到所欲開發本身的需求。好比我提到的例子,過去我們用Struts,現在有時也用,但是對於許多新興、但屬性又單純的專案,我甚至直接用JSP來寫action。這背後的關鍵在於必須評估,使用這樣的工具究竟能為你帶來什麼好處,而不是整個社群或社群大多數的人都這麼做,所以你也從善如流的這麼做。
「回到最簡樸的開發」,好比有很多人想要回到最簡樸的生活一樣。何謂簡樸的生活?在我看來,簡樸的生活是滿足確切真實慾望的一種生活,你不會因為追求不必要的慾望,而為自己招來煩惱和痛苦。其間的關鍵在於審視自己真實的需求。
專案就和生活一樣,也有專案本身的目標和設定的需求。我看到有許多專案,同樣為了不切實際的慾望,為自己創造了不必要的需求,自然也為自己招來不必要的煩惱和痛苦。
我在五年前,也做Web應用程式的程式碼產生器、表單欄位檢核器、…。為什麼現在,反而把它們都丟掉了。事實上,這些東西都超乎我現在的需求太多(當然,我是一定不會解釋為什麼我連鑰匙圈都要買GUCCI的,因為在這個理論下實在很難解釋)。
除了審視真實的需求之外,我提到另一個我覺得重要的地方,就是「問題不在你使用什麼framework,而是在於你的設計對不對」。
許多想要利用引進framework來節省自己的開發時間。在你能克服某種程度的複雜度以及學習曲線時,這能做到。不過,我比較懷疑的是,你究竟能節省多少時間。
即使你使用了Struts,你使用了Hibernate,但是,你沒有建立一個好的設計,所以你在Action class裡直接操作資料物件,也就是說在Action class中直接實作了business logic。光是你的Action class和你的資料物件之間的相依性,以及Action class和business logic實作之間的相依性,其複雜程度以及引發錯誤的機會,就足以在開發中後期把你拖跨。而這才是你真正很有可能會投入大量時間的地方。
你或許用了被普遍被採用的工具或應用程式框架,而這些工具或框架若是使用得法,也確實會有很大的助益,但根本的問題時常出在只是知其然而不知其所以然,那是一種「反正別人用了,我跟著用準沒錯」的心態在支配著。
把焦點移回你自身的設計,會更有助於真正的生產力。好的設計,能夠提昇品質,也能大幅的提昇開發的效率。有道是:「練拳不練功,到老一場空。」
Posted at 03:02下午 五月 21, 2008 by Chien-Hsing Wang in Java | 迴響[0]
使用Jakarta Apache之Commons-HttpClient時,忽略對建立SSL連線之certificate的檢查
續前文,這篇blog要講的是使用Jakarta Apache之Commons-HttpClient時,如何略對建立SSL連線之certificate的檢查的方法。
在這邊我必須說,有個情況我也還沒有找出真正的原因。就是前文中所提及的方法,有時對採用Jakarta Apache之Commons-HttpClient的程式可以起作用,有時不行。這裡挺怪的,因為HttpClient所賴以建立SSL連線的,應該也是Java的底層SSL Socket程式庫,所以前文中所提到的方法應該可以適用,不過我發現,有時候不行,而且目前還沒法子解釋。不過,下述提供一個方法,可以讓HttpClient忽略對憑證的檢查。基本上,可以先參考Commons官方網站上的SSL Guide。在這份文件裡,提到了如何自訂HttpClient中的 SSL行為。你可以從它的範例中找到EasySSLProtocolSocketFactory的例子,利用它來改就可以了。
在HttpClient的原始碼中,可以找到這個package:org.apache.commons.httpclient.contrib.ssl。其中有兩個class是我們會用到的:它是
EasySSLProtocolSocketFactory及EasyX509TrustManager。把它們分別rename成你想要的名稱並且設定適當的package,例如MySSLProtocolSocketFactory及MyX509TrustManager。MySSLProtocolSocketFactory會使用到MyX509TrustManager,所以把原先EasySSLProtocolSocketFactory中指涉EasyX509TrustManager的地方,全部改成MyX509TrustManager。接著修改MyX509TrustManager的這幾個methods:
public MyX509TrustManager(){ super(); } public boolean isClientTrusted(X509Certificate[] certificates) { return true; } public boolean isServerTrusted(X509Certificate[] certificates) { return true; } public X509Certificate[] getAcceptedIssuers() { return null; } |
接著就必須在你的主程式裡註冊你的MySSLProtocolSocketFactory:
Protocol myhttps = new Protocol("https", new MySSLProtocolSocketFactory(), 443); Protocol.registerProtocol("https", myhttps); |
這代表你向HttpClient註冊https協定,採用443 port(標準的https port),由你的MySSLProtocolSocketFactory來處理。透過上述的設定,應能順利的在HttpClient中忽略對憑證的檢查。
Posted at 10:27上午 八月 27, 2007 by Chien-Hsing Wang in Java | 迴響[0]
Java中忽略對建立SSL連線之certificate的檢查
建立SSL連線時,伺服器會提供一份電子憑證(certificate),以供用戶端程式在連線時用以編解與伺服器之間的訊息交換。正常的情況下,在建立連線之際,用戶端程式收到伺服器提供的ceritificate,會向這張ceritifccate的憑證授權單位(CA)要求檢驗此張憑證的有效性。
有許多網站,尤其是還在測試中的網站,因為向憑證授權單位購買憑證是需要花錢的,所以都會自行利用一些簽證的工具,自行產生憑證,一般來說,就被稱為是 self-signed的certificate。當我們透過像IE這樣子的瀏覽器來檢視網址為https://開頭的網站時,倘若看到下述的視窗:

通常都是遇上self signed的網站。瀏覽器處理這種情況很簡單,因為它可以詢問使用者是否願意接受,再進行下一步的處理。但是使用Java來連線此類的網站時,事情就比較複雜。倘若我們寫下下述的程式碼:
import java.io.*; import java.net.*; public class TestCert { public static void main(String argv[]) { try { String _url = "https://www.cs.nthu.edu.tw/"; URL url = new URL(_url); URLConnection uc = url.openConnection(); InputStream is = uc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String s = br.readLine(); System.out.println(s); br.close(); isr.close(); } catch(Exception e) { e.printStackTrace(); } } } |
這段程式碼基本上就是連接到一個https://的網站,但由於這個網站的憑證是self signed的,所以發生了下述的錯誤:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExce ption: unable to find valid certification path to requested target at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1 518) |
這便是Java的SSL socket實作在收到伺服器回傳的憑證時,由於無法認證該憑證,而引發的錯誤。有一個解決的方法,便是把這個憑證加到你Java執行環境中儲存信任憑證的地方,不過這不是本篇blog的主旨,本篇的主旨在於如何忽略檢查,方法如下:
import javax.net.ssl.*; TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { } public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { } }}; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); |
基本上,就是為目前SSL的SocketFactory設定一個自訂的TrustManager,並且在TrustManager該有的介面中,pass所有的檢查。如此一來,便可以忽略掉上述所提到的錯誤。
使用上述的方式,必須小心整個application是否會連線到不在預期中的網站,因為這樣子的寫法,等於是開放完全不檢查來自所有網站的憑證是否有效。我是偷懶的人,所以不想把憑證加到信任的cert store去,但因為明確的知道憑證的來源網站,所以才使用忽略的方式,倘若你的情況並非如此,就應該審慎的評估。
另外,如果你所收到的憑證,其中的domain name並未和其實際的domain name相符(我有遇到domain name填IP address的情況:p),也會引發憑證的檢查錯誤,解決之道(其實就是忽略之道)如下:
HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String urlHostName, SSLSession session) { System.out.println("Warning: URL Host: "+urlHostName+" vs. "+session.getPeerHost()); return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hv); |
為java.net.ssl.HttpsURLConnection設定一個自訂的HostnameVerifier,並且在其verify()中回傳檢查成功(true),就可以把對憑證host name的檢查予以忽略。
以上兩種情況,約略是連接至測試用之https網站的主要問題。
Posted at 10:06上午 八月 27, 2007 by Chien-Hsing Wang in Java | 迴響[3]
用ROME parse Yahoo新聞的RSS內容(ROME所能parse的日期格式)
今天不願透露姓名且有意角逐2028年總統大位的邱姓學弟來託夢,表示ROME 0.9版不能正確的parse Yahoo的新聞RSS。夢醒來後立即一試,發現的確不能正確的parse。其中不能parse的原因在於,其中的pubDate欄位,ROME怎麼取都會取到null。仔細追查,發現在於Yahoo RSS的pubDate欄位寫法,是非標準的寫法,像是:Sun 15 Jul 2007 21:49:09 GMT
而ROME支援幾種日期的寫法,包括RFC822、W
datetime.extra.masks=EEE dd MMM yyyy HH:mm:ss z
並且將rome.properties設到CLASSPATH所在的位置上,的確就可以正確的parse Yahoo RSS所用的日期格式了(EEE代表三位的每週的那一天,dd代表每月第幾天,MMM代表三位的月份文字表示式,yyyy代表四位的西元年份,HH :mm :ss代表時分秒,而z是時區)。
Posted at 09:05下午 七月 16, 2007 by Chien-Hsing Wang in Java | 迴響[1]
五分鐘DWR快速上手
最近因為特殊的需要,開始研究AJAX。我的習慣是傾向用落後一到兩個世代的技術來開發,所以之前對AJAX完全沒有接觸。所以,便從tempo大濕的名著《荒島漂流記》開始讀起,一讀之後發現這DWR實在是很單純,很適合像我這樣的初學者入門,只要把握要領,有經驗的Java程式員應該要在五分鐘內就可以運用起來才對。所以,我覺得可以試著整理幾個重點,有心採用DWR(但無意漂流荒島)來開發AJAX應用程式的朋友們可以參考看看。
第一個重點就是,DWR是一種RPC(remote procedure call)式的AJAX framework。在RPC模式下,client(caller)透過某個包裝procedue call的通訊協定,呼叫位在遠端的程序(callee)。在使用DWR時,caller就是瀏覽器上執行的javascript程式,而部署在遠端的Java類別所提供的method就是callee。
再來就是,那你要如何實作遠端的函式呢?基本上,你只要撰寫一個普通的Java class,提供希望被叫用的methods就行了。但是,你必須要做兩樣設定的工作,第一個是在你Web應用程式的web.xml加上DWR的一個servlet:
<servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class> org.directwebremoting.servlet.DwrServlet </servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> |
第二個,便是必須把一個叫做dwr.xml的檔案放到你的Web應用程式的WEB-INF目錄中(和web.xml擺在一)。這個dwr.xml的長相看起來會有點像是這樣:
<dwr> <allow> <create creator="new" javascript="FriendWidgetDWR" scope="session"> <param name="class" value=" widget.friend.dwr.FriendWidgetDWR" /> </create> </allow> </dwr> |
基本上,dwr.xml要描述的就是在你的系統中,那些class是要提供DWR遠端程序的,以及相對應,該class對javascript來說的名稱。例如本例中,對javascript來說,它叫做FriendWidgetDWR,而對應到Java的遠端物件類別名稱,就是widget.friend.dwr.FriendWidgetDWR。可以想見的是,DWR的invoker在收到來自javascript的請求時,會依據dwr.xml裡的設定,透過reflection的機制,來把遠端的Java物件產生出來,並且呼叫相對應的method。
另一方面,因為有DWR的invoker的存在,所以不可能無限制開放任何一個class都允許位在client side的javascript呼叫,這個dwr.xml的目的之一,也是為了保護不希望開放的class被javascript使用到。
第三個重點,撰寫你的DWR class。基本上就跟一般的Java class沒有什麼兩樣。通常你的method不是回傳void就是String。傳入的參數如果是String的話,是不需要做什麼額外的設定的,一會再來說如果不是String的情況要怎麼辦。倘若你要回傳的是String,那麼你要回傳什麼String呢?通常是一段要給javascript使用的字串,最常見的就是一段HTML碼,這段HTML碼是要讓javascript程式拿來操作DOM的。
public String genBlogTitle(String id) { return "<table border=0 bgcolor=#FFFFFF width=240><tr><td aligh=center>title</td></tr></table>"; } |
第四個重點,就是就是撰寫你的javascript程式。你得include一些制式的javascript檔案,包括:
<script type='text/javascript' src='/friendwidget/dwr/interface/FriendWidgetDWR.js'></script> <script type='text/javascript' src='/friendwidget/dwr/engine.js'></script> <script type='text/javascript' src='/friendwidget/dwr/util.js'></script> |
標紅色的friendwidget是Web應用程式context的名稱,而FriendWidgetDWR則是在dwr.xml中所設定的。下面程式碼裡的FriendWidgetDWR同樣就是在dwr.xml裡設定的,而genBlogTitle就是在Java class裡寫的method。Java method-genBlogtitle()接受一引數,但在使用javascript呼叫它時,因為這個method會傳回字串,所以可以指定一個callback函式來加以處理,在下例中的callback函式,是當genBlogTitle()完成後,將它所回傳的字串,設成id為blogTitle這個DOM節點的innerHTML。
window.onload = function() { FriendWidgetDWR.genBlogTitle ("qing", function(returnStr) { $('blogTitle').innerHTML = returnStr; }); } |
到此,大概就把DWR的主要流程說完了。基本上只要注意(1)要怎麼設定(2)寫Java class方式(3)寫javascript程式的方式,就可以了。
使用DWR是很簡單的,只要你把它想像是讓javascript去呼叫一個遠端的函式就成了。此外,最大的特別處在於這遠端函式回傳的通常是一段要拿來動態操作DOM的HTML片段。當然,直接讓遠端類別回傳一段javascript碼,再於javascript端透過eval()去執行該段javascript碼,應用起來會更有彈性。
所以我說,DWR觀念上十分簡單,五分鐘上手非難事,就連毒菇木也都能輕易的運用的得心應手呢。
最後要附註的就是,DWR裡遠端呼叫函式的傳入引數,其型別若非預設可轉換的型別時,得在dwr.xml中進行額外的指定,例如:
<signatures> <![CDATA[ import java.util.Map ; qing.QPOperationTabGen.submitFormData(Map); ]]> </signatures> |
因為qing.QPOperationTabGen.submitForData()希望能傳入一個Map引數,所以我們在dwr的xml裡特別定義了這段signatures的部份,明確的指定其signature,以便使DWR的engine本身能夠正確的判斷所欲傳入的型別究竟為何。
那倒底什麼情況會傳入Map呢?最常見的情況就是把某個表單的資料(多個name value pair)傳入囉。以下的這個函式,就會取出DOM中id分別為field1、field2
星期三 五月 21, 2008