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

查看所有