Orienginal URL: http://dev2dev.bea.com.cn/bbs/yuanch/ArticleShow.jsp?Id=35
摘要
擁有自己的音樂站點是個不錯的想法,相比傳統的ASP或PHP站點,我們將采用J2EE架構實現多層結構的,高度可擴展的站點。您將看到,采用J2EE技術,加上強大的WebLogic平臺,我們能更容易的實現一個音樂站點應用,而非傳統意義上的平面結構的網站。
簡介
隨著J2EE的快速普及,越來越多的開發人員都想編寫基于J2EE架構的分布式的企業級應用程序。為了降低開發難度,J2EE提供的基于組件的,分層的分布式應用模式,使具有可伸縮,可擴展和易維護等優點。利用J2EE,可以快速開發、部署和管理多層結構、面向Web的,以服務器為中心的企業級應用。
在下面的這個示例中,我們將構建一個基于J2EE的音樂站點,暫定名為JetMusic,計劃給用戶提供瀏覽,下載等在線服務。事實上,Internet上已有很多這種類型的站點,但是它們大多是傳統的基于兩層結構的模型,使用了混合HTML和ASP或PHP腳本代碼的頁面。
盡管我們的JetMusic站點可能現在規模不大,僅僅使用JSP/JavaBeans/數據庫就已經足夠了,但是作為示例,我們還是打算用J2EE來實現它。借助J2EE的強大功能,我們能夠輕易地快速創建這樣一個音樂站點,并且使我們的編碼量最小。更重要的是,它是完全的3層分布式結構,具有很強的擴展性。
系統設計
我們的站點需要對用戶提供瀏覽,下載等服務,還要允許系統管理員能隨時登陸并管理站點資源。對于我們的JetMusic站點,我們計劃使用樹型結構來組織分類、藝術家、專輯、歌曲,一個簡單的示例圖如下:

對用戶而言,我們需要提供以下服務:
用戶可以瀏覽分類、藝術家、專輯、歌曲、歌詞。
用戶可以按關鍵字模糊搜索歌曲,專輯,藝術家和歌詞。
用戶可以對某一首歌評分。
如果歌曲文件存在,用戶可以下載歌曲。
如果歌詞存在,用戶可以瀏覽歌詞。
為了管理站點資源,我們把使用者分為用戶和管理員兩類:
用戶可以注冊,更改自己的信息。
管理員可以創建或刪除分類,藝術家,專輯,歌曲。
以上可以簡單看作是我們分析的全部用例。我們將采用J2EE標準的三層結構模型,將系統分為表示層,邏輯層和持久層(EIS層就免了,因為我們并沒有所謂的“遺產系統”),下面將詳細討論設計方案。
通常,設計總是從域模型開始的,我們通過對用例的分析,可以建立以下實體:
分類對象(Category),藝術家對象(Artist),專輯對象(Album),歌曲對象(Song),這些對象是簡單的一對多關系。我們把這些對象都一一映射為實體Bean,由于這些對象最終映射為數據庫表,這些表的結構相對簡單,因此我們選擇CMP實現。相比BMP,CMP不僅大大簡化了數據庫訪問的代碼,而且由容器管理的特性通常使CMP比BMP具有更好的緩沖性能和可移植性。
在邏輯層,毫無疑問,會話Bean將是最好的選擇。如果不使用Session Bean,您可能需要自行處理多線程同步,安全,事務處理等問題,這將是一項復雜而艱巨的工程。由于容器自動為我們處理了大量的如事務,安全,多線程等底層服務操作,我們就只需關注于我們的業務邏輯。此外,對于異步調用,消息驅動Bean是不錯的選擇。
在表示層,我們用JSP頁面將界面呈現給用戶。有過網站開發經驗的開發人員一定有過這樣的深刻感受,HTML元素和JSP代碼混合在一起是多么巨大的災難,它將導致可維護性隨著頁面數量的增加呈指數增長。
開源的Struts模型通過MVC架構大大簡化了JSP前端的開發。但是,使用Struts還要求開發人員對于Struts底層結構和配置文件有相當的了解。WebLogic提供的Java Page Flow技術完全基于Struts技術,并且隱藏了更多的底層細節,提供了圖形化的頁面導航功能,大大簡化了Web層的開發。此外,大量的NetUI標簽和數據綁定技術,使您的JSP頁面立刻具備了可視化開發的功能。我們將采用各種實現方式來編寫Web層。
開發環境
J2EE僅僅是一個標準,眾多的廠商提供了相應的實現平臺,其中,BEA的WebLogic無疑是最優秀的J2EE平臺之一。我們將把JetMusic站點建立在WebLogic Platform 8.1平臺上,并采用WebLogic Workshop 8.1這個強大的IDE作為開發工具。你可以免費從BEA官方站點上下載非商業目的開發使用的5-IP限制版本的WebLogic Platform 8.1:
http://commerce.bea.com/showproduct.jsp?family=WLP&major=8.1&minor=2
我們的站點資源除了歌曲文件外,將全部存放在關系數據庫中。選用MS SQL Server 2000的原因是這個數據庫對硬件資源的消耗要比Oracle少,適合于我們作開發使用。如果您計劃采用Oracle或其他數據庫,沒有關系,您只需修改實體Bean的相關部署配置,即可立刻移植到其他廠商的數據庫上。
要流暢地運行WebLogic Server 8.1和WebLogic Workshop 8.1,您至少需要512M內存,如果僅有256M內存,您可以修改WebLogic的啟動參數,指定較小的JVM內存(如128M)。
配置數據庫:
啟動MS SQL Server,打開企業管理器,新建數據庫music,請注意我們并不需要建立任何表,稍候部署我們的應用時WebLogic會自動創建相應的表。
配置WebLogic:
新建一個名為“music”的配置,并選擇模板Basic Weblogic Workshop Domain,以便能使用Workshop進行開發和調試。
啟動WebLogic,打開瀏覽器,輸入http://localhost:7001/console,登陸,在左邊找到music→Services→JDBC→Connection Pools,新建一個連接池,選擇數據庫“MS SQL Server”,驅動程序“BEAs MS SQL Server Driver(Type 4) Versions:7.0, 2000”,填好數據庫名,用戶名,口令等,測試無誤后部署。然后選擇左邊的music→Services→JDBC→Data Sources,新建一個名為“jdbc/MusicDataSource”的數據源,使用剛才建好的連接池,部署。
啟動Workshop,選擇New→Application新建應用程序Music,然后選擇Browse,找到服務器配置文件“<BEA安裝目錄>user_projectsdomainsmusicconfig.xml”,確定后創建一個應用程序。
為了有效組織和管理代碼,我們把所有的實體Bean均放在包music.ejb.db中,所有的會話Bean放在music.ejb中,另外建一個music.shared包用于存放會話Bean和表示層JSP/Servlets共享的Java類,包括各種自定義異常,傳遞的值對象等。
我們將創建3個工程,分別為MusicEjb Project,實現持久層和邏輯層;MusicClient Project,用于在命令行測試和調試邏輯層和持久層;MusicWeb Project,用于實現表示層。創建好的結構在Workshop中顯示如下:
設計持久層
我們首先開始設計持久層,通過前面的分析,我們抽象出以下實體Bean:
Category實體Bean:代表一個分類;
Artist實體Bean:代表一個藝術家;
Album實體Bean:代表一個專輯;
Song實體Bean:代表一首歌曲;
Account實體Bean:代表一個用戶。
對應的CMR關系為:
Category-Artist:一對多
Artist-Album:一對多
Album-Song:一對多
先建立一個簡單的名為Category的Entity Bean:展開左側MusicEjb→music→ejb→db,選擇菜單File→New→Entity Bean,在設計視圖中分別添加3個CMP字段,一個Create方法,一個Finder方法,另外我們還添加了一個copy方法用于返回一個Value Object對象:
然后在右邊的屬性面板中為Entity Bean設置屬性,在這里我們的主要設置如下:
data-source-name: jdbc/MusicDataSource
default-transaction: Required
table-name: category
Local EJB
JNDI name: ejb/Category
Bean class name: CategoryLocal
Home class name: CategoryLocalHome
為了實現自增主鍵的功能,需要指定一個Ejb-Gen屬性:點擊右鍵彈出菜單,選擇Insert EJB Gentag→automatic-key-generation,指定字段名id,數據庫類型SQLServer2000即可。
為了提高CMP的性能,我們全部采用本地接口,如果某些CMP字段需要提供只讀接口(如id,username字段),只需右鍵點擊字段名,然后選擇“Read Only”。
按相同方法建立Artist.ejb,Album.ejb,Song.ejb,Account.ejb,然后創建CMR關系:
打開Category.ejb的設計視圖,點擊右鍵,在彈出菜單里選擇Add Relation,建立一對多的CMR關系:
類似的,創建Artist-Album和Album-Song的一對多關系。
注意我們還設計了一個AccountUtil類,使用正則表達驗證用戶信息的有效性,比如用戶名,口令是否符合要求。
設計邏輯層
首先我們設計一個JndiHelper類,用于封裝查找實體Bean的JNDI操作,同時還對實體Bean的Home接口緩存。
我們把所有的邏輯封裝到3個Session Bean中:
MusicView.ejb:用于封裝瀏覽,下載等頁面操作。
AccountManage.ejb:用于封裝注冊,登陸等賬號操作。
MusicManage.ejb:用于封裝管理員的各項操作。
首先建立MusicView.ejb,我們一共設計了以下業務方法:
所有的方法均為遠程方法,這樣Web層和EJB就可分開部署。當然你也可以將會話Bean接口全部改成本地接口以進一步提高性能。方法看上去雖多,但邏輯相當簡單,例如,getAlbums(int artistId)方法:
基本邏輯大致為:
查找相應的實體Bean,獲得實體Bean對應CMR關系的集合,在將其包裝成值對象返回給客戶端。你可以看到,由于沒有JDBC操作,代碼被大大的簡化了,并且由于容器處理了底層數據庫連接,使我們的代碼更加健壯。
在這里我們采用了值對象(Value Object)模式,用于在Web層和邏輯層之間傳送對象。你可以很容易的寫出每個實體Bean對應的值對象,比如Album.ejb對應的值對象AlbumVO。
然后創建AccountManage.ejb,用于處理用戶帳號相關的操作:
如果要提高系統的安全性,建議使用MD5碼存儲用戶口令,MD5算法是一個單向函數,即使獲得數據庫表也無法得知用戶口令。如果要進一步防止使用預先算好的MD5碼攻擊,還可以采用加鹽處理。為了簡單起見,我們直接將用戶口令存儲在數據庫表的字段中,因此login(String username, String password)看起來像這樣:
如果找到了相應的實體Bean(即數據庫表中存在此記錄),再判斷password字段,如果相符,驗證通過,否則,拋出一個自定義的UnauthorizedException異常。這里也不涉及JDBC操作,因此代碼非常簡單。
搜索功能是通過實體Bean的Finder方法實現的,標準的EJB QL 2.0并不支持帶參數的like關鍵字(在SUN J2EE SDK中編譯就會失敗),幸好WebLogic對其進行了擴充。為了實現模糊搜索,這里我們定義了Song.ejb的一個Finder方法findByTitle(String title),對應的EJB QL為“SELECT OBJECT(o) FROM Song AS o WHERE lower(o.title) LIKE concat(%, concat(?1, %))”。如果使用其他廠商的服務器,你需要查看廠商的EJB QL文檔然后作相應的修改。
最后一個是MusicManage.ejb,用于管理員創建、刪除信息:
創建和刪除實體Bean的代碼都非常簡單,唯一需要的就是捕獲相應的異常,我們在deleteSong(int songId)中將RemoveException包裝成更一般的ApplicationException異常返回客戶端:
需要特別注意的是,對存在一對多CMR關系的實體Bean,如果指定了級聯刪除,當刪除“一”時,對應的“多”會被自動刪除,因此執行刪除前要異常小心,至少應該提示用戶,或者,只允許刪除“多”為空的實體Bean,比如刪除Artist.ejb時:
home = JndiHelper.getArtistLocalHome();
artist = home.findByPrimaryKey(new Integer(artistId));
Collection c = artist.getAlbums();
if(c.size()>0) throw new Exception("Cannot delete unless it is empty.");
為了安全起見,我們在刪除時不允許刪除“多”一方不為空的實體。
編譯,部署。OK,小功告成!現在我們可以測試一下我們的邏輯層。一個好的辦法是使用JUnit測試,但是似乎Workshop尚未集成JUnit。沒關系,我們自己寫一個客戶端Java程序來測試邏輯層。
在MusicClientProject中建立Client.Java類,放在包music.client中。為了測試,我們首先在數據庫中創建一些測試數據:
void initData () throws Exception { … }
然后,我們寫一個testGetCategories()方法:
void testGetCategoris(int parentCategoryId) throws Exception { … }
類似的,我們對每個業務邏輯都寫一個相應的測試方法,直到每一個業務邏輯都正確無誤。在一個獨立的Java客戶端程序中測試EJB要比在JSP中方便得多,你可以方便地設置斷點,跟蹤以便查看變量,隨時使用System.out.println()在控制臺輸出任何調試信息。
設計表示層
表示層用于向用戶提供系統交互的接口。采用J2EE的系統表示層一般都是瘦客戶端,使用JSP/Servlets技術,使用戶能通過瀏覽器使用系統。在JetMusic站點中,我們將考慮以下幾種實現方式:
1.在JSP中使用業務代表模式:
通常,在JSP/Servlets中直接調用EJB并不是一個好主意,這需要大量的查找JNDI的代碼,業務代表(Business Delegate)是一個不錯的模式,它封裝了所有的EJB查找和調用,使得表示層和邏輯層的耦合度能降到最低,并且還可以進行一些緩存。
由于業務代表類將Web和EJB隔開了,因此,對于JSP/Servlets來說,它們根本就不知道EJB的存在。這將帶來另一個好處:只要在設計時仔細定義了業務代表的接口,Web層和EJB的開發人員立即可以同時開發各自的模塊,Web層的開發人員可以首先對業務代表類進行硬編碼,以便返回他們希望的結果。等到EJB開發完成后,再用實際的EJB調用替換即可。
為了封裝對MusicView.ejb的所有調用,我們建立一個MusicViewBD的業務代表類:
在index.jsp中即可使用MusicViewBD以便輸出Categories:
2.使用NetUI標簽實現數據綁定
WebLogic提供了大量的NetUI標簽,能夠方便地在JSP中綁定數據。我們下一步將創建一個viewCa.jsp頁面,此頁面向用戶展示分類。
我們首先創建一個EJB Control。WebLogic向我們提供的EJB Control完全封裝了EJB調用。利用EJB Control,我們不用寫一行代碼,立刻就可以在JSP中調用EJB。這個EJB Control可以看作是一個用標簽封裝的業務代表模式的應用。
在MusicWeb工程下新建文件夾music.control,用于存放EJB Control。新建EJB Control,命名為CallMusicView:
選擇“Browse application EJBs…”,直接找到“MusicView (remote jndi)”,Workshop會自動填好相應的接口,點擊“create”即創建成功。
當我們獲得了從EJB返回的Collection后,可以使用Repeater標簽顯示這個復雜的數據,在這里我們不打算進一步討論Repeater標簽的細節,關于如何使用Repeater標簽,可以參考下面的文章:
http://dev2dev.bea.com/products/wlworkshop81/articles/repeater.jsp
以下JSP頁面顯示了如何使用EJB Control調用EJB的getCategories遠程方法,并將返回的Collection結果集用Repeater標簽以超鏈接的形式顯示出來,這里還用到了一個netui:anchor標簽:
第一個DeclareControl標簽申明了一個Control,名為ctrlMusicView,第二個CallControl標簽調用控件ctrlMusicView的一個方法getCategories并附帶參數MethodParameter,參數值由URL的id確定,然后將結果放在變量categories中。Repeater標簽從categories取得數據源,然后循環顯示集合中的每個元素。
對應的源代碼看起來是這樣:
運行結果在IE中顯示如下:
類似地,您可以自己“畫”出列藝術家,列專輯的頁面,然后將它們組合起來。這些JSP頁面僅僅是通過簡單的鼠標拖放和屬性設置完成的,真是太棒了!
3.使用Java Page Flow
Java Page Flow是完全基于開源的Struts模型,但提供了更強的易用性和自動化的狀態管理,支持數據綁定,擁有更強大的動作和異常處理。下一步,我們將創建一個名為FileUploadController的頁面流,完成一個文件上傳的功能。
新建頁面流fileUpload,Workshop自動為我們創建了一個FileUploadController.jpf,這個jpf作為控制器,我們再添加一個uploadFile的Action,包含一個名為“UploadForm”的ActionForm,3個JSP文件,然后設置導航如下:
詳細代碼請下載源代碼包。
使用Java Page Flow的最大的優點是完全的MVC架構,有清晰的導航模型,并且配合NetUI標簽,實現起來更為簡單。
以上討論了基本的瀏覽頁面,下面我們要實現用戶登陸的功能和針對管理員的管理功能。
實現用戶登陸
為了跟蹤用戶,我們創建一個AccountManageBD的業務代理類,并將它放在Session中,封裝所有與用戶帳號相關的操作。幾個關鍵的方法如下:
String getName() // 返回用戶登陸名
boolean isAdmin() // 是否是管理員
void login(String username, String password) // 登陸系統
我們在MusicSessionListener中監聽Session事件,以便創建和銷毀與Session關聯的AccountManageBD對象。然后在/WEB-INF/web.xml中注冊MusicSessionListener。
這樣,任何時候均可在JSP中調用
AccountManageBD account = (accountManageBD)
session.getAttribute(AccountManageBD.ACCOUNT);
來訪問用戶當前登陸信息。
創建一個登陸頁面流LoginController.jpf實現登陸頁面:
類似的,一個registerUser頁面流實現用戶注冊功能:
實現管理功能
為了能使管理員登陸后管理站點,我們把所有的管理頁面放在目錄{MusicWeb}/admin/下,為了防止未授權用戶訪問任何/admin/下的任何頁面,我們準備一個過濾器AdminFilter,將所有未授權的操作重定向到/login/LoginController.jpf中。核心代碼如下:
修改/WEB-INF/web.xml,添加過濾器申明:
最后,你只需要用Dreamwaver之類的網頁制作軟件強化界面,然后把這些JSP及頁面流組織起來,即可發布您的JetMusic站點。
安全
正如你所看到的,我們的安全是通過Web層的登陸驗證實現的,在邏輯層并未使用基于角色的驗證邏輯,對于我們現在的站點來說,可能已經足夠了。不過,如果您打算將來向用戶提供付費下載,付費收聽的業務,那么就需要切實保證系統的安全性。通常,不建議您自己開發安全邏輯,因為開發防攻擊的安全邏輯本身就不是一個簡單的任務,此外,自定義的安全邏輯可能會導致較低的可重用性。
從Java 1.4和J2EE 1.3開始,Java驗證與授權服務JAAS(Java Authentication and Authorization Service)就被引入到核心包中。JAAS實現了驗證和授權兩類服務,驗證服務能夠可靠并安全地確定目前是誰在執行代碼,在我們的這個應用中,只需要用到JAAS的驗證服務。以下是JAAS驗證的一般步驟:
1.創建一個LoginContext實例。
2.指定LoginContext的配置文件。
3.加載指定的LoginModule。
4.客戶端調用LoginContext的login方法。
5.如果登陸成功,就會將一個通過身份驗證的Principal和Subject聯系起來。
6.LoginContext將通過身份驗證的Subject返回客戶機。
在WebLogic中,缺省的用戶,組和角色都是在XML配置文件中指定的,我們只需要提供一個登陸頁面,包含一個名為j_security_check的Form,一個名為j_username的TextBox和一個名為j_password的PasswordBox,然后在/WEB-INF/web.xml中配置即可使用WebLogic默認的JAAS身份驗證。但是,這些用戶是靜態的,無法滿足我們使用數據庫動態管理用戶的需求。好在JAAS本身就是一個“可插拔”的模塊,我們可以輕易地使用自定義的安全模塊實現驗證功能。
要在WebLogic中實現自定義的安全驗證,需要提供一個Security Provider,包括自定義的Authentication Provider,Login Module,Identity Assertion,Principal Validation Provider,Role Mapping Provider,看上去有點復雜,不過,你可以從BEA站點下載一個示例代碼:
http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp
然后修改相應的實現類,配置部署,即可實現自定義的JAAS驗證。
使用JAAS驗證的好處是,驗證邏輯從頁面中分離,對頁面的限制訪問是通過/WEB-INF/web.xml中的配置指定的,無需自定義過濾器;在調用EJB時,Web容器能隱含的把Principal傳遞給EJB容器,從而和EJB的安全限制聯系起來。
在我們的JetMusic站點中,目前僅僅使用Session+過濾器的機制,也許可以考慮在下一個版本中加入JAAS驗證,以便更清晰地劃分表示層邏輯和安全邏輯。
部署
可以直接在Workshop中啟動WebLogic Server,即可自動完成部署。如果部署成功,WebLogic不會輸出提示信息,如果部署失敗,會提示異常信息。
常見的異常有:
JDBC連接出錯:沒有啟動數據庫,或者沒有找到對應JNDI名稱的數據庫。你需要檢查JNDI設置和數據庫是否已經啟動。
INSERT語句出錯:這是創建實體Bean時容器的INSERT語句出錯。如果指定了automatic-key-generation,第一次部署時WebLogic自動創建的主鍵為INT類型,但并不是AUTOINCREASE,需要打開數據庫,手動更改主鍵設置,或者,您可以直接下載示例數據庫,然后導入到SQL Server2000即可。
總結
我們已經利用J2EE技術在WebLogic Server上基本建立起了一個健壯的,可擴展的JetMusic音樂站點,其中涉及到EJB,JSP,Servlets,NetUI,Java Page Flow,JAAS以及幾個常用的EJB設計模式等。相信您能從這個小小的Web應用中看到J2EE體系強大的功能。由于時間倉促,水平有限,文中不免會有一些錯誤,懇請讀者指正。最后希望本文能夠給您帶來一點收獲。最后申明:如果您使用JetMusic系統在網上發布音樂,所引起的一切版權糾紛本文作者概不負責。
源代碼下載
JetMusic站點的全部源代碼(Workshop 8.1工程)和示例數據庫:
http://javap2p.nease.net/src/jetmusic.zip
參考
Sun J2EE Tutorial,http://java.sun.com/j2ee/tutorial/1_3-fcs/index.html
EJB設計模式,[美]Floyd Marinescu
EJB 2.0企業級應用程序開發,[美]Chuck Cavaness,Brian Keeton
BEA WebLogic Server寶典,[美]Joe Zuffoletto
WebLogic Workshop Help,http://e-docs.bea.com/workshop/docs81/doc/en/core/index.html
關于作者
廖雪峰(dev2dev論壇ID:xuefengl)
北京郵電大學在校本科生,信息工程專業,對J2EE/J2ME開發有濃厚興趣。