Spring Roo 入門

工程 | Ben Alex | 2009 年 5 月 27 日 | ...

更新:“Spring Roo 簡介”部落格系列的第三篇現已釋出,詳細介紹了 Roo 的內部架構。

我得坦白一件事。雖然你們中的許多人知道我最近幾個月一直在忙著開發Spring Roo,但我還有一個獨立的專案,它還沒提交到 Subversion。另一個專案是規劃我們的婚禮,因為下個月我未婚妻和我將出國結婚。所以當我思考在這篇部落格文章中能向大家展示關於 Roo 的什麼內容時,我想到我應該藉此機會使用 Roo 構建我們婚禮的 RSVP(回覆)網站!所以今天我們將學習如何使用 Roo 構建一個婚禮 RSVP 網站,我的一些同事評論說這是一個追求工作與生活平衡的進取範例。:-)

進度更新

如果您錯過了 2009 年 5 月 1 日釋出的 Spring Roo 部落格系列的第一篇,簡而言之,我介紹了 SpringSource 新推出的開源生產力工具的願景,該工具旨在幫助那些希望快速構建符合最佳實踐的 Java Spring 應用程式的人們。正如許多試用過 alpha 版本的人所發現的,Spring Roo 提供了一種強大且易於使用的應用程式開發方法,其背後的許多動機都體現在第一個 Jira 問題 ROO-1 中(由 Spring 之父、SpringSource 執行長 Rod Johnson 記錄)。

今天我很高興宣佈我們剛剛釋出了Spring Roo 1.0.0.M1。此版本不僅包含大量修復、增強和 31% 的效能提升,還帶來了一系列令人興奮的新功能,包括電子郵件服務JMSSpring Web Flow、簡化的安裝以及自動化的 Selenium 支援。這些都建立在 alpha 版本中已有的許多功能之上,正如我在之前的部落格文章中提到的那樣。

除了開發第一個里程碑版本外,在過去的一個月裡,我們還建立了典型的開源專案公共專案基礎設施。我們現在擁有社群支援論壇、Jira 問題跟蹤、公共 Subversion 程式碼庫、FishEye 原始碼監控。過去一個月在 #roo Twitter 頻道上的一些評論包括“我很驚喜”、“喜歡它”、“自定義 roo 外掛非常容易”、“創新來了”、“第一個里程碑版本會很酷!”、“Roo 看起來很有趣且有效”、“非常棒的工具”、“非常酷”等等。我們也開始看到第一個社群貢獻的外掛,這強調了您可以輕鬆地使用自己的自定義功能擴充套件 Roo。

本月早些時候,我們還結束了社群專案命名競賽,“Spring Roo”輕鬆獲勝。總共收到了 952 張有效票,分佈在 Spring Roo (467)、Spring Boost (180)、Spring Spark (179)、Spring HyperDrive (64) 和 Spring Dart (62) 之間。感謝所有投票的人!

安裝

現在我們已將簡化的安裝新增到 Spring Roo 1.0.0.M1 中,安裝就像下載 ZIP 檔案、解壓,然後將“bin”目錄新增到您的路徑一樣簡單
  • Windows 使用者應開啟“控制面板”,然後依次單擊“系統”、“高階”、“環境變數”,雙擊“Path”條目。在路徑末尾追加“;C:\spring-roo-1.0.0.M1\bin”(或您安裝 Roo 的位置)。
  • *nix 使用者應建立指向 roo.sh shell 指令碼的符號連結。通常,諸如“sudo ln -s ~/spring-roo-1.0.0.M1/bin/roo.sh /usr/bin/roo”之類的命令即可奏效。
雖然您可以獨立使用 Spring Roo,但我還建議您下載 SpringSource Tool Suite (STS) 2.3.0 或更高版本。STS 是我們免費的基於 Eclipse 的 IDE,提供了許多簡化 Spring 應用程式開發的功能。例如,如果您下載 STS 2.3.0 或更高版本,您將獲得內建的 Spring Roo 支援,甚至無需安裝獨立的 Spring Roo shell。

本部落格將假設您正在使用獨立的 Spring Roo shell。除非另有說明,否則所有命令在標準 Spring Roo 和 STS 2.3.0 或更高版本中都以相同方式工作。主要區別之一是,在使用 Roo shell 時,您按 TAB 鍵獲取完成選項,而在 STS 中,TAB 鍵被 CTRL + SPACE 替代。我們使用 CTRL + SPACE 是因為它是 Eclipse 等基於 IDE 中用於完成的更傳統按鍵組合。

您還應驗證是否安裝了 Maven 2.0.9 或更高版本。雖然 Roo 本身不使用 Maven 並且可以在未安裝的情況下執行,但 Roo 建立的專案目前使用 Maven。此外,如果您安裝了早期 Roo alpha 版本之一,請務必刪除 ROO_HOME 變數。

應用程式需求

現在您已經安裝了 Roo,讓我們考慮一下我們的婚禮 RSVP 應用程式的需求。然後,我們將結合使用 Roo 和一些手動編碼來構建應用程式。對某些手動編碼的需求反映了 Roo 的一個基本理念:您仍然需要開發那些使您的應用程式與眾不同的部分。但是,您會看到 Roo 被設計為以一種完全透明、熟悉且支援雙向同步(round-trip supporting)的方式來實現手動編碼和定製

儘管有線上 RSVP,我們仍然寄出了紙質婚禮請柬。每張請柬的背面都有一個小小的“邀請碼”。這些碼不容易猜到,但很容易閱讀和輸入(不是 UUID!)。婚禮請柬的文字邀請賓客訪問我們的婚禮 RSVP 網站進行回覆。當他們訪問婚禮 RSVP 網站時,會要求賓客輸入他們的邀請碼。

賓客輸入邀請碼後,任何現有的 RSVP 記錄將被檢索並允許他們編輯。如果他們之前沒有回覆過,則會提供一個新的 RSVP 表單供他們填寫。表單只會簡單地詢問賓客有多少人參加,以及是否有任何特殊要求(例如飲食需求)。他們還會被要求輸入他們的電子郵件地址,如果提供了電子郵件地址,應用程式將傳送一份基於電子郵件的回覆確認。我們還會記錄他們回覆的日期和時間,如果他們多次更改回復,這將非常有用。

建立持久化專案

現在我們有了想構建的真實世界應用程式的想法,讓我們使用 Roo 來構建它。第一步是建立一個空目錄並載入 Roo
$ mkdir wedding
$ cd wedding
$ roo

如果您按照上述安裝說明操作,您應該會看到下方顯示的 Roo 標誌。如果您沒有看到類似的訊息,請返回並檢查您的安裝是否正確。

Start logo

Roo 中有很多可用性功能。如果您輸入“hint”,將顯示分步說明。如果您輸入“help”,將看到當前所有可用的命令(這些命令在專案生命週期的不同階段會變化)。此外,在幾乎所有情況下,您都可以按 TAB 鍵來獲取完成服務。如果您正在學習 Roo,“hint”是您的朋友。它不僅會教您命令,還會教您關於 shell 以及鍵盤功能的工作原理。遇到疑問時養成使用“hint”的習慣,尤其是在您的前幾個 Roo 專案中。

讓我們開始我們的婚禮專案。在 Roo shell 中輸入“create project”命令後,您應該會收到以下輸出

roo> project --topLevelPackage com.wedding
Created /home/balex/wedding/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml

如控制檯輸出所示,Roo 建立了一個 Maven 2 專案結構。即使您此時退出 Roo 且不再重新載入它,此時您也已擁有一個配置正確的 Spring 3 web 應用程式,包括 URL 重寫、基於註解的類路徑掃描以及任何類的依賴注入——即使是那些使用“new”關鍵字或透過像 Hibernate 這樣的 ORM 建立的類。您甚至可以使用“mvn tomcat:run”啟動嵌入式 Tomcat 容器。

如果您此時輸入“hint”,Roo 會建議您安裝 JPA 提供者和資料庫。現在就讓我們來做這件事

roo> persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties
Managed SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Managed ROOT/pom.xml

請注意,我們在命令中選擇了 Hibernate 和一個持久化的 Hypersonic 資料庫。這個選擇是透過 TAB 鍵完成的,所以我們實際上無需完全輸入這些命令和引數。還要注意底部的兩個“Managed”語句。這些表示 Roo 正在管理的檔案或目錄。Roo 內建了檔案撤銷服務,所以如果出現問題,它會自動回滾有問題的命令。

由於 Roo 使用 JPA,我們可以享受跨不同 JPA 實現的可移植性優勢。如果您檢視 Roo 生成的程式碼,您會發現沒有一行程式碼是特定於某個持久化提供者的。您還會發現 Roo 提供的程式碼效率極高。Java 的一大優勢是其顯著的效能,Roo 從避免反射到最佳化 toString() 方法中的字串操作(以及介於兩者之間的所有操作),無所不用其極地最大化應用程式的執行時效能。

因為我們請求了一個持久化資料庫,它預設儲存在 ~/wedding.* 中。有一個“database properties list”命令會顯示資料庫配置

roo> database properties list
database.driverClassName = org.hsqldb.jdbcDriver
database.password =
database.url = jdbc:hsqldb:${user.home}/wedding
database.username = sa

雖然預設位置可以正常工作,但讓我們將它更改到其他地方

roo> database properties set --key database.url --value jdbc:hsqldb:/home/balex/our-wedding
Managed SRC_MAIN_RESOURCES/META-INF/spring/database.properties

如控制檯輸出所示,Roo 所做的只是編輯了一個標準的 database.properties 檔案。您也可以使用文字編輯器或 IDE 合法地編輯您的專案檔案。Roo 不會介意。它從一開始就被設計成您可以同時使用其他工具與 Roo 一起工作,並且一切都能正常執行。

您可能喜歡透過 Roo 使用“database properties set”命令的一個原因是您正在製作一個獨立的指令碼,以後可以重放。您可以使用“script filename.roo”命令執行指令碼,這些指令碼只是標準文字檔案格式的 Roo 命令。為了方便您,我已將 wedding.roo 指令碼包含在 Roo 1.0.0 分發版中。請注意,指令碼中也可以使用普通的 Java 註釋語法(//, /* 和 */)包含註釋。

建立實體

現在讓我們建立一個 JPA 實體。該實體將儲存在我們的資料庫中,並代表我們應用程式的整個領域模型。您可以使用您選擇的 IDE、文字編輯器或 Roo shell 建立實體
roo> entity --class ~.domain.Rsvp
Created SRC_MAIN_JAVA/com/wedding/domain
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp.java
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Configurable.aj

此時我猜想你們中的一些人可能在想,“那些 .aj 檔案是什麼?” 簡單來說,這些是 AspectJ 跨型別宣告 (ITD),它們非常有效地實現了關注點分離,同時還能保持與相關 Roo 外掛未來版本的相容性。.aj 檔案由 Roo 自動建立、維護和刪除,允許終端使用者安全地忽略它們。事實上,STS 2.1.0+ 預設會自動隱藏它們,就像基於 Eclipse 的 IDE 隱藏 .classpath、.project 和 .settings 資源一樣。畢竟,這些資源僅僅是工具的內部輸出,您很少會開啟它們——更不用說自己維護了。我會在我的下一篇部落格文章中更詳細地討論這些以及其他 Roo 內部機制,所以在此之前我將推遲進一步的討論。

您可能已經注意到我們在“.domain”包中建立了 Rsvp 實體。“”字元會自動擴充套件為您的專案頂級包,您可能還記得我們在最初建立專案時指定過這個包。因此,Roo 完全理解 Java 包的概念,並允許您以您認為最直觀的方式組織您的專案包結構。

自然地,一個實體通常會包含一些欄位,所以讓我們新增它們(此處省略了 Roo 的輸出,因為它只是管理上面列出的相同檔案)

roo> field string code --notNull --sizeMin 1 --sizeMax 30
roo> field string email --sizeMax 30
roo> field number attending --type java.lang.Integer
roo> field string specialRequests --sizeMax 100
roo> field date confirmed --type java.util.Date

在第一行中,您會注意到我們使用了 --notNull 引數,以及 --sizeMin 和 --sizeMax 引數。這些引數指的是新的 Bean Validation 標準,也稱為 JSR 303。這個標準提供了自動化的 web 層和持久化層驗證,包括為資料庫中的表建立正確的 DDL使用 Roo 的優點之一是您可以輕鬆獲得相關標準的好處,例如 JSR 303、JPA、Servlet SpecificationREST,無需額外努力。當然,如果您不願意,您不必使用 JSR 303 引數。

從上面的欄位命令中需要注意的另一點是,我們沒有指定要將這些欄位插入哪個實體。Roo 會自動判斷您可能希望將欄位新增到 Rsvp,因為這是您上次處理的實體。如果您希望明確指定或將欄位指向另一個實體,也可以指定“--class ~.SomeEntity”引數(在這種情況下,該實體將成為後續與實體相關的命令的預設目標)。

證明它有效:JUnit、Web 層和 Selenium

現在我們有了一個如果部署就能實際執行的應用程式。但不要只聽我說的——讓我們新增一些功能,以便您可以自己嘗試一下這個應用程式。

讓我們從 JUnit 整合測試開始。您可以使用一個命令獲取整合測試

roo> test integration
Created SRC_TEST_JAVA/com/wedding/domain
Created SRC_TEST_JAVA/com/wedding/domain/RsvpDataOnDemand.java
Created SRC_TEST_JAVA/com/wedding/domain/RsvpIntegrationTest.java
Created SRC_TEST_JAVA/com/wedding/domain/RsvpDataOnDemand_Roo_Configurable.aj
Created SRC_TEST_JAVA/com/wedding/domain/RsvpDataOnDemand_Roo_DataOnDemand.aj
Created SRC_TEST_JAVA/com/wedding/domain/RsvpIntegrationTest_Roo_Configurable.aj
Created SRC_TEST_JAVA/com/wedding/domain/RsvpIntegrationTest_Roo_IntegrationTest.aj

這個整合測試將驗證常見的 JPA 操作,如 persist、remove、find、merge 等是否都正常工作。每個實體總共執行八個測試,所有測試都基於 Spring Framework 豐富的整合測試基礎設施。雖然我們可以在這個階段執行整合測試,但新增一個 web 層也是輕而易舉的事情

roo> controller scaffold ~.web.RsvpController
Created SRC_MAIN_JAVA/com/wedding/web
Created SRC_MAIN_JAVA/com/wedding/web/RsvpController.java
Created SRC_MAIN_WEBAPP/WEB-INF/config
Created SRC_MAIN_WEBAPP/WEB-INF/config/webmvc-config.xml
Created SRC_MAIN_JAVA/com/wedding/web/RsvpController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP/images
Created SRC_MAIN_WEBAPP/images/banner-graphic.png
Created SRC_MAIN_WEBAPP/images/springsource-logo.png
Created SRC_MAIN_WEBAPP/images/resultset_first.png
Created SRC_MAIN_WEBAPP/images/resultset_next.png
Created SRC_MAIN_WEBAPP/images/resultset_previous.png
Created SRC_MAIN_WEBAPP/images/resultset_last.png
Created SRC_MAIN_WEBAPP/images/us.png
Created SRC_MAIN_WEBAPP/images/de.png
Created SRC_MAIN_WEBAPP/images/list.png
Created SRC_MAIN_WEBAPP/images/add.png
Created SRC_MAIN_WEBAPP/styles
Created SRC_MAIN_WEBAPP/styles/roo-menu-left.css
Created SRC_MAIN_WEBAPP/styles/roo-menu-right.css
Created SRC_MAIN_WEBAPP/WEB-INF/classes
Created SRC_MAIN_WEBAPP/WEB-INF/classes/left.properties
Created SRC_MAIN_WEBAPP/WEB-INF/classes/right.properties
Created SRC_MAIN_WEBAPP/WEB-INF/layouts
Created SRC_MAIN_WEBAPP/WEB-INF/layouts/layouts.xml
Created SRC_MAIN_WEBAPP/WEB-INF/layouts/default.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views
Created SRC_MAIN_WEBAPP/WEB-INF/views/dataAccessFailure.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/resourceNotFound.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/uncaughtException.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/index.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/views.xml
Created SRC_MAIN_WEBAPP/WEB-INF/tags
Created SRC_MAIN_WEBAPP/WEB-INF/tags/pagination.tagx
Created SRC_MAIN_WEBAPP/WEB-INF/tags/language.tagx
Created SRC_MAIN_WEBAPP/WEB-INF/tags/theme.tagx
Created SRC_MAIN_WEBAPP/WEB-INF/i18n
Created SRC_MAIN_WEBAPP/WEB-INF/i18n/messages.properties
Managed SRC_MAIN_WEBAPP/WEB-INF/i18n/messages.properties
Created SRC_MAIN_WEBAPP/WEB-INF/i18n/messages_de.properties
Managed SRC_MAIN_WEBAPP/WEB-INF/i18n/messages_de.properties
Created SRC_MAIN_WEBAPP/images/show.png
Created SRC_MAIN_WEBAPP/images/update.png
Created SRC_MAIN_WEBAPP/images/delete.png
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp
Managed SRC_MAIN_WEBAPP/WEB-INF/config/webmvc-config.xml
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/list.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/show.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/create.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/update.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/views.xml
Created SRC_MAIN_WEBAPP/WEB-INF/urlrewrite.xml
Created SRC_MAIN_WEBAPP/WEB-INF/web.xml
Managed SRC_MAIN_WEBAPP/WEB-INF/web.xml
Managed ROOT/pom.xml

Roo 提供的自動化 web 層構建在 Spring Framework 3 出色的REST 支援之上。所有端點都是完全 RESTful 的,並使用清晰、格式正確的 URL。Roo 的自動化 web 層在多種情況下都很有用,特別是

  • 透過 REST 客戶端進行程式設計訪問
  • 應用程式的管理部分
  • 用作您手動建立的控制器和 JSP 的模板
最後,讓我們新增一個 Selenium 測試,它將實際驗證我們新的 RsvpController 是否正常工作
roo> selenium test --controller ~.web.RsvpController
Created SRC_MAIN_WEBAPP/selenium
Created SRC_MAIN_WEBAPP/selenium/test-rsvp.xhtml
Created SRC_MAIN_WEBAPP/selenium/test-suite.xhtml
Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Managed ROOT/pom.xml

好的,讓我們執行以下命令來看看應用程式的實際效果

roo> perform test
 (Maven console output condensed)
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.wedding.domain.RsvpIntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.726 sec
roo> quit
$ mvn tomcat:run

現在您可以使用瀏覽器訪問 https://:8080/wedding。當您準備測試 web 層時,請保持 Tomcat 伺服器執行,然後執行以下命令

$ mvn selenium:selenese

在執行 Selenium 測試期間,您應該會看到類似於

Selenium screet shot

安全和日誌記錄

到目前為止,我們已經建立了我們的婚禮應用程式,運行了一些整合測試,在 web 瀏覽器中試用了一下,並使用自動化的 Selenium 測試驗證了 web 層的正確操作。下一步是微調基於 Log4J 的日誌記錄
roo> logging setup --package WEB --level DEBUG
Created SRC_MAIN_RESOURCES/META-INF/spring/log4j.properties
Managed SRC_MAIN_WEBAPP/WEB-INF/web.xml

現在讓我們關注安全性。目前任何人都可以訪問我們的網站,並使用現有的 RESTful 管理後端建立、更新和刪除 RSVP。回想一下應用程式需求,我們希望使用邀請碼(印在卡片背面)來確保只有被邀請的賓客才能回覆。幸運的是,Spring Security 為我們提供了一種非常快速的方法來滿足這一需求,並且 Roo 可以用一行命令安裝 Spring Security

roo> security setup
Managed ROOT/pom.xml
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext-security.xml
Created SRC_MAIN_WEBAPP/WEB-INF/views/login.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/views.xml
Managed SRC_MAIN_WEBAPP/WEB-INF/web.xml

還有類似的命令,如“web flow”和“jms setup”,但我們不會在本部落格文章中探討它們。您可以期待在未來版本的 Roo 中看到更多“install”命令(也歡迎您將安裝程式請求新增到 Roo 問題跟蹤器)。

手動控制器、動態查詢器和電子郵件支援

如上所述,我們的自動化 web 層非常適合我們應用程式的管理後端。但我們還需要應用程式的一部分,它適合準賓客使用。應用程式的這一部分必須理解邀請碼、管理 RSVP 和傳送電子郵件之間的關係。由於這些需求將需要一些程式設計,讓我們構建一個新的控制器
roo> controller class --class ~.web.PublicRsvpController
Created SRC_MAIN_JAVA/com/wedding/web/PublicRsvpController.java
Managed SRC_MAIN_WEBAPP/WEB-INF/web.xml
Managed ROOT/pom.xml

PublicRsvpController 將響應 HTTP GET 和 POST 請求。PublicRsvpController.java 原始檔中已自動提供了用於這些操作的兩個存根方法。

如果我們考慮 GET 用例,我們的目標是為特定的邀請碼檢索正確的 RSVP。其工作方式是 Spring Security 將要求使用者登入才能使用應用程式,我們將每個邀請碼視為一個唯一的登入名。因此,GET 方法需要從 Spring Security 獲取當前登入使用者的名稱,然後從資料庫中檢索相應的 RSVP。通常,您此時會編寫 JPA QL 查詢來獲取具有匹配碼的特定 Rsvp 例項,但由於您正在使用 Roo,您可以省去麻煩,轉而使用動態查詢器。

動態查詢器為您提供了幾乎無限範圍的預定義查詢。這些查詢內部都使用 JPA QL,提供了最大的基於標準的相容性和可移植性。所有動態查詢器(以及其他 Roo 方法)都實現為格式正確、型別安全的 Java 方法——帶來了熟悉性、IDE 程式碼輔助、偵錯程式整合和顯著的執行時效能等所有常規優勢。您可以使用如下命令列出可用的動態查詢器

roo> finder list --class ~.domain.Rsvp --filter code,equ
findRsvpsByCodeEquals(String code)
findRsvpsByCodeNotEquals(String code)

請注意,“--filter”引數將輸出限制為僅包含“code”和“equ”字串的建議方法簽名。您可以透過省略“-filter”引數或指定“-depth 2”(如果您希望查詢涉及更多屬性,可以指定 3、4 等)來指示 Roo 顯示更多組合。

一旦找到您想使用的動態查詢器,只需新增它

roo> finder add --finderName findRsvpsByCodeEquals
Managed SRC_MAIN_JAVA/com/wedding/domain/Rsvp.java
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Finder.aj
Managed SRC_MAIN_JAVA/com/wedding/web/RsvpController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/findRsvpsByCodeEquals.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/rsvp/views.xml

如果我們考慮 PublicRsvpController 的 POST 用例,我們的需求規定我們應該向賓客傳送一封電子郵件以確認他們的 RSVP。通常我們會翻閱 Spring 參考指南,找到關於配置電子郵件支援的部分,但現在我們只需讓 Roo 為我們處理即可

roo> email sender setup --hostServer 127.0.0.1
Created SRC_MAIN_RESOURCES/META-INF/spring/email.properties
Managed SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Managed ROOT/pom.xml
roo> field email template --class ~.web.PublicRsvpController
Managed SRC_MAIN_JAVA/com/wedding/web/PublicRsvpController.java
Managed SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Managed SRC_MAIN_JAVA/com/wedding/web/PublicRsvpController.java

最後一個命令向 PublicRsvpController 添加了一個 Spring MailSender 欄位,並提供了一個方法向我們展示如何使用它。

關於電子郵件整合的話題,我的同事 Stefan Schmidt 剛剛發表了另一篇部落格文章,展示瞭如何將 Roo 電子郵件和 JMS 外掛結合使用。文章展示了更高階的配置選項,例如如何使用 Gmail 傳送電子郵件。

IDE 整合

現在我們已經到了可以使用 Eclipse/STS 來完成應用程式的地步。讓我們將應用程式匯入到 Eclipse/STS 中
roo> perform eclipse
 (Maven console output condensed)

最後,讓我們將專案匯入到 Eclipse/STS 中。透過啟動 Eclipse/STS,然後選擇 File > Import > Existing Projects into Workspace,並選擇專案目錄來完成此操作。如果您使用的不是 STS 2.3.0 或更高版本,請確保您已單獨安裝了 AJDT 1.6.5 或更高版本。當 AJDT 提示您是否要啟用 JDT weaving 時,選擇啟用 weaving。這將在使用 Eclipse 的 Java 編輯器時提供更好的 Roo 體驗。

最後步驟

現在我們將使用 Eclipse/STS 更改幾個檔案。以下截圖顯示了我們最終的專案結構,我已高亮顯示了我們將要更改的檔案: structure

首先編輯 applicationContext-security.xml 檔案。進行一些小的更改,使其類似於以下檔案


    <http auto-config="true" use-expressions="true">
    	<form-login login-processing-url="/static/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/static/j_spring_security_logout"/>
        <intercept-url pattern="/rsvp/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/static/**" access="permitAll" />
        <intercept-url pattern="/login**" access="permitAll" />
        <intercept-url pattern="/**" access="isAuthenticated()" />
    </http>

    <authentication-manager alias="authenticationManager">
    	<authentication-provider>
	        <user-service>
	            <user name="admin1234" password="ignored" authorities="ROLE_ADMIN"/>
		        <user name="user12345" password="ignored" authorities="ROLE_USER"/>
		        <user name="user67890" password="ignored" authorities="ROLE_USER"/>
		    </user-service>
    	</authentication-provider>
	</authentication-manager>

上面的檔案顯示邀請碼實際上就是使用者名稱,並且我們忽略了密碼。Spring Security 不知道我們忽略了密碼,因此我們需要編輯 src/main/webapp/WEB-INF/views/login.jspx 檔案,並在表單中新增一行 <input name="j_password" type="hidden" value="ignored"/>。當然,包含“j_password”標籤和輸入元素的現有 <div> 應該被刪除。還應在該檔案中新增一些適當的文字,向賓客解釋他們可以在卡片的哪個位置找到他們的邀請碼。

現在安全性已經設定好了。現在讓我們開啟 PublicRsvpController.java 檔案。如所示,Roo 已經為電子郵件功能提供了存根,併為您提供了空的 Spring MVC 方法來完成。這是整個應用程式中唯一實際需要的 Java 程式設計,而且由於這些方法使用了 Spring MVC 和 Spring 的 MailSender 類的常規功能,我在此將不再進一步討論它們


@RequestMapping("/publicrsvp/**")
@Controller
@SessionAttributes("rsvp")
public class PublicRsvpController {

    @Autowired
    private transient MailSender mailTemplate;

    @RequestMapping
    public String get(ModelMap modelMap) {
    	modelMap.put("rsvp", getRsvp());
    	return "publicrsvp";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String post(@ModelAttribute("rsvp") Rsvp rsvp, ModelMap modelMap) {
  	rsvp.setConfirmed(new Date());
        if (rsvp.getId() == null) {
        	rsvp.persist();
        } else {
        	rsvp.merge();
        }
    	if (rsvp.getEmail().length() > 0) {
        	sendMessage("Ben Alex <[email protected]>", "RSVP to our wedding", rsvp.getEmail(), "Your RSVP has been saved: " + rsvp.toString());
    	}
    	modelMap.put("rsvp", rsvp);
    	return "thanks";
    }

    private Rsvp getRsvp() {
    	Rsvp rsvp = new Rsvp();
    	try {
        	String code = SecurityContextHolder.getContext().getAuthentication().getName();
        	rsvp.setCode(code);
    		// Cast due to http://java.sun.com/javaee/5/docs/api/javax/persistence/Query.html#getSingleResult()
    		rsvp = (Rsvp) Rsvp.findRsvpsByCodeEquals(code).getSingleResult();
    	} catch (DataAccessException ignored) { /* no Rsvp for this code was found, so start a new Rsvp */ }
    	return rsvp;
    }

    private void sendMessage(String mailFrom, String subject, String mailTo, String message) {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setFrom(mailFrom);
        simpleMailMessage.setSubject(subject);
        simpleMailMessage.setTo(mailTo);
        simpleMailMessage.setText(message);
        mailTemplate.send(simpleMailMessage);
    }
}

請注意,“get”和“post”方法中我們返回了一個字串,這應該與我們希望渲染的 JSP 檢視名稱相關聯。因此,我們的下一步是提供這兩個 JSP 檔案。幸運的是,Roo 構建了一些 JSP 檔案,它們可以作為有用的模板。

首先將 src/main/webapp/WEB-INF/index.jsp 重新命名為 thanks.jsp。這將是“post”方法返回時顯示的頁面。您可能想要新增一些內容,例如“您的回覆已確認:${rsvp}”。

接下來將 src/main/webapp/WEB-INF/views/rsvp/create.jspx 複製到 src/main/webapp/WEB-INF/views/publicrsvp.jspx。然後應該編輯此頁面。您可以安全地刪除“code”和“confirmed”部分,因為 PublicRsvpController 會處理它們。您還應該將“form_url”變數賦值更改為 <c:url value="/publicrsvp" var="form_url"/>。

由於 Spring Roo 使用 Tiles 來方便地為每個 web 檢視進行品牌化,因此您需要編輯 src/main/webapp/WEB-INF/views/views.xml 檔案。您需要將“index”定義重新命名為“thanks”,併為新的 publicrsvp.jspx 新增一個新的“publicrsvp”定義。最終檔案應類似於


<tiles-definitions>

	<definition extends="public" name="thanks">
		<put-attribute name="body" value="/WEB-INF/views/thanks.jspx"/>
	</definition>

	<definition extends="public" name="dataAccessFailure">
		<put-attribute name="body" value="/WEB-INF/views/dataAccessFailure.jspx"/>
	</definition>

	<definition extends="public" name="resourceNotFound">
		<put-attribute name="body" value="/WEB-INF/views/resourceNotFound.jspx"/>
	</definition>

	<definition extends="public" name="uncaughtException">
		<put-attribute name="body" value="/WEB-INF/views/uncaughtException.jspx"/>
	</definition>

	<definition extends="public" name="login">
        <put-attribute name="body" value="/WEB-INF/views/login.jspx"/>
    </definition>

	<definition extends="public" name="publicrsvp">
        <put-attribute name="body" value="/WEB-INF/views/publicrsvp.jspx"/>
    </definition>

</tiles-definitions>

最後一步是編輯 src/main/webapp/WEB-INF/urlrewrite.xml 檔案,並更改針對 / 的 URL 重寫規則。/app/index 應該修改為 /app/publicrsvp/,這表示預設情況下對新的 PublicRsvpController 執行 GET 操作。

現在您應該準備好測試部署了。您有幾種選擇

  • 從命令列使用“mvn tomcat:run”
  • 在 Eclipse/STS 中右鍵單擊專案,選擇 Run As > Run on Server
  • 在 STS 中右鍵單擊專案,選擇 Spring Tools > Open Roo Shell,然後輸入“deploy --server someServer”
現在訪問 https://:8080/wedding 時,系統會提示您輸入邀請碼。使用上面 applicationContext-security.xml 檔案中列出的使用者名稱之一。回覆幾次,您就會看到應用程式正確檢索了您先前的 RSVP 記錄。您還會看到它會向您傳送一封電子郵件,前提是您輸入了電子郵件地址並配置了正常的 SMTP 伺服器(如果您想更改 SMTP 伺服器詳細資訊,請編輯 mail.properties 檔案)。以管理員使用者身份登入,您將看到您可以訪問所有 RSVP 記錄,更改它們等等。

自然地,在這個階段,我們通常會整理應用程式公共可見部分的外觀。然後我們會執行“perform package”命令,以生成一個可部署到生產伺服器環境(例如 SpringSource tc ServerSpringSource dm Server)的 WAR 檔案。

結論

在這篇部落格文章中,我們介紹了安裝 Roo、使用其 shell 以及快速構建一個具有以下特性的實際應用程式所需的步驟:
  • 基於 Spring Framework 3 的最佳實踐應用程式架構
  • 基於 Maven 2 的專案結構
  • 基於 JPA 的持久化,在此例中使用了 Hibernate
  • 資料庫儲存到自定義的 Hypersonic 檔案位置
  • Bean Validation (JSR 303) 支援,包括將約束傳播到資料庫 DDL
  • 基於 Spring Framework 整合測試功能的自動化 JUnit 整合測試
  • 自動化的 RESTful 應用程式後端
  • 您 web 層的自動化 Selenium 測試
  • 在我們的應用程式中用於實際用例的動態查詢器
  • Spring Security 整合,包括 web URL 安全性和定製的登入頁面
  • 電子郵件傳送(有關透過 Roo 傳送電子郵件的更多資訊,請閱讀 Stefan 的部落格文章
  • Log4J 配置支援
  • 保持 URL 清晰和 RESTful 的 URL 重寫
  • 一個手動 web 控制器
  • 嵌入式 Tomcat 伺服器容器使用
  • Eclipse 和 STS 整合
未來幾周和幾個月內,我們將為 Roo 新增許多額外功能,我的下一篇部落格文章將涵蓋 Roo 的內部機制和架構。在此期間,我們非常歡迎您對 Roo 的評論、使用體驗和反饋。社群論壇是提問的好地方,您也可以在 Twitter 上關注我們#roo)。我們希望您能享受使用 Spring Roo 的樂趣。

獲取 Spring 時事通訊

訂閱 Spring 時事通訊,保持聯絡

訂閱

取得進展

VMware 提供培訓和認證,助您加速前進。

瞭解更多

獲取支援

Tanzu Spring 透過一項簡單的訂閱,提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將到來的活動

檢視 Spring 社群所有即將到來的活動。

檢視全部