Spring:Grails 的基石

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

在 SpringSource 的 Groovy & Grails 培訓課程中,我們強調 Grails 是站在巨人的肩膀上。Spring 就是其中一位巨人。沒有 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 MVC 檢視解析器,會回退到 JSP
  • 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 互動,並不意味著它很難。

有幾種方法可以採用,我將一一介紹。

服務和自動裝配

Spring 的關鍵在於 Bean 的建立和裝配。通常你需要提供一些配置來完成此操作,但 Grails 服務為你提供了一種輕量級的、基於約定的替代方案。正如我之前所說,你在 Grails 應用程式中建立的幾個工件會自動成為 Spring Bean,但服務能讓你擁有最大的控制權。

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

將其他 Bean 裝配到你的服務中(以及所有其他核心 Grails 工件)同樣簡單:宣告一個與你想要的 Bean 同名的屬性。例如,假設我想在另一個服務(或可能是控制器)中使用“auditReportService” Bean。我可以像這樣裝配它:

class MyService {
    def auditReportService
    ...
}

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

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

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

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

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

手動定義 Bean

在過去的那個美好年代,在註解出現之前,你會為你的 Spring 應用程式建立一個或多個 XML 格式的 Bean 描述符檔案。Grails 允許你使用grails-app/conf/spring/resources.xml檔案執行相同的操作。它沒有什麼特別之處,所以標準的 Spring 文件適用!

我必須說,我不再喜歡編寫 XML 了,所以我更喜歡一種替代的 Bean 定義格式:Grails 的 Spring Bean DSL。這是一個定義報表生成器 Bean 的非常簡單的例子,在grails-app/conf/spring/resources.groovy:

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

中。定義以 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 服務客戶端,而對於所有其他環境,我們使用一個虛擬的記憶體服務。你還可以看到,可以將字面量對映賦給一個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,或者如果團隊開發了一些你想放入 Grails 應用程式的 Spring MVC 控制器,這會很有幫助。

如你所見,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。你可能會發現程式碼允許你以不同於上述方式獲取應用程式上下文,但你可能會得到一個指向父上下文的引用,該父上下文不包含服務、控制器等。

總之,Grails 本質上是一個偽裝的 Spring 應用程式。雖然它在表面上隱藏了 Spring,但它提供了一些強大的技術來直接與 Spring 互動。這意味著你可以輕鬆地利用現有的 Java/Spring 庫,並使用一個能夠使大型應用程式比其他方式更易於管理的框架。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

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

瞭解更多

即將舉行的活動

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

檢視所有