Ruby on Rails 是一個相對較新的 Web 應用程序框架,構建在 Ruby 語言之上。它被宣傳為現有企業框架的一個替代,而它的目標,簡而言之,就是讓生活,至少是 Web 開發方面的生活,變得更輕松。在本文中,Aaron Rustad 將對 Rails 和傳統的 J2EE 框架在架構上的一些關鍵特性進行比較。
Ruby on Rails 是一個 Web 應用程序框架,它的目標是為應用程序開發提供一條易行道。實際上,框架的支持者們聲稱 Ruby on Rails 開發人員的生產率最多是使用傳統 J2EE 框架的 10 倍。(請閱讀“Rolling with Ruby on Rails”一文,以獲得關于這一聲明的更多內容;請參閱
參考資料
)。雖然這句話造成了 Rails 和 J2EE 社區相當大的爭議,但爭論中卻很少談及如何比較 Rails 和 J2EE 架構。本文將使用企業應用程序中常見的開源工具,對 Rails 框架和典型的 J2EE 實現進行比較。
什么是 Ruby on Rails?
要想找到用一句話描述 Rails 的簡單說明,只需查看項目的
主頁
即可:
Rails 是一個用 Ruby 編寫的全棧的(full-stack)、開源的 Web 框架,可以使用它來輕松編寫實際的應用程序,所需的代碼也要比大多數框架花在處理 XML 上的代碼少。
雖然我不能保證框架確實會提供它所承諾的輕松快樂,但是上面這句話確實很好地總結了 Rails 的品質。全棧包括:Web 服務器、處理 HTTP 請求和響應的框架,以及方便地把數據持久存儲到關系數據庫的框架。Rails 通過消除復雜的 XML 配置文件,使用 Ruby 語言的動態性質,幫助把靜態類型語言中常見的許多重復代碼減少到最少,努力使開發工作變得更容易。
Rails 和典型的 J2EE Web 堆棧
圖 1 比較了 Rails 堆棧和典型的 J2EE Web 堆棧(包括 Tomcat servlet 容器、Struts Web 應用程序框架和 Hibernate 持久性框架)。
圖 1. Rails 和 J2EE 堆棧的比較?
?
可以看到,Rails 堆棧和構成普通的基于 J2EE 的 Web 應用程序的組件之間的基本區別很小。兩者都有用來執行應用程序代碼的容器;都有幫助分離應用程序的模型、視圖和控件的 MVC 框架;以及持久存儲數據的機制。
|
MVC 框架
模型-視圖-控制器(MVC)是應用時間相當長、應用面相當廣的一個設計模式。它源自 Smalltalk;如今,幾乎所有的 GUI 框架,包括 Web 和胖客戶端,都以該框架為基礎。MVC 有三個部分:模型,負責業務邏輯,包括應用程序狀態和將在這個狀態上執行的動作;視圖,用來渲染和向用戶呈現模型(在 Web 應用程序中,視圖一般渲染為 HTML);控制器,定義應用程序的行為。有關 MVC 模式的詳細解釋,請參閱
參考資料
。
|
|
前端控制器
Struts 的 ActionServlet
和 Rails 的 DispatchServlet
都是前端控制器模式的例子;所以,它們提供了相同的功能。它們接受 HTTP 請求,解析 URL,把請求的處理轉發給適當的動作。在 Struts 中,動作是擴展自 Action
的類;對于 Rails,動作是擴展自 ActionController
的類。兩個前端控制器之間的主要區別是它們如何決定處理具體請求的動作。
使用 Struts,開發人員需要把特定請求的映射外部化到 XML 配置文件中的 Action
類。當首次裝入 ActionServlet
時,它將解析這個文件,并準備接受請求。根據約定,以 .do
結束的請求被重定向到 ActionServlet
,由 ActionServlet 分派到適當的 Action
。
圖 2
的 XML 是一個典型的映射。它告訴 ActionServlet
把叫作 deleteOrder.do
的請求轉發到 controllers.order.DeleteOrderAction
作進一步處理。
Rails 采用了不同的方式。它沒有依賴配置文件把請求映射到某一個動作,而是根據請求的 URL 發現適當的動作。從圖 2 可以看到,URL http://localhost/order/delete/4
告訴 Rails 調用 OrderController
實例上的 delete
方法,并將 4
作為可用的實例變量。Rails 足夠聰明,知道 /order
將映射到文件 order_controller.rb 中定義的一個控制器類。如果在控制器中定義了 find
方法,那么只要用 find
替代 URL 中的 delete
,就可以調用這個方法。
圖 2. Rails 和 Struts 中的 URL 映射?
?
動作和模型
在 Rails 和 Struts 中,動作用來充當前端控制器和模型之間的橋梁。開發人員提供動作的現實,從而提供特定于應用程序的請求處理。前端控制器負責接受請求,并把請求傳遞到特定動作。圖 3 演示了 Rails 和 Struts 基本的動作層次結構。
圖 3. Rails 和 Struts 的動作層次結構
?
|
動作是模型還是控制器?
Action
和 ActionController 從技術上講是 MVC 模式的控制器的一部分,因為它們對客戶發起的事件進行響應。但是,在小型應用程序中,開發人員通常在這些類中對域或業務邏輯進行編碼,所以在這些情況下,也可以把它們看作是模型的一部分。最佳實踐建議:應當把域邏輯從控制器中抽象出來,放置在它自己的特定于域的類中。
|
|
Struts 要求開發人員擴展 Action
并覆蓋 execute()
,以處理請求。通常,每個 Action
類都提供了非常具體的工作單元。
圖 3
演示了三個特定動作:SaveOrderAction
、DeleteOrderAction
和 ListOrdersAction
。前端控制器將調用 execute()
方法,傳遞給它許多有用的對象,其中包括 HTTP 請求和響應對象。ActionForm
是一個類,它可以方便地向視圖來回傳輸并驗證與表單有關的輸入,ActionMapping
包含映射的配置信息,就像
圖 2
的 XML 所描述的那樣。
execute()
方法返回 ActionForward
對象,Struts 用這個對象來確定對請求繼續進行處理的組件。一般來說,這個組件是一個 JSP 頁面,但是 ActionForward
也能指向其他動作。開發人員必須清楚,Struts 創建的是 Action
的單一實例,并允許多個線程調用它的 execute()
。這使請求處理變得更快,因為框架處理每個請求時不用頻繁地創建新的 Action
實例。但是因為可以在多個線程之間共享單一對象,所以必須遵守適當的線程注意事項,因為其他線程可能會破壞在這個動作中保持狀態的實例變量。
在 Rails 中,必須擴展 ActionController::Base
,讓模型參與到請求處理中。Rails 沒有將 ActionController
的實例池化;相反,它為每個請求創建新的實例。雖然這對性能可能有負面影響,但是它可以讓開發變得更容易。開發人員不需要關注 Struts 中存在的線程問題,因此,會話、請求、標題和參數都可以作為 ActionController
的實例成員來進行訪問。ActionController
還是一個將特定域邏輯的所有處理組合在一起的合理場所。Struts 的 Action
類是細粒度的,它提供了非常具體的工作單元,而 Rails ActionController
則是粗粒度的,它將具體的工作單元模擬為一些方法。
清單 1
和
清單 2
分別演示了典型的 Struts 動作和典型的 Rails 動作
表 1 提供了對兩種方法的邏輯流程的比較,并演示了清單
1
和清單
2
的特定行中發生的事情。研究 DeleteOrderAction
的 execute()
方法和 OrderController
的 delete
方法,可以看出它們基本上是相同的。
表 1. execute() 和 delete 方法比較
步驟
|
Struts
|
Rails
|
框架調用動作
|
行 03:
execute()
|
行 07:
delete
|
從請求中檢索到的 ID
|
行 06-07:從請求對象中取出
|
行 08:從所有參數的實例哈希中取出
|
從數據庫刪除訂單記錄
|
行 09、14-24:調用 delete() 方法,用 Hibernate 刪除記錄
|
行 09:用 ActiveRecord 刪除記錄
|
重定向到列出剩余訂單
|
行 11:用 ActionMapping 對象查找將要轉發處理的下一個組件。
圖 2
中的 XML 映射顯示,success 將映射到 /listOrders ,這是另一個 Action ,負責查找剩余訂單,并以 JSP 的形式呈現它們
|
行 10:用將調用的下一動作的哈希來調用 redirect_to 方法;在這種情況下,它只是調用同一控制器的 list 方法
|
持久性框架
持久性框架 用來在應用程序層和數據庫之間來回移動數據。Hibernate 和 Rails 的持久性框架可以歸類為對象/關系映射(ORM)工具,這意味著它們接受數據的對象視圖,并將該視圖映射到關系數據庫內的表中。使用兩種框架的目的都是為了減少與關系數據庫有關的開發時間。但是,
圖 4
演示了兩者在設計和配置上的一些根本區別。
圖 4. Active Record 和 Hibernate 持久性框架的比較?
?
Hibernate
Hibernate 基于 Data Mapper 模式,在這種模式中,特定的映射器類 Session
負責在數據庫中持久存儲和檢索數據。Hibernate 可以持久存儲任何 Java 對象,只要這個對象符合 JavaBean 規范。XML 映射文件描述了如何將類映射到數據庫中具體的表,并描述了類與其他類的關系。
清單 3
顯示了 Hibernate 映射文件的一個實例。class
標簽把 Order
對象映射到 ORDERS
表,還有許多子標簽用于描述其屬性、ID 訂單名稱,以及同 models.Item
的一對多關系。
清單 4
顯示了 Order
類本身。
清單 3. Order.hbm.xml
...
01 <hibernate-mapping>
02 <class name="models.Order" table="ORDERS"
03 dynamic-update="true" dynamic-insert="false"
04 discriminator-value="null">
05
06 <id name="id" column="id" type="java.lang.Long"
07 unsaved-value="null">
08 <generator class="identity"/>
09 </id>
10
11 <set name="items" lazy="false" inverse="false"
12 cascade="none" sort="unsorted">
13 <key column="id"/>
14 <one-to-many class="models.Item"/>
15 </set>
16
17 <property name="name" type="java.lang.String"
18 update="true" insert="true"
19 access="property" column="name"/>
20 </class>
21 </hibernate-mapping>
|
清單 4. Order.java
01 public class Order {
02 private Set items;
03 private String name;
04 private Long id;
05
06 public Long getId() { return id;}
07
08 public void setId(Long id) { this.id = id;}
09
10 public Set getItems() { return items;}
11
12 public void setItems(Set items) { this.items = items; }
13
14 public String getName() { return name; }
15
16 public void setName(String name) { this.name = name; }
17 }
|
Active Record
|
反射和元編程
Wikipedia 中(請參閱
參考資料
)簡要地把 反射 定義為“程序在運行的時候檢查和修改其高級結構的能力”。在那里,還將 元編程 定義為“編寫那些能夠編寫和操作其他其他程序(或它們自己),將其他程序作為自己的數據的程序,或者編寫那些完成其他程序在運行時所做的部分工作的程序。”
以下代碼將實現反射:
01 obj = "some_string"
02 if obj.respond_to?('length'):
03 puts "obj length = #{obj.length}"
03 end
>> obj length = 5
|
這個代碼將實現元編程:
01 class SomeClass
02 end
03 newMethod = %q{def msg() puts "A message!" end}
04 SomeClass.class_eval(newMethod)
05 aClass = SomeClass.new
06 aClass.msg
>> A message!
|
|
|
Rails 的 ORM 框架叫作 Active Record,它基于同名的設計模式。Martin Fowler 將 Active Record 描述為“包裝數據庫表或視圖中數據行的對象,封裝數據庫訪問,在數據上添加域邏輯”。在 Rails 中,每個域對象都將擴展提供 CRUD 操作的 ActiveRecord::Base
。
與 Hibernate 一樣,Active Record 不需要映射文件;實際上,使用 Active Record 的開發人員不需要對 getter 或 setter、甚至類的屬性進行編碼。通過一些漂亮的詞匯分析,Active Record 能夠判斷出,Order
類將映射到數據庫中的 ORDERS
表。使用 Ruby 反射和元編程的組合,表的列可以變成對象的屬性。訪問器和調整器也添加了進來。
清單 5
顯示了 Order
類的完成后的代碼。在 Order
類體中有一行代碼定義了它與 Item
對象的關系。has_many
是一個靜態方法調用,符號 :items
是它的參數。ActiveRecord 用 :items
發現 Item
域對象,然后將這個 Item
對象映射回數據庫中的 ITEMS
表。
清單 5. order.rb
01 class Order < ActiveRecord::Base
02 has_many :items
03 end
|
像
清單 5
那樣編碼的 Order
類在運行時提供了一些類和實例方法。表 2 提供了可在 Order
上使用的操作和屬性的部分列表:
表 2. 在 Order 上可用的屬性和操作
類方法
|
實例方法
|
屬性
|
-
find(*args)
-
find_by_sql(sql)
-
exists?(id)
-
create(attributes)
-
update(id, attributes)
-
update_all(updates, conditions
-
delete(id)
-
delete_all(conditions)
-
...
|
-
add_items
-
build_to_items
-
create_in_items
-
find_all_in_items
-
find_in_items
-
has_items?
-
items
-
items=
-
items_count
-
remove_items
|
|
結束語
雖然 Ruby on Rails 是一個非常新、令人興奮的框架,并且在 Web 社區中已經引起了人們相當的興趣,但是它的核心架構仍然遵循在 J2EE 中發現的基本模式。開發把兩個框架分開的 Web 應用程序是一種合理的方法。Rails 更喜歡清楚的代碼而不是配置文件,而 Ruby 語言的動態性質在運行時生成了大部分管道 代碼。大多數 Rails 框架都是作為獨立項目創建的,而且應用程序開發能夠從一組同類組件受益。相比之下,典型的 J2EE 堆棧傾向于構建在通常獨立開發的最好的組件之上,常常用 XML 進行配置并將組件組合在一起。
那么,是否應該考慮對下一個 Web 應用程序使用 Rails 呢?嗯,為什么不呢?它是編寫得很好的組件堆棧,它們彼此之間工作得很好,并且基于行業接受的企業模式。Ruby 語言支持快速開發,并通過生產大多數應用程序管道來添加到框架。熟悉 Java 世界中的 MVC 和 ORM 框架的人們在用 Rails 表達自己的思想時沒有任何困難。
與 J2EE 一起分發會不會有利于 Rails?絕對不要。J2EE 是一個已經設置好的標準,有許多固定的實現,而且,最重要的是,它是一個經過驗證的技術。我建議您下載一份 Rails 的副本,并開始自己鉆研它。許多可用的教程都是介紹性的,這些教程可以讓您立即開始使用 Rails。再次聲明,我并不能保證您會通過使用 Rails 得到快樂,但是我敢打賭您會感到滿意。