Spring:Grails 的基礎

工程 | Peter Ledbrook | 2010 年 6 月 8 日 | ...

在 SpringSource 關於 Groovy & Grails 的培訓課程中,我們強調 Grails 是站在巨人的肩膀上。其中一個巨人就是 Spring。沒有它,Grails 就不會發展得如此迅速。它可能也沒有靈活性輕鬆地與企業級 Java 系統整合。看看可用的外掛數量就知道了:許多都是基於支援 Spring 的 Java 庫。

在這篇文章中,我想先探討 Grails 如何使用 Spring,然後介紹您可以訪問其原始能力和靈活性的各種方式。

Spring 的孩子

您可能沒有意識到,但當您建立一個 Grails 應用程式時,您也在建立一個 Spring MVC 應用程式。在底層,Grails 建立了 Spring MVC 的一個變體,即DispatcherServlet並配置了一堆 bean 來完成繁重的工作。當然,這意味著您的應用程式有一個 Spring 上下文作為基礎——正如您稍後將看到的,您可以訪問它。

以下是在典型的 Grails 應用程式的 Spring 上下文中可以找到的一些 bean 的示例:

  • grailsApplication - 代表當前應用程式及其資源的 bean
  • pluginManager - 外掛管理器,您可以查詢載入的外掛資訊
  • jspViewResolver - 一個用於 GSP 並回退到 JSP 的自定義 MVC 檢視解析器
  • messageSource - 本地化訊息源
  • localeResolver - 如何確定使用者的區域設定
  • annotationHandlerMapping - 允許使用@Controller註解
這只是 Grails 建立的眾多 bean 中的一小部分。實際上,您可能會驚訝地發現,您的所有應用程式控制器、標籤庫、領域類和服務也都是 bean。

Grails 在其他哪些方面依賴於 Spring?首先是資料繫結:Spring 完成所有字串資料到物件屬性的物理繫結。也不僅僅是 Web 層:GORM 使用 Spring 的 Hibernate 模板來儲存和查詢領域類。或許最重要的是,Grails 使用 Spring 的事務管理。正如我已經提到的,許多外掛都利用了 Java 庫提供的 Spring 整合。

因此,一個 Grails 應用程式本質上是一個偽裝的 Spring 應用程式。這就引出了一個問題:當您需要時,如何利用底層的 Spring 元件。

與 Spring 交流

您可以輕鬆編寫 Grails 應用程式,即使是相當複雜的應用程式,而無需擔心 Spring。這很棒,因為這意味著新的開發者不必立即學習這項技術。但是,僅僅因為您不必直接與 Spring 互動,並不意味著這很困難。

有幾種方法可供您選擇,我將依次介紹它們。

Service 和自動裝配

Spring 的關鍵在於 bean 的建立和裝配。通常您需要提供一些配置來完成這項工作,但 Grails 的 Service 提供了一種輕量級、基於約定的替代方案。正如我之前所說,您在 Grails 應用程式中建立的幾個 artifact 會自動成為 Spring bean,但 Service 提供了最多的控制能力。

原理很簡單:在grails-app/services目錄下建立一個以Service為字尾的類,您就會在應用程式中自動獲得一個新的(單例)Spring bean。這個 bean 的名稱是什麼?很簡單:將類名的首字母小寫。例如,類SecurityService將產生一個名為 "securityService" 的 bean。AuditReportService同樣會成為一個名為 "auditReportService" 的 bean。

將其他 bean 裝配到您的 Service(以及所有其他核心 Grails artifact)中同樣簡單直接:宣告一個與您想要的 bean 同名的屬性。例如,假設我想在另一個 Service(或者控制器)中使用 "auditReportService" bean。我可以這樣裝配:

class MyService {
    def auditReportService
    ...
}

我相信您會同意這非常簡單。這是 Spring 自動裝配的一個例子。即使您為屬性指定了型別,Grails 也會按名稱裝配 bean。

Service 的另一個有用特性是它們預設是事務性的。這使得它們成為抽象資料訪問併為您的應用程式構建健壯架構的絕佳方式。典型的方法是為您的 Service 建立不同的“閘道器”:HTML UI、XML REST 介面、透過 RMI 進行遠端呼叫等,它們都呼叫您的 Service。

最後一點:我說 Grails 會將您的 Service 例項化為單例 bean,但您可以為每個 Service 更改此行為。只需在您的 Service 類中新增一個靜態scope屬性,如下所示:

class MyService {
    static scope = "request"
    ...
}

正如您所見,使用 Service 時,您幾乎不費吹灰之力就能獲得 Spring 的許多主要好處。這很棒,但是如果您有現有的類想要變成 bean 怎麼辦?也許您是用 Java 編寫的,或者它們打包在一個 JAR 檔案中。使用純粹的 Spring,您需要手動配置它們。幸運的是,您可以在 Grails 中做同樣的事情。

手動定義 bean

在過去,也就是註解出現之前,您會為 Spring 應用程式建立一或多個 XML 格式的 bean 描述符檔案。Grails 允許您透過grails-app/conf/spring/resources.xml檔案來做到這一點。它沒有什麼特別之處,因此標準的 Spring 文件也適用於此!

我必須說,我不再喜歡編寫 XML 了,所以我更喜歡另一種 bean 定義格式:Grails 的 Spring Bean DSL。這是一個在grails-app/conf/spring/resources.groovy:

beans = {
    reportGenerator(org.example.XmlReportGenerator)
}

中定義報告生成器 bean 的一個非常簡單的例子。定義以 bean 的名稱("reportGenerator")開始,後跟括號中的類("XmlReportGenerator")。您還可以配置 bean 和 bean 定義屬性

beans = {
    reportGenerator(org.example.XmlReportGenerator) { bean ->
        bean.autowire = "byName"
        prettyFormat = true
    }
}

好的,所以它比 XML 格式更簡潔,但這是否足以成為切換的理由?對於大多數人來說可能不是。DSL 的真正強大之處在於它是真實的 Groovy,這意味著

  • 您可以混入普通的 Groovy 程式碼,例如條件和迴圈;以及
  • 您可以使用真實的型別作為屬性值。

更新 我已經修改了下面的示例,使用了檢測當前環境的新方法。

請看這個例子:

import grails.util.Environment

beans = {
    if (Environment.current == Environment.PRODUCTION) {
        // Use the real web service for production
        securityService(org.example.WsClientSecurityService) {
            endpoint = "http://..."
        }
    }
    else {
        // Use a dummy service for development and testing
        securityService(org.example.DummySecurityService)  {
            userRoles = [ peter: [ "admin", "user"], tom: [ "user" ] ]
        }
    }
}

它演示瞭如何使用條件為不同的 Grails 環境配置不同的 bean 實現。對於生產環境,“securityService” 是一個 Web Service 客戶端,而對於所有其他環境,我們使用一個記憶體中的虛擬 Service。您還可以看到,可以將一個字面量 Map 賦值給一個Map屬性,同樣也適用於任何其他型別。它不僅比 XML 簡潔得多,而且您正在處理真實的型別和物件,可以在執行時進行操作。

DSL 的內容遠不止我在這裡介紹的這些,所以我建議您查閱使用者指南以獲取更多資訊。您會發現 DSL 支援 Spring 名稱空間、工廠方法等。

我已經介紹了 Grails 中兩個最常見的 Spring 整合點,但還有另一個:註解。

註解

Spring 2.5 引入了用於各種用途的註解,例如元件、Spring MVC 控制器、事務等。您會很高興知道可以在 Grails 應用程式中使用這些註解。例如,假設您在src/java目錄下有一個 Java 類,如下所示:
package org.example;

@Component("securityService")
public class DummySecurityService implements SecurityService {
    ...
}

這應該會自動成為一個名為 "securityService" 的 Spring bean,但這還不會發生。還需要額外一步:您必須指定 Grails 應該掃描哪些包以查詢 Spring 註解。因此,在這個例子中,我們希望掃描org.example包。要做到這一點,只需將以下設定新增到grails-app/conf/Config.groovy:

grails.spring.bean.packages = [ "org.example" ]

中。現在,該類將自動建立為一個 Spring bean。請注意,Grails 也會掃描所有子包,因此即使該類位於org.example.sub.pkg包中,上面的設定也有效。

只要您透過grails.spring.bean.packages指定了包,您甚至可以使用 @Controller 註解將類新增為控制器。如果您決定從 Spring MVC 遷移到 Grails,或者一個團隊開發了一些 Spring MVC 控制器,您希望將其放入您的 Grails 應用程式中,這會很有幫助。

正如您所見,Grails 中定義 bean 的選項足夠多,可以滿足大多數人的需求。接下來只需要介紹執行時如何查詢 Spring 應用程式上下文了。

執行時互動

許多應用程式發現直接與 Spring 應用程式上下文互動很有用,無論是查詢特定型別的所有 bean,還是僅僅檢索某個特定的 bean 而不依賴於自動裝配。如何訪問應用程式上下文呢?

如果您的類是一個 Spring bean,那麼您可以簡單地實現ApplicationContextAware介面。Spring 將自動把上下文注入到您的applicationContext屬性中。或者,您可以注入grailsApplicationbean,並透過grailsApplication.mainContext.

檢索上下文。另一方面,如果您的類不受 Spring 管理,則必須進行一些手動操作。雖然不太優雅,但您可以透過這段程式碼片段獲取上下文:

import org.springframework.web.context.support.WebApplicationContextUtils
import org.codehaus.groovy.grails.web.context.ServletContextHolder
import org.springframework.context.ApplicationContext
...
def ctx = WebApplicationContextUtils.getWebApplicationContext(ServletContextHolder.servletContext)

注意:在 Grails 中,您處理的不是單個應用程式上下文。透過上面描述的不同技術獲得的應用程式上下文都有一個父應用程式上下文。該父上下文包含grailsApplication, pluginManager以及從web-app/WEB-INF/applicationContext.xml檔案配置的其他 bean。您可能會發現一些程式碼允許您透過不同於上述詳細介紹的方式獲取應用程式上下文,但您最終可能會獲得對父上下文的引用,該上下文不包含 services、controllers 等。

總而言之,Grails 本質上是一個偽裝的 Spring 應用程式。雖然它隱藏了 Spring,使其不顯眼,但它確實提供了一些強大的技術可以直接與 Spring 互動。這意味著您可以輕鬆利用現有的 Java/Spring 庫,並使用一個框架,該框架使得大型應用程式比沒有它時更易於管理。

獲取 Spring 新聞通訊

訂閱 Spring 新聞通訊以保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

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

瞭解更多

即將舉行的活動

檢視 Spring 社群中所有即將舉行的活動。

檢視全部