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 社区中所有即将举行的活动。

查看全部