beans = {
// beans here
}
18 Grails 和 Spring
版本 6.2.0
目錄
18 Grails 和 Spring
此區段適用於進階使用者,以及有興趣了解 Grails 如何整合和建構於 Spring Framework 上的人員。對於考慮執行 Grails 執行時期設定的 外掛程式開發人員 也很有用。
18.1 設定其他 Bean
使用 Spring Bean DSL
您可以透過在 grails-app/conf/spring/resources.groovy
中設定 Bean 來輕鬆註冊新的 Bean(或覆寫現有的 Bean),此設定使用 Grails Spring DSL。Bean 定義在 beans
屬性(一個 Closure)內
以下是一個簡單範例,您可以使用以下語法設定 Bean
import my.company.MyBeanImpl
beans = {
myBean(MyBeanImpl) {
someProperty = 42
otherProperty = "blue"
}
}
設定後,Bean 可以自動注入到 Grails 工件和其他支援依賴注入的類別(例如 BootStrap.groovy
和整合測試)中,方法是宣告一個公用欄位,其名稱為 Bean 的名稱(在本例中為 myBean
)
class ExampleController {
def myBean
...
}
使用 DSL 的好處是您可以混合 Bean 宣告和邏輯,例如根據 環境
import grails.util.Environment
import my.company.mock.MockImpl
import my.company.MyBeanImpl
beans = {
switch(Environment.current) {
case Environment.PRODUCTION:
myBean(MyBeanImpl) {
someProperty = 42
otherProperty = "blue"
}
break
case Environment.DEVELOPMENT:
myBean(MockImpl) {
someProperty = 42
otherProperty = "blue"
}
break
}
}
可以使用 application
變數存取 GrailsApplication
物件,並用於存取 Grails 設定(以及其他項目)
import grails.util.Environment
import my.company.mock.MockImpl
import my.company.MyBeanImpl
beans = {
if (application.config.getProperty('my.company.mockService')) {
myBean(MockImpl) {
someProperty = 42
otherProperty = "blue"
}
} else {
myBean(MyBeanImpl) {
someProperty = 42
otherProperty = "blue"
}
}
}
如果您在 resources.groovy 中定義一個 Bean,其名稱與先前由 Grails 或已安裝外掛程式註冊的名稱相同,您的 Bean 將取代先前的註冊。這是一種自訂行為的便利方式,無需編輯外掛程式程式碼或採用其他會影響可維護性的方式。
|
使用 XML
也可以使用 grails-app/conf/spring/resources.xml
設定 Bean。在較早版本的 Grails 中,此檔案會由 run-app
腳本自動為您產生,但現在 resources.groovy
中的 DSL 是首選方式,因此現在不會自動產生。不過它仍然受到支援,您只需要自行建立即可。
此檔案是典型的 Spring XML 檔案,Spring 文件有一個 極佳的參考,說明如何設定 Spring Bean。
我們使用 DSL 設定的 myBean
bean 會在 XML 檔案中使用以下語法設定
<bean id="myBean" class="my.company.MyBeanImpl">
<property name="someProperty" value="42" />
<property name="otherProperty" value="blue" />
</bean>
與其他 bean 一樣,它可以自動注入到支援依賴注入的任何類別中
class ExampleController {
def myBean
}
參照現有 Bean
在 resources.groovy
或 resources.xml
中宣告的 Bean 可以依慣例參照其他 Bean。例如,如果您有一個 BookService
類別,它的 Spring bean 名稱將會是 bookService
,因此您的 bean 會在 DSL 中像這樣參照它
beans = {
myBean(MyBeanImpl) {
someProperty = 42
otherProperty = "blue"
bookService = ref("bookService")
}
}
或在 XML 中像這樣
<bean id="myBean" class="my.company.MyBeanImpl">
<property name="someProperty" value="42" />
<property name="otherProperty" value="blue" />
<property name="bookService" ref="bookService" />
</bean>
bean 需要一個公用的 setter 來設定 bean 參照(以及兩個簡單的屬性),在 Groovy 中會像這樣定義
package my.company
class MyBeanImpl {
Integer someProperty
String otherProperty
BookService bookService // or just "def bookService"
}
或在 Java 中像這樣
package my.company;
class MyBeanImpl {
private BookService bookService;
private Integer someProperty;
private String otherProperty;
public void setBookService(BookService theBookService) {
this.bookService = theBookService;
}
public void setSomeProperty(Integer someProperty) {
this.someProperty = someProperty;
}
public void setOtherProperty(String otherProperty) {
this.otherProperty = otherProperty;
}
}
使用 ref
(在 XML 或 DSL 中)非常強大,因為它設定了一個執行時間參照,因此被參照的 bean 不需要存在。只要它在最終應用程式內容設定發生時存在,所有內容都會正確解析。
有關可用 bean 的完整參考,請參閱參考指南中的外掛程式參考。
18.2 使用 Bean DSL 的執行時間 Spring
Grails 中的這個 Bean 建構器旨在提供一種簡化的方式來連接使用 Spring 為核心的依賴項。
此外,Spring 的常規設定方式(透過 XML 和註解)是靜態的,難以在執行時間修改和設定,除了容易出錯且冗長的程式化 XML 建立。Grails 的 BeanBuilder 改變了這一切,讓您可以在執行時間以程式化方式連接組件,讓您可以根據系統屬性或環境變數調整邏輯。
這讓程式碼能夠適應其環境,並避免不必要的程式碼重複(針對測試、開發和生產環境有不同的 Spring 設定)
BeanBuilder 類別
Grails 提供了一個 grails.spring.BeanBuilder 類別,它使用動態 Groovy 來建立 bean 定義。基礎如下
import org.apache.commons.dbcp.BasicDataSource
import org.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean
import org.springframework.context.ApplicationContext
import grails.spring.BeanBuilder
def bb = new BeanBuilder()
bb.beans {
dataSource(BasicDataSource) {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:grailsDB"
username = "sa"
password = ""
}
sessionFactory(ConfigurableLocalSessionFactoryBean) {
dataSource = ref('dataSource')
hibernateProperties = ["hibernate.hbm2ddl.auto": "create-drop",
"hibernate.show_sql": "true"]
}
}
ApplicationContext appContext = bb.createApplicationContext()
在 外掛程式 和 grails-app/conf/spring/resources.groovy 檔案中,您不需要建立 BeanBuilder 的新執行個體。相反地,DSL 分別在 doWithSpring 和 beans 區塊中隱含可用。
|
此範例顯示如何使用 BeanBuilder
類別設定具有資料來源的 Hibernate。
每個方法呼叫(在本例中為 dataSource
和 sessionFactory
呼叫)都對應到 Spring 中 bean 的名稱。方法的第一個引數是 bean 的類別,而最後一個引數是一個區塊。在區塊的主體中,你可以使用標準 Groovy 語法設定 bean 的屬性。
bean 參照會使用 bean 的名稱自動解析。這可以在上面的範例中看到,其中 sessionFactory
bean 解析 dataSource
參照的方式。
建構器也可以設定某些與 bean 管理相關的特殊屬性,如下列程式碼所示
sessionFactory(ConfigurableLocalSessionFactoryBean) { bean ->
// Autowiring behaviour. The other option is 'byType'. <<autowire>>
bean.autowire = 'byName'
// Sets the initialisation method to 'init'. [init-method]
bean.initMethod = 'init'
// Sets the destruction method to 'destroy'. [destroy-method]
bean.destroyMethod = 'destroy'
// Sets the scope of the bean. <<scope>>
bean.scope = 'request'
dataSource = ref('dataSource')
hibernateProperties = ["hibernate.hbm2ddl.auto": "create-drop",
"hibernate.show_sql": "true"]
}
方括號中的字串是 Spring XML 定義中對應 bean 屬性的名稱。
在 Spring MVC 中使用 BeanBuilder
在你的類別路徑中包含 grails-spring-<version>.jar
檔案,以便在一般的 Spring MVC 應用程式中使用 BeanBuilder。然後將下列 <context-param>
值新增到你的 /WEB-INF/web.xml
檔案
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.groovy</param-value>
</context-param>
<context-param>
<param-name>contextClass</param-name>
<param-value>
grails.web.servlet.context.GrailsWebApplicationContext
</param-value>
</context-param>
然後建立一個 /WEB-INF/applicationContext.groovy
檔案來完成剩下的工作
import org.apache.commons.dbcp.BasicDataSource
beans {
dataSource(BasicDataSource) {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:grailsDB"
username = "sa"
password = ""
}
}
從檔案系統載入 Bean 定義
你可以使用 BeanBuilder
類別載入外部 Groovy 程式碼,這些程式碼使用這裡定義的相同路徑比對語法來定義 bean。例如
def bb = new BeanBuilder()
bb.loadBeans("classpath:*SpringBeans.groovy")
def applicationContext = bb.createApplicationContext()
在這裡,BeanBuilder
會載入類別路徑上所有以 SpringBeans.groovy
結尾的 Groovy 檔案,並將它們解析成 bean 定義。範例程式碼如下所示
import org.apache.commons.dbcp.BasicDataSource
import org.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean
beans {
dataSource(BasicDataSource) {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:grailsDB"
username = "sa"
password = ""
}
sessionFactory(ConfigurableLocalSessionFactoryBean) {
dataSource = dataSource
hibernateProperties = ["hibernate.hbm2ddl.auto": "create-drop",
"hibernate.show_sql": "true"]
}
}
將變數新增到繫結(內容)
如果你從程式碼載入 bean,你可以建立一個 Groovy Binding
來設定要使用的繫結
def binding = new Binding()
binding.maxSize = 10000
binding.productGroup = 'finance'
def bb = new BeanBuilder()
bb.binding = binding
bb.loadBeans("classpath:*SpringBeans.groovy")
def ctx = bb.createApplicationContext()
然後你可以在 DSL 檔案中存取 maxSize
和 productGroup
屬性。
18.3 BeanBuilder DSL 說明
使用建構函數引數
建構函數引數可以使用每個定義 bean 的方法的參數來定義。將它們放在第一個引數(類別)之後
bb.beans {
exampleBean(MyExampleBean, "firstArgument", 2) {
someProperty = [1, 2, 3]
}
}
此設定對應到一個 MyExampleBean
,其建構函數如下所示
MyExampleBean(String foo, int bar) {
...
}
設定 BeanDefinition(使用工廠方法)
閉包的第一個引數是對 bean 設定實例的參照,你可以使用它來設定工廠方法,並呼叫 AbstractBeanDefinition 類別中的任何方法
bb.beans {
exampleBean(MyExampleBean) { bean ->
bean.factoryMethod = "getInstance"
bean.singleton = false
someProperty = [1, 2, 3]
}
}
或者,你也可以使用定義 bean 的方法的傳回值來設定 bean
bb.beans {
def example = exampleBean(MyExampleBean) {
someProperty = [1, 2, 3]
}
example.factoryMethod = "getInstance"
}
使用工廠 bean
Spring 定義了工廠 Bean 的概念,通常 Bean 不是直接從 Class 的新實例建立,而是從這些工廠建立。在這種情況下,Bean 沒有 Class 參數,而必須將工廠 Bean 的名稱傳遞給 Bean 定義方法
bb.beans {
myFactory(ExampleFactoryBean) {
someProperty = [1, 2, 3]
}
myBean(myFactory) {
name = "blah"
}
}
另一種常見的方法是提供工廠方法的名稱,以呼叫工廠 Bean。這可以使用 Groovy 的命名參數語法來完成
bb.beans {
myFactory(ExampleFactoryBean) {
someProperty = [1, 2, 3]
}
myBean(myFactory: "getInstance") {
name = "blah"
}
}
這裡會呼叫 ExampleFactoryBean
Bean 上的 getInstance
方法,以建立 myBean
Bean。
在執行階段建立 Bean 參照
有時候你不知道要在執行階段建立哪個 Bean 的名稱。在這種情況下,你可以使用字串內插法動態呼叫 Bean 定義方法
def beanName = "example"
bb.beans {
"${beanName}Bean"(MyExampleBean) {
someProperty = [1, 2, 3]
}
}
在這種情況下,呼叫 Bean 定義方法時會使用先前定義的 beanName
變數。範例有一個硬編碼值,但也可以使用根據設定、系統屬性等以程式方式產生的名稱。
此外,由於有時候 Bean 名稱在執行階段之前並不知道,因此在將其他 Bean 連結在一起時,你可能需要透過名稱參照它們,在這種情況下使用 ref
方法
def beanName = "example"
bb.beans {
"${beanName}Bean"(MyExampleBean) {
someProperty = [1, 2, 3]
}
anotherBean(AnotherBean) {
example = ref("${beanName}Bean")
}
}
這裡 AnotherBean
的範例屬性使用對 exampleBean
的執行階段參照來設定。ref
方法也可以用來參照 BeanBuilder
建構函式中提供的父 ApplicationContext
中的 Bean
ApplicationContext parent = ...//
def bb = new BeanBuilder(parent)
bb.beans {
anotherBean(AnotherBean) {
example = ref("${beanName}Bean", true)
}
}
這裡第二個參數 true
指定參照會在父內容中尋找 Bean。
使用匿名 (內部) Bean
你可以透過將 Bean 的屬性設定為區塊來使用匿名內部 Bean,該區塊會取得一個參數,即 Bean 類型
bb.beans {
marge(Person) {
name = "Marge"
husband = { Person p ->
name = "Homer"
age = 45
props = [overweight: true, height: "1.8m"]
}
children = [ref('bart'), ref('lisa')]
}
bart(Person) {
name = "Bart"
age = 11
}
lisa(Person) {
name = "Lisa"
age = 9
}
}
在上述範例中,我們將 marge
Bean 的丈夫屬性設定為建立內部 Bean 參照的區塊。或者,如果你有一個工廠 Bean,你可以省略類型,而只使用指定的 Bean 定義來設定工廠
bb.beans {
personFactory(PersonFactory)
marge(Person) {
name = "Marge"
husband = { bean ->
bean.factoryBean = "personFactory"
bean.factoryMethod = "newInstance"
name = "Homer"
age = 45
props = [overweight: true, height: "1.8m"]
}
children = [ref('bart'), ref('lisa')]
}
}
抽象 Bean 和父 Bean 定義
若要建立抽象 Bean 定義,請定義沒有 Class
參數的 Bean
class HolyGrailQuest {
def start() { println "lets begin" }
}
class KnightOfTheRoundTable {
String name
String leader
HolyGrailQuest quest
KnightOfTheRoundTable(String name) {
this.name = name
}
def embarkOnQuest() {
quest.start()
}
}
import grails.spring.BeanBuilder
def bb = new BeanBuilder()
bb.beans {
abstractBean {
leader = "Lancelot"
}
...
}
這裡我們定義一個抽象 Bean,它有一個 leader
屬性,其值為 "Lancelot"
。若要使用抽象 Bean,請將其設定為子 Bean 的父項
bb.beans {
...
quest(HolyGrailQuest)
knights(KnightOfTheRoundTable, "Camelot") { bean ->
bean.parent = abstractBean
quest = ref('quest')
}
}
使用父 Bean 時,你必須在設定 Bean 上的任何其他屬性之前設定 Bean 的父屬性! |
如果你想要一個指定 Class
的抽象 Bean,你可以這樣做
import grails.spring.BeanBuilder
def bb = new BeanBuilder()
bb.beans {
abstractBean(KnightOfTheRoundTable) { bean ->
bean.'abstract' = true
leader = "Lancelot"
}
quest(HolyGrailQuest)
knights("Camelot") { bean ->
bean.parent = abstractBean
quest = quest
}
}
在此範例中,我們建立一個抽象 bean,其類型為 KnightOfTheRoundTable
,並使用 bean 參數將其設定為抽象。稍後,我們定義一個沒有定義 Class
的 knights bean,但從父 bean 繼承 Class
。
使用 Spring 名稱空間
自 Spring 2.0 以來,Spring 使用者透過 XML 名稱空間更輕鬆地存取主要功能。您可以在 BeanBuilder 中使用 Spring 名稱空間,方法是使用此語法宣告它
xmlns context:"http://www.springframework.org/schema/context"
然後呼叫與 Spring 名稱空間標籤及其關聯屬性名稱相符的方法
context.'component-scan'('base-package': "my.company.domain")
您可以使用 Spring 名稱空間執行一些有用的操作,例如查詢 JNDI 資源
xmlns jee:"http://www.springframework.org/schema/jee"
jee.'jndi-lookup'(id: "dataSource", 'jndi-name': "java:comp/env/myDataSource")
此範例將透過對指定的 JNDI 名稱執行 JNDI 查詢,建立具有識別碼 dataSource
的 Spring bean。使用 Spring 名稱空間,您還可以從 BeanBuilder 完整存取 Spring 中所有強大的 AOP 支援。例如,給定這兩個類別
class Person {
int age
String name
void birthday() {
++age;
}
}
class BirthdayCardSender {
List peopleSentCards = []
void onBirthday(Person person) {
peopleSentCards << person
}
}
您可以定義一個切面,它使用切入點來偵測 birthday()
方法何時被呼叫
xmlns aop:"http://www.springframework.org/schema/aop"
fred(Person) {
name = "Fred"
age = 45
}
birthdayCardSenderAspect(BirthdayCardSender)
aop {
config("proxy-target-class": true) {
aspect(id: "sendBirthdayCard", ref: "birthdayCardSenderAspect") {
after method: "onBirthday",
pointcut: "execution(void ..Person.birthday()) and this(person)"
}
}
}
18.4 屬性佔位符組態
Grails 支援透過 Spring 的 PropertyPlaceholderConfigurer 延伸版本來進行屬性佔位符組態。
在 ConfigSlurper 腳本或 Java 屬性檔中定義的設定可以用作 grails-app/conf/spring/resources.xml
和 grails-app/conf/spring/resources.groovy
中 Spring 組態的佔位符值。例如,給定 grails-app/conf/application.groovy
(或外部化組態) 中的下列項目
database.driver="com.mysql.jdbc.Driver"
database.dbname="mysql:mydb"
然後,您可以使用熟悉的 ${..} 語法,如下所示在 resources.xml
中指定佔位符
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${database.driver}</value>
</property>
<property name="url">
<value>jdbc:${database.dbname}</value>
</property>
</bean>
要在 resources.groovy
中指定佔位符,您需要使用單引號
dataSource(org.springframework.jdbc.datasource.DriverManagerDataSource) {
driverClassName = '${database.driver}'
url = 'jdbc:${database.dbname}'
}
這會將屬性值設定為文字字串,稍後 Spring 的 PropertyPlaceholderConfigurer 會針對組態解析此字串。
resources.groovy
的較佳選項是透過 grailsApplication
變數存取屬性
dataSource(org.springframework.jdbc.datasource.DriverManagerDataSource) {
driverClassName = grailsApplication.config.getProperty('database.driver', String)
url = "jdbc\:${grailsApplication.config.getProperty('database.dbname', String)}"
}
使用此方法會讓型別維持在組態中定義的狀態。
18.5 屬性覆寫組態
Grails 支援透過 組態 設定 bean 屬性。
您定義一個 beans
區塊,其中包含 bean 的名稱及其值
beans {
bookService {
webServiceURL = "http://www.amazon.com"
}
}
一般格式為
<<bean name>>.<<property name>> = <<value>>
Java 屬性檔中的相同組態為
beans.bookService.webServiceURL=http://www.amazon.com
18.6 Spring Boot 致動器
Spring Boot Actuator 端點 讓您可以監控和與您的應用程式互動。Spring Boot 包含許多內建端點。例如,health
端點提供基本的應用程式健康資訊。
這些端點在 Grails 3.1.8 之後預設已停用。
您可以在 application.yml
中啟用 actuator 端點,如下所示
management:
endpoints:
enabled-by-default: true