Java EE是個相當復雜的東西,被很多開發者們視為龐然大物。在下面的文章中,javaonejcy探討了如何簡化Java EE開發中不必要的復雜,並給出一個不使用任何框架的架構模型。
你可以說可愛的PHP ,可愛的ror ,可愛的Python ,甚至可愛的.Net ,但是Java EE ?他太復雜了。相比其他兩種技術,Java EE 的技術體系更全面、更規整也更復雜,他的復雜性也讓很多廠商望而止步,寧可選擇簡單甚至簡陋的PHP ,這充分說明快速開發是這個時代最迫切的需求。
Java EE 的servlet 、Javabean 、jdbc 規范給了我們組件和容器的唯一標准,而更高級的支持,JSf 、jdo 規范卻沒能給予我們唯一的框架級標准,他們被認可的程度遠低於相同領域的開源框架。盡管開源社區給了我們最豐富的選擇,但是相比.Net 、PHP 、ror 的全棧式服務,Java EE 開發者必須DIY 。DIY 不但需要時間而且需要冒險,這種發燒友做的事情是企業所不願意做的。一段時間以來,公司Java EE 方向的招聘幾乎清一色的要求struts 、spring 、hibernate 這幾種主流框架的能力就是一種證明。
Java EE 的開發往往避免不了配置之旅,盡管很多框架都有自動生成工具,但是,面對一個中型項目,仍然容易造成混亂。配置使你無論在開發、測試、集成還是維護的時都要配置代碼兩頭看。配置給了框架一個注入服務的切入點,但是對人並無優雅可言。ror 給了我們啟發,盡管企業開發是復雜的,但是大多數的需求都是通用的,事實證明,ror 把這部分通用性用約定的方式抽象得很好。其實Java EE 並不缺乏約定,因為他本身就是建立於一系列規范的基礎之上,而規范就是約定。所以,Java EE 實際上有機會成為相對簡潔的開發技術,只不過由於種種原因,這種規范並未出現。
在眾多的Java EE 開發框架中,struts+spring+hibernate 有著黃金組合的美譽,用的人多,會的人多,就算是沒出校門的學生,也知道學會ssh 的重要性。但是學會和學懂是兩碼事,對於一個中型項目,ssh 就成了一柄雙刃劍,需要由高水平的設計師引路,才能披荊斬棘。spring+hibernate 給了設計者廣闊的空間,而設計需要因項目的前進而演進,如果你的項目進度緊張,人手不足,設計質量就難以保障,給系統帶來隱患。
“任何優秀的語言,都可以幫助開發者寫出優秀的代碼,但不能阻止開發者寫出糟糕的代碼”。在這一點上,無論是Java EE ,.Net ,ror ,PHP 都不會例外。而開發框架就像是“一間有很多屋梁的房子”,“框架的強大之處不是他能讓你做什麼,而是他不能讓你做什麼”,其實如同語言一樣,框架雖然可以給予開發一定程度的規范指導,但是這種指導仍然是有限的,這真應了那句老話:事在人為。
本文試圖探討如何簡化Java EE 開發中不必要的復雜,並給出的是一個不使用任何框架的架構模型,讓我們看看僅僅通過用編碼約定,結構設計和使用方式的組合能不能滿足項目開發的主要需求— 短期培訓,降低隱患和快速開發。
問題的源頭
應用軟件開發是復雜的,但其基本模型至為簡單,請求-處理-響應。對應於軟件的層次結構就是:請求-Cortrol (C );處理-Model (M );響應-VIEw (V )。在早期的Java EE 應用中,servlet 負責C ,javabean 和jdbc 在M ,JSp 是V 。這些就是Java EE 的基礎設施,他們職責劃分的方式被稱為JSP Model2 ,已經可以滿足web 開發的基本需要,Java EE 的開發始終都圍繞著這幾項主要技術,框架也不例外。以下的內容,將從這些技術的應用與不足說起,然後介紹主流框架的解決方案,之後再介紹我們不用框架的處理方式。
(C) 選擇控制器
基礎規范的不足
任何web 應用,處理請求之後返回響應是必須的環節,如果編碼規范,傳統的響應就是轉向到某個頁面,servlet 處理轉向有兩種方式,其中request 轉向隱藏著重復提交的問題,response 重定向帶來參數傳遞的編碼解碼的問題,同時眾多的轉向地址直接寫在servlet 中也十分不雅,另外,JSp 和javabean 有一種出色的關聯技術,就是在JSP 裡可以把來自請求表單的數據自動拼裝到Javabean 中。糟糕的是,這麼有用的技術卻無法在servlet 中使用,所以Model2 缺乏對表單數據的自動轉換處理。servlet 有這些不足很好理解,因為servlet 畢竟是較早出現的技術,他的職責只是將(http )請求轉化為面向對象的視圖和輸出響應而已,由於他是低階組件,所以部分功能的缺失是正常的。不過這就讓servlet 成為了Model2 最薄弱的一環。
開發框架的解決方案
由於以上需求是共性的,所以編寫一個通用框架就成為了很多人努力的事情,struts 很快推出並且很快流行。我們先來看一看struts 的特性:
前端控制器: struts 使用一個servlet 作為前端控制器,所有請求先經過這裡,再分派給配置指定的action (這裡是指行為,而不是具體的Action ),意圖是以一個中間層將視圖層和控制層解耦,這種思路帶來了三種可能的好處:1 視圖和控制分離,所以可以選擇不同的視圖技術,比如視圖模板既可以用JSP ,也可以用Volecity 、FreeMarker ;2 可以對所有請求預處理,和後處理(webwork );3 可以將響應的轉向地址管理起來。前端控制器也存在一種直接的不足:配置繁瑣。
ActionForm : struts 主要是一個控制層框架,所以他並不意圖深入到模型層,ActionForm 是一種無奈的選擇,盡管提供了表單數據到javabean 的轉換,但是遺憾的是這個javabean 並不能直接使用,還要手工的轉換為模型Javabean ,使得ActionForm 的位置有些尴尬。
國際化支持、標簽庫和全局異常: 國際化和標簽庫都是struts 的亮點,不過全局異常作用有限。
我們的選擇
Java EE 的控制器必然是一個servlet ,我們也不能例外,因為我們必須要運行在servlet 容器之中。不過,我們選擇的是servlet 的演進版本-JSp 。別誤會,我們並不是要退回到JSP Model1 。一個典型的示例是,如果我有一個員工信息錄入的功能點,那麼為了實現這個功能,我可以建立下面兩個文件:
worker_input.JSP
worker_inputOper.JSP
worker_input.jsp 裡不寫控制代碼,worker_inuptOper.jsp 裡也不寫視圖代碼,這種用法實際是JSP Model1 和JSP Model2 的綜合體。這樣做最大的好處就是,免去配置的煩惱,但是等等.. 前端控制器呢?我們的中間層呢?
考慮一下,你有一個企業信息的表單,表單中有一個企業名稱域,對這個域的要求是不能在已有企業中重名,域旁邊有一個按鈕,業務員可以通過點擊這個按鈕獲得錄入企業名稱是否重復的提示。如果是傳統方式,點擊按鈕將導致一個頁面提交,如果用struts ,將要配置這個action 處理之後轉向的URL 地址,這就是傳統web 應用的響應方式- 基於URL 地址的頁面導航。
web2.0 來了,ajax 來了,異步請求的出現徹底顛覆了傳統的web 交互模型。對於AJax 應用而言,服務器端返回響應只需要out.print ,請求從哪來,回哪去,轉向(如果需要)和更新視圖的任務都交給了客戶端腳本,也就是說,基於異步交互模式的web 應用,根本就沒有需要配置的result URL 路徑。這樣,頁面導航的問題就自動解決了。而對於預處理,我們可以用filter 替代。所以,我們完全可以和前端控制器說:再見。
由於客戶端技術的趨勢,在webappdemo 中我們將全面使用AJax 。也許你會說,如果客戶端浏覽器禁用腳本呢?這個擔心在如今已經沒有必要,你可以訪問開心或者當當,看看禁用腳本他們能不能工作。時代在進步,富客戶RIA 是必然的選擇。
使用JSP 作為控制器,還使我們得到了另一個關鍵的特性,那就是從form 表單數據到javabean 的自動創建和輸入,使Javabean 本身既是模型也是DTO ,再也不必象ActionForm 那樣還要手工轉換。這裡還有一個隱含的好處,就是強制統一了表單域名和模型屬性名,不然,有可能出現這樣的情況:表單域:child_center ;模型屬性:branch 。以下是worker_inputOper.JSP 的寫法:
JSP代碼
- < JSP:useBean id="worker" class="webappdemo.worker.entity.Worker" scope="page"/>
- < JSP:setProperty name="worker" property="*"/>
- < %
- response.setContentType("text/x-JSon;charset=UTF-8");
- response.setHeader("Cache-Control", "no-cache");
- String method = request.getParameter("method");
- if("save".equals(method)){
- EntityService es = new EntityService();
- Message m = es.add(worker);
- out.print(new JSONObject().put(m.isSucceed()?"succeed":"error", m.getMessage()));
- return;
- }
- %>
可以看出,只需將實體類名引入標簽,我們就可以獲得自動拼裝的Worker 對象。對於復雜對象或復合對象,由於request 裡同樣有我們需要的所有請求參數,所以你可以在自動創建的Javabean 基礎上修改部分屬性,以符合業務需要。
代碼還展示了基於“method ”的用法,這只是一個字符串,用來告訴oper JSp 要用哪個方法來處理請求,這類似於ror 控制器內部定義的方法以及struts 的DispatchAction 但比他更靈活,變通的解決了JSP 的請求不能直接面向方法的不足。
在調用服務處理請求之後,worker_inputOper.JSP 將處理結果out.print 回客戶端,這句代碼的意思是新建一個JSON 對象,將處理結果添加進去,然後輸出這個對象,方便客戶端js 腳本解析。JSON 對象可以增加多個處理結果,只要他們的key 不同就可以。在實際應用中,往往返回處理消息,或者Html 視圖的字符串。最後別忘了return; 否則程序仍然會向下進行。
如果你的項目需要國際化,我們可以使用fmt 標簽,而對於反饋消息的國際化,我們也許就需要建立一個全局MessageSource 對象了,這個問題在webappdemo 中沒有涉及,因為筆者認為這不是普遍需求。
對於異常處理,其實JSP 已經提供了簡單的機制,我們可以在web.XML 中配置:
XML代碼
- < error-page>
- < error-code>404< /error-code>
- < location>/404.JSP< /location>
- < /error-page>
- < error-page>
- < error-code>500< /error-code>
- < location>/500.JSP< /location>
- < /error-page>
這種簡單的處理其實正是我們需要的全部,因為筆者認為,web 應用的系統錯誤和Java 的異常沒有區別,即檢測錯誤和運行時錯誤。在web2.0 時代,所有的錯誤都應該被捕獲,並且把內容經處理後在用戶輸入位置反饋給用戶,而不應該重新定向。運行時錯誤屬於系統bug ,是需要修復的代碼級錯誤,這種錯誤是真正的“意外”,所以我們用定制的錯誤頁面反饋給用戶就可以了。
綜上所述,我們用AJax+JSp+ 控制JSP 的方式代替了servlet 或者action ,擺脫了前端控制器,用模型javabean 代替了過程Javabean ActionForm ,這些使用方式使我們不需要配置即可以開發應用程序,除了AJax 是相對新概念外不需要額外學習框架技術也是他的優點。
(M)ORM 可以做什麼
基礎規范的不足
jdbc 是java 應用程序數據持久化的基礎,也是眾多數據庫廠商與Java 的接口。直接使用jdbc 編寫代碼非常繁瑣,比如數據庫資源的獲得和釋放,異常捕獲和事務處理等等,重復代碼多是他的一個特點。另外,不同的數據庫,在數據類型,主鍵類型還是sql 語句都和SQL 標准小有出入,所以如何使應用程序可以在不同數據庫平台方便的遷移,也是個問題。
開發框架的解決方案
spring 和hibernate 的出現使情況大為好轉,spring 面向切面管理事務, hibernate 自動ORM 可以大大簡化開發,spring 和hibernate 都有.Net 的版本,這證明了他們的成功。但是“用好hibernate ,需要扎實的掌握關系模型和SQL ”,同時對面向對象設計和hibernate 自身的運行機制也要有非常清晰的認識,只有這種水平才能發揮hibernate 的威力,降低hibernate 帶來的風險。所以,在合適的層面上配置好spring 的事務管理,設計好對象模型,把對hibernate 的直接使用控制在一定范圍內是設計者要解決的基本問題。如果設計不佳,或者直接交給初出校門的開發者去用,那這種組合就會變成洪水猛獸,同時也不利於團隊成員的成長。
我們的選擇
如果只有jdbc ,我們的系統也可以工作,只不過要寫很多重復和機械的代碼,通過框架的ORM 的映射,可以將數據庫表的數據自動填入Javabean ,這節省了勞動力,也使系統結構自然清晰。如果不用ORM 工具,我們能不能通過某種形式來模擬他的行為呢?我們可以創建這樣一個接口:
Java代碼
- public interface IOper {
- boolean load(Connection connect) throws SQLException;
- boolean add(Connection connect) throws SQLException;
- boolean update(Connection connect) throws SQLException;
- boolean delete(Connection connect) throws SQLException;
- }
在接口中定義 CRUD 方法。返回類型為 boolean 而非影響的行數,意圖是對象內部的操作可能是復雜的多步驟的,所以對他的上層應用來說,知道結果成功與否就可以了。接下來在他的實現類裡可以這麼寫:
Java代碼
- public class Worker implements IOper {
- // FIElds
- private Integer workerId;
- private String workerName;
- private String logonName;
- private String logonPwd;
- /*
- * 對於允許為空的字段賦予初始值,避免頁面顯示null
- */
- private String mobile = "";
- private String email = "";
- private String remark = "";
- private String isFreeze = "0";
- // Constructors
- /** default constructor */
- // Property Accessors
- public boolean add(Connection connect) throws SQLException {
- SQLBuffer sql = new SQLBuffer();
- sql.segment("insert into worker (worker_name,logon_name,logon_pwd,");
- sql.segment("mobile,email,remark,is_freeze) values (");
- sql.value(this.workerName);
- sql.comma();
- sql.value(this.logonName);
- sql.comma();
- sql.value(this.logonPwd);
- sql.comma();
- sql.value(this.mobile);
- sql.comma();
- sql.value(this.email);
- sql.comma();
- sql.value(this.remark);
- sql.comma();
- sql.value(this.isFreeze);
- sql.segment(")");
- return Proxy.update(connect, sql) == 1;
- }
- public boolean delete(Connection connect) throws SQLException {
- // 凍結用戶
- SQLBuffer sql = new SQLBuffer();
- sql.segment("update worker set is_isfreeze = ");
- this.isFreeze = "1";
- sql.value(this.isFreeze);
- sql.segment(" where worker_id = ");
- sql.value(this.workerId);
- return Proxy.update(connect, sql) == 1;
- }
- public boolean load(Connection connect) throws SQLException {
- SQLBuffer sql = new SQLBuffer(
- "select worker_name,logon_name,logon_pwd,mobile,email,remark,is_freeze from worker");
- sql.segment(" where worker_id = ");
- sql.value(this.workerId);
- MapRow mr = Proxy.getMapRow(connect, sql);
- if (mr == null) {
- return false;
- }
- this.workerName = mr.getString("worker_name");
- this.logonName = mr.getString("logon_name");
- this.logonPwd = mr.getString("logon_pwd");
- this.mobile = mr.getString("mobile");
- this.email = mr.getString("email");
- this.remark = mr.getString("remark");
- this.isFreeze = mr.getString("is_freeze");
- return true;
- }
- public boolean update(Connection connect) throws SQLException {
- SQLBuffer sql = new SQLBuffer();
- sql.segment("update worker set worker_name = ");
- sql.value(this.workerName);
- sql.segment(", logon_name = ");
- sql.value(this.logonName);
- sql.segment(", logon_pwd = ");
- sql.value(this.logonPwd);
- sql.segment(", mobile = ");
- sql.value(this.mobile);
- sql.segment(", email = ");
- sql.value(this.email);
- sql.segment(", remark = ");
- sql.value(this.remark);
- sql.segment(", is_freeze = ");
- sql.value(this.isFreeze);
- sql.segment(" where worker_id = ");
- sql.value(this.workerId);
- return Proxy.update(connect, sql) == 1;
- }
- }
實體 Javabean 通過實現 IOper 接口,負責對自身數據的操作。盡管這種實現方式等於是使模型成為了富血模型,但其實我們仍然可以把這種模型認為是貧血的,因為他沒有業務邏輯,只是模擬了 ORM 的行為。 如果對象關系有包含和聚合,我們同樣也可以通過類似 hibernate 的行為方式來實現,比如懶加載。以上的代碼使用了筆者所用的 API ,由於操作都在內部,所以換成直接用 jdbc 也是一樣的。在實際應用中, load 方法有些單薄,因為有的查詢需要一些附加條件,我們可以通過增加一個類屬性來達到這個目的:
String condition;
如果設置了條件,我們就拼接給定的查詢條件取得結果,不過結果只能是一條,這是模型結構所決定的。另外Connection 對象是每個方法的必須的參數,意圖是在實際業務操作中,單一的操作往往是不存在的,所以,總是由外部傳遞資源和釋放資源。但是,在每個調用的地方都要寫重復的try catch 以及獲得和釋放 連接的代碼豈不是很無聊?那麼,我們可以用一個服務類包裝他,這就體現了IOper 接口的用處了:
Java代碼
- public class EntityService {
- public Message add(IOper io) {
- try {
- Connection connect = Proxy.getConnect();
- connect.setAutoCommit(false);
- // 增加
- if (!io.add(connect)) {
- throw new Exception("增加操作失敗");
- }
- // 其他操作
- connect.commit();
- return new Message(true);
- } catch (Exception e) {
- Proxy.rollbackConnect();
- e.printStackTrace();
- return new Message(e);
- } finally {
- Proxy.closeConnect();
- }
- }
- public Message update(IOper io) {
- try {
- Connection connect = Proxy.getConnect();
- connect.setAutoCommit(false);
- // 修改
- if (!io.update(connect)) {
- throw new Exception("修改操作失敗");
- }
- // 其他操作
- connect.commit();
- return new Message(true);
- } catch (Exception e) {
- Proxy.rollbackConnect();
- e.printStackTrace();
- return new Message(e);
- } finally {
- Proxy.closeConnect();
- }
- }
- public Message delete(IOper io) {
- try {
- Connection connect = Proxy.getConnect();
- connect.setAutoCommit(false);
- // 刪除
- if (!io.delete(connect)) {
- throw new Exception("刪除操作失敗");
- }
- // 其他操作
- connect.commit();
- return new Message(true);
- } catch (Exception e) {
- Proxy.rollbackConnect();
- e.printStackTrace();
- return new Message(e);
- } finally {
- Proxy.closeConnect();
- }
- }
- public Message load(IOper io) {
- try {
- Connection connect = Proxy.getConnect();
- connect.setAutoCommit(false);
- // 載入
- if (!io.load(connect)) {
- throw new Exception("載入操作失敗");
- }
- // 其他操作
- connect.commit();
- return new Message(true);
- } catch (Exception e) {
- Proxy.rollbackConnect();
- e.printStackTrace();
- return new Message(e);
- } finally {
- Proxy.closeConnect();
- }
- }
- }
從EntityService 的代碼中看到,try catch 應該總是出現在服務層,只有一個獨立的服務才有必要保證一個完整的事務。如果你要在CUD 操作後記錄業務日志,那麼你可以寫在“其他操作”的位置。由於對外界信息的依賴,比如用戶信息,功能ID 等等,在實際應用中需要更復雜的設計。至此,我們完成了對ORM 行為的模擬,在調用的地方,我們只需要:
Java代碼
- EntityService es = new EntityService();
- Message m = es.add(worker);
盡管方法內部的代碼仍然是繁瑣的,但是我們由於把這些代碼的位置聚焦於較少的地方而降低了繁瑣程度,同時也獲得了良好的結構。對數據庫的操作更為直觀是“手寫”代碼的優點,當然,我們還可以選擇ibatis ,不過結構就不會是這樣的了。同時筆者認為sql 代碼總是和程序內部息息相關,配置在外部看起來並不一定方便吧。
對於數據庫遷移問題,hibernate 很大程度的解決了這個問題,不過企業開發中遷移數據的需求並不多,而且數據庫遷移不是使用某個工具或者規范(jdbc ,jdo )就可以完全解決的,對於大型復雜應用,遷移數據庫需要付出巨大的努力,所以實際上也沒有人這麼做。
(V) 客戶端MVC
關於客戶端應用的說明沒有內部分段,因為Java EE 技術(暫不談Javafx )本身就和客戶端沒有太大關系,所以也不存在不足的問題,這裡只是意圖糾正一些系統中不良的編碼習慣。
jsf 是Java EE UI 框架規范,前端控制器、事件模型和UI 組件是他的主要組成部分,但是實際開發中JSf 標簽非常繁瑣,並且只有客戶端明確禁用腳本或者其他組件(flex-Flash ,Javafx 甚至silverlight )時,jsf 才是最有價值的,糟糕(幸運)的是目前幾乎沒有客戶端浏覽器不支持以上腳本或插件,所以JSf 的價值大大降低了。
JSP 是Java EE 視圖組件,也是JSf UI 框架的基礎之一,他實際上就是一個環境寬松的視圖模板,盡管這種寬松貌似不利於軟件分層,但是比起其他模板技術,他至少有一個優點,就是非常象ASP ,這使他更容易為開發者所接受。在web2.0 的時代,如果你的項目不打算用flex-Flash 或者javafx ,那麼JSP 就是你最佳的視圖載體,你可以把javacript 、CSS 、Html 、XML 、JSon 還有java 代碼都放在這裡,為了實現AJax 的交互邏輯,你可能要寫大段的Javascript ,沒關系,JSP 都能承受,盡管,這些東西放在一起看上去就像是一個老鼠窩。
AJax in action 的作者提出了客戶端MVC 的概念,不過別誤會,這並不是ext 或者flex 的開發模式,而只是一種觀念。html 是由dom 樹建立的視圖結構,所以是M ,CSS 給Html 結構添加效果,所以是V ,Javascript 控制顯示邏輯,所是C ,這裡指的客戶端MVC 就是把這三種語言代碼分別建立單獨的文件,在Html 頁面中引用外部的CSS 和JS 。同樣拿員工信息錄入舉例,我們可以建立如下幾個文件:
worker_input.JSP
worker_inptu.JS
style.CSS
可以看到,CSS 文件我們並沒有用功能方式命名,因為樣式表往往可以被整個系統共用,也就是說,我們只要記得不在Html 裡寫CSS ,js 代碼,並單獨建立js 文件就可以了。不同種類代碼分開建立文件是提高代碼可讀性的關鍵步驟,尤其是在客戶端程序愈發復雜的今天。worker_input.JS 是這樣寫的:
JS代碼
- // 保存
- function save(){
- if(check()){
- AJaxUtil.sendForm(document.forms[0], "method=save", function () {
- if (this.result.succeed) {
- document.forms[0].reset();
- messageapp.printMessage(this.result.succeed);
- } else if (this.result.error) {
- messageapp.printMessage(this.result.error);
- }
- }, true);
- }
- }
這個文件只定義了一個save 方法,這個方法內部發起了AJax 方式的異步請求,如果返回成功,則重置表單,並更新輸出信息到指定位置,反之則僅更新輸出信息。你會注意到,我們返回的JSON 對象起到了作用。
問題:如果我的JSP 代碼裡出現了這樣的情況,算是違背客戶端MVC 的原則嗎?
Html代碼
- < tr>
- < td colspan="2">
- < input type="button" value="保 存" onclick="save();"/>
- < input type="reset" value="重 置" />
- < input type="button" value="返 回" onclick="window.history.go(-1);" />
- < /td>
- < /tr>
以上代碼片段中有簡短的 JS 代碼和方法引用,這種情況,筆者認為是可以的,項目開發沒必要追去絕對的完美,只要達到結構清晰的目的就行了,我們要的是高質量的產品,而不是高質量的理論。
問題:如果我使用了類似 ror 的 JavaScriptHelper 那樣的腳本標簽庫,生成的腳本與 Html 代碼混在一起,這樣算不算違背客戶端 MVC 原則呢?很顯然,也不算,因為對於輸出前的 JSP 代碼模板,程序結構是清晰的。
(F) 結構和細節
關於MVC 各部分的介紹,再讓我們來看看整體結構,假設我們是在一個eclipse 工程中,打開WebRoot 目錄,如果忽略META-INF 和WEB-INF 目錄的話,我們的示例目錄是這樣的:
module
404.JSP
500.JSP
index.htm
其中module 就是我們應用所有的視圖,由於我們對於JSP 的使用方式,還包括所有的控制器。將應用程序都放在一個目錄下是為了方便今後使用filter 對目錄進行保護,或者進行某些其他預處理。打開module 目錄,展現的結構是這樣的:
- common
- worker
一個common 目錄,存放所有共用程序文件,一個worker 目錄存放關於員工這個實體的功能文件。實際上你的結構也可能是sys/worker 或者center/sys/worker 等等,就要看實際的應用需求了,總之,通過目錄劃分功能模塊,以正確的樹形結構描述他是沒錯的。我們再來打開common 目錄:
- CSS
- img
- JS
- import_page.def
前三個目錄分別存放的是常用文件,而最後一個是文件,這個文件將用這種方式添加到每個視圖JSP 頭,在< head>< /head> 間加入:
JSP代碼
- < %@ include file=”../common/import_page.def”%>
這裡使用了相對路徑,增加這個文件是為了方便的引用其他css 和js 庫文件。使用這種方式可以避免在部署的階段因合並JS 或進行CSS 、JS 庫文件的版本變更而帶來的麻煩。
再來看看Java src 的目錄結構:
- webappdeom
- common
- worker
- datasource.XML
- log4j.propertIEs
除了根目錄,與WebRoot 下的結構是一致的。兩個文件:datasource.XML 是數據源配置文件,也是筆者demo 中使用的API 所需要的,log4j.propertIEs 是log4j 的屬性文件。另外,系統默認使用hsql 數據庫,如果要正常啟動,需要改動web.XML 中的一個配置項,具體位置有注釋說明。
再具體的結構和代碼這裡就不多講了,如果感興趣,可以下載webappdemo 看看,裡面的注釋比較完整。不過在demo 裡還有一些隱藏問題,以及可以擴展的思路,不知道你能不能找出來。
尾聲
老話說,條條大路通羅馬,在開源的java 的世界更是如此,我們可以有自己的想法並且拿出來分享,這就是開源最大的魅力,也是資源的寶庫。盡管sun 被收購,但是java 仍然會是開源最響亮的聲音,Javafx 是筆者目前最關注的技術,希望他能一路走好。