beans = {
// beans here
}
18 Grails 和 Spring
版本 6.2.0
目录
18 Grails 和 Spring
此部分适用于高级用户和有兴趣了解 Grails 如何与 Spring 框架 集成以及在 Spring 框架基础上构建的那些用户。对于正在考虑对 Grails 执行运行时配置的插件开发者来说,此部分也十分有用。
18.1 配置附加 Bean
借助 Spring Bean DSL
通过在 grails-app/conf/spring/resources.groovy
中配置 Bean,您可以轻松注册新的(或覆盖现有的)Bean,该文件使用 Grails Spring DSL。Bean 在 beans
属性(一个闭包)内定义
作为示例,您可以使用以下语法配置 Bean
import my.company.MyBeanImpl
beans = {
myBean(MyBeanImpl) {
someProperty = 42
otherProperty = "blue"
}
}
配置后,可以通过声明一个名为 Bean 名称(在此例中为 myBean
)的公共字段,将 Bean 自动注入到 Grails 构件和其他支持依赖注入的类(例如 BootStrap.groovy
和集成测试)
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 文件中定义一个名为先前由 Grails 或已安装插件注册的 Bean,那么您的 Bean 将替换先前的注册。这是一种自定义行为的便捷方法,无需编辑插件代码或采用其他会影响可维护性的方法。 |
借助 XML
Bean 还可以使用 grails-app/conf/spring/resources.xml
进行配置。在 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 需要一个公共设置器来设置 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
引用看到这一点。
正如在以下代码中所示,Builder 还可以设置与 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
类加载使用此处定义的相同路径匹配语法定义 Bean 的外部 Groovy 脚本。例如
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 的概念,经常从这些工厂而非 Class 的新实例直接创建 bean。在这种情况下,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 名称,因此在使用 ref 方法连接其他 bean 时可能需要按名称引用它们
def beanName = "example"
bb.beans {
"${beanName}Bean"(MyExampleBean) {
someProperty = [1, 2, 3]
}
anotherBean(AnotherBean) {
example = ref("${beanName}Bean")
}
}
此处使用对 exampleBean 的运行时引用设置 AnotherBean 的示例属性。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,它具有值为 "Lancelot" 的 leader 属性。要使用抽象 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
}
}
在此示例中,我们创建了一个类型为 KnightOfTheRoundTable 的抽象 bean,并使用 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 属性。
你用 Bean 名称及其值定义一个 beans
块
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 执行器端点 允许你监控和与你的应用程序进行交互。Spring Boot 包含了许多内置端点。例如,health
端点提供基本的应用程序运行状况信息。
从 Grails 3.1.8 起,默认情况下禁用这些端点。
你可以按照如下方式在你的 application.yml
中启用执行器端点
management:
endpoints:
enabled-by-default: true