在 Spring Boot 中使用创新的 Groovy 模板引擎

工程 | Cédric Champeau | 2014 年 5 月 28 日 | ...

随着 Spring Boot 1.1.0.M2 的发布,对 新模板引擎 的支持也随之而来,该引擎由 Groovy 2.3 提供。在这篇文章中,我们将介绍使用这种引擎的好处,当然还有如何在 Boot 中使用它。

本文中的所有源代码都可以在 GitHub 上找到,所以请随意克隆仓库并尝试一下

git clone https://github.com/melix/springboot-groovytemplates.git
cd springboot-groovytemplates
./gradlew run

然后在浏览器中打开 http://localhost:8080

此应用程序完全用 Groovy 编写,并且还使用了 GORM for Boot,但当然也可以只将 Groovy 用于模板部分,而用 Java 编写应用程序的其余部分。从现在开始,我们将只关注此项目的模板方面。

依赖项

在 Spring Boot 中集成 Groovy 2.3 模板非常容易。您只需在构建文件中添加对 groovy-templates 模块的依赖项即可。例如,如果您使用 Gradle,只需使用以下内容

dependencies {
  compile "org.codehaus.groovy:groovy:${groovyVersion}"
  compile "org.codehaus.groovy:groovy-templates:${groovyVersion}"
  compile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
  compile "org.grails:gorm-hibernate4-spring-boot:1.0.0.RELEASE"

  runtime "com.h2database:h2:1.3.173"
}

Groovy 模板

Groovy 标记模板引擎提供了一个基于 builder 语法的创新模板系统。它提供各种关键功能

  • 用于生成类似 XML 内容(特别是 HTML5)的层级(builder)语法
  • 模板包含
  • 将模板编译为字节码以实现快速渲染
  • 国际化
  • 用于共享结构模式的布局机制
  • 可选的类型检查

等等!您可以在文档中找到此模板引擎功能的完整列表。这些模板基本上是带有特殊模板用例支持的 Groovy 代码。

让我们从一个非常简单的示例开始,在这个示例中,我们想显示一个包含当前正在使用的 Spring Boot 和 Groovy 版本号的简单消息的索引

yieldUnescaped '<!DOCTYPE html>'
html {
  head {
    title('Spring Boot - Groovy templates example')
    link(rel: 'stylesheet', href: '/css/bootstrap.min.css')
  }
  body {
    div(class: 'container') {
      div(class: 'navbar') {
        div(class: 'navbar-inner') {
          a(class: 'brand',
              href: 'http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html',
              'Groovy - Template Engine docs')
          a(class: 'brand',
              href: 'hhttp://projects.spring.io/spring-boot/') {
            yield 'Spring Boot docs'
          }
        }
      }
      div("This is an application using Boot $bootVersion and Groovy templates $groovyVersion")
    }
  }
}

在第一行,您可以看到 yieldUnescaped 指令。它指示渲染器将参数按原样渲染。此指令可用于渲染任何类型的基于文本的内容。在这里,它用于渲染我们 HTML 文件的文档类型声明,但您确实可以使用它来渲染任何内容。模板引擎提供了许多辅助函数,例如 yield,这些函数在文档中有描述。

模板的其余部分由与 HTML 输出匹配的层级结构组成,使得渲染 HTML 内容非常自然。例如,代码:link(rel: 'stylesheet', href: '/css/bootstrap.min.css') 将被渲染为

<link rel='stylesheet' href='/css/bootstrap.min.css'/>

类似地,这

a(class: 'brand',
  href: 'http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html',
  'Groovy - Template Engine docs')

将渲染为

<a class='brand' href='http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html'>Groovy - Template Engine docs</a>

请注意模板中的属性如何映射到渲染的 HTML 中的标签属性。最后一个参数对应于标签的主体。或者,也可以使用 yield 指令来渲染标签的主体

a(class: 'brand',
  href: 'http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html') {
  yield 'Groovy - Template Engine docs'
}

选择通常取决于您是否有嵌套内容要渲染。但到目前为止,我们的模板生成的所有内容都是静态的。模板的最后一部分更有趣

div("This is an application using Boot $bootVersion and Groovy templates $groovyVersion")

正如您猜到的,这将渲染为

<div>This is an application using Boot 1.1.0.M2 and Groovy templates 2.3.2</div>

这里的模板使用了模型中的两个变量

  • bootVersion
  • groovyVersion

这些变量由我们的应用程序作为模板中的变量公开,所以让我们看看我们是如何做到这一点的。

控制器

我们唯一需要做的就是创建一个将渲染我们视图的控制器,并且像往常一样使用 Spring Boot,这只需要几行代码

package sample

import org.springframework.boot.Banner
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.servlet.ModelAndView

@Controller
class SampleController {
  @RequestMapping("/")
  def home() {
    new ModelAndView(
        "views/home",
        [bootVersion: Banner.package.implementationVersion, 
         groovyVersion: GroovySystem.version])
  }
}

我们的 home 方法返回一个 ModelAndView 实例,模型中只包含两个元素,即 Spring Boot 版本和 Groovy 版本。该视图通过其引用 views/home 自动找到。Spring Boot 期望在 src/main/resources/templates/views 中找到该视图。这难道还不够简单吗?

实际数据

在实际生活中,模板不太可能如此简单。您会有实体、数据库、CRUD 操作等等... 所以下一步是向您展示如何使用新模板引擎来渲染更复杂的模型。为此,我们使用了 GORM,所以我们将首先定义一个名为 Person 的实体

package sample

import grails.persistence.*

@Entity
class Person {
  String firstName
  String lastName
}

而我们想做的事情例如是

  • 列出数据库中的人员
  • 添加/编辑新人员

所以我们将需要两个模板:一个用于列出人员,另一个用于创建(或编辑)人员。列表示例很有趣,因为它将让我们向您展示如何在模板中迭代列表。因此,在此之前,让我们创建一个带有列表操作的控制器

@Controller
@RequestMapping("/person")
class PersonController {

  @RequestMapping("list")
  def list() {
    new ModelAndView('views/person/list', [persons: Person.list()])
  }
}

您可以看到,与我们在简单示例中所做的类似,我们返回一个 ModelAndView 示例,但这次,模型包含一个人员列表。所以让我们看看模板是什么样子

yieldUnescaped '<!DOCTYPE html>'
html {
  head {
    title('Spring Boot - Groovy templates example')
    link(rel: 'stylesheet', href: '/css/bootstrap.min.css')
  }
  body {
    div(class: 'container') {
      div(class: 'navbar') {
        div(class: 'navbar-inner') {
          a(class: 'brand',
              href: 'http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html',
              'Groovy - Template Engine docs')
          a(class: 'brand',
              href: 'hhttp://projects.spring.io/spring-boot/') {
            yield 'Spring Boot docs'
          }
        }
      }

      ul {
        persons.each { person ->
          li {
            a(href:"/person/$person.id", "$person.lastName $person.firstName")
          }
        }
      }

      div {
        a(href:'/person/add', 'Add new person')
      }
    }
  }
}

模板的大部分实际上对应于页面的装饰,并从原始模板复制。此时,您可能想知道可以做些什么来改进这一点,但我们稍后再讨论,并专注于此模板中最有趣的部分,即迭代

ul {
  persons.each { person ->
    li {
      a(href: "/person/$person.id", "$person.lastName $person.firstName")
    }
  }
}

通过 Groovy 开发人员惯用的传统 each 方法来循环 persons 变量。这是正常的,因为模板实际上是 Groovy 代码!所以我们可以迭代人员列表,我们为迭代中的当前人员指定一个名称 (person),然后在 a 标签内使用它。

如果数据库中有几个人,结果 HTML 将如下所示

<ul>
  <li><a href='/person/1'>John Doe</a></li>
  <li><a href='/person/2'>Bob Dylan</a></li>
  <li><a href='/person/3'>Guillaume Laforge</a></li>
  <li><a href='/person/4'>Graeme Rocher</a></li>
  <li><a href='/person/5'>Dave Syer</a></li>
</ul>

因此,如果您习惯于使用 JSPs、GSPs 以及任何类似 HTML 的模板系统,您可以立即看到此模板引擎将使您摆脱必须处理开放/封闭标签的臭名昭著的问题。这仅仅是个开始... 为了说明如何简化事情,我们将向您介绍*布局*机制。

如果您还记得,我们实际上有两个共享通用结构的模板。它们都使用 Twitter Bootstrap,它们都共享相同的菜单,最终唯一改变的是页面标题和主体内容。如果我们能将这些内容从我们的模板中提取出来并共享会怎样?

引入布局

布局就是为此而生。所以让我们将模板的共同部分提取到一个 main.tpl 文件中,我们将它保存在 src/main/resources/templates/layouts

yieldUnescaped '<!DOCTYPE html>'
html {
  head {
    title(pageTitle)
    link(rel: 'stylesheet', href: '/css/bootstrap.min.css')
  }
  body {
    div(class: 'container') {
      div(class: 'navbar') {
        div(class: 'navbar-inner') {
          a(class: 'brand',
              href: 'http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html',
              'Groovy - Template Engine docs')
          a(class: 'brand',
              href: 'hhttp://projects.spring.io/spring-boot/') {
            yield 'Spring Boot docs'
          }
        }
      }
      mainBody()
    }
  }
}

这看起来与标准模板非常相似,但您实际上可以找到两个特殊之处

  • title(pageTitle) 其中 pageTitle 预期是我们要提供的页面标题
  • mainBody(),这将导致使用该布局的页面渲染主体内容。请注意括号的重要性。

现在让我们更新主页模板以使用此布局

layout 'layouts/main.tpl',
    pageTitle: 'Spring Boot - Groovy templates example with layout',
    mainBody: contents {
      div("This is an application using Boot $bootVersion and Groovy templates $groovyVersion")
    }

您可以看到我们调用了 layout 方法并提供了几个参数

  • 要使用的布局文件的名称 (layouts/main.tpl)
  • pageTitle,一个简单的字符串
  • mainBody,使用 contents

使用 contents 块将触发在布局中找到 mainBody() 指令时渲染 mainBody 的内容。因此,使用此布局文件,我们确实在多个模板之间共享了一个通用的结构模式。作为一个示例,让我们看看 list.tpl 模板现在是什么样子

layout 'layouts/main.tpl',
    pageTitle: 'List persons',
    mainBody: contents {
      ul {
        persons.each { person ->
          li {
            a(href:"/person/$person.id", "$person.lastName $person.firstName")
          }
        }
      }

      div {
        a(href:'/person/add', 'Add new person')
      }
    }

当然,布局本身是可组合的,所以您可以在布局中嵌套布局...

结论

在本文中,我们向您展示了 Spring Boot 如何轻松地使用 Groovy 在 Groovy 2.3 中引入的新模板引擎。这个模板引擎提供了一种非常自然且强大的语法来生成任何类型的基于文本的内容。有关模板引擎功能的完整描述可以在Groovy 文档中找到,并且可以在Spring Boot 示例中找到使用相同技术的另一个应用程序。

最后但同样重要的是,Spring 4.1 即将原生支持此模板引擎!所以未来请期待更多 Groovy 的精彩!

获取 Spring 通讯

订阅 Spring 通讯保持连接

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,一站式订阅。

了解更多

即将到来的活动

查看 Spring 社区所有即将到来的活动。

查看全部