在 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

然后在浏览器中打开 https://: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 标记模板引擎提供了一个基于构建器语法的创新模板系统。它提供各种主要功能

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

以及更多!您可以在文档中找到此模板引擎功能的完整列表。模板基本上是 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 文件的 doctype 声明,但您确实可以使用它来渲染任何内容。模板引擎提供了许多辅助函数,如 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")
    }
  }
}

persons 变量的循环是通过 Groovy 开发人员惯用的传统 each 方法完成的。这是正常的,因为模板实际上是 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>

因此,如果您习惯使用 JSP、GSP 和任何类似 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

当找到 mainBody() 指令时,使用 contents 块将触发布局内 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 2.3 中引入的新模板引擎变得非常容易使用。这个模板引擎提供了一种非常自然和强大的语法来生成任何类型的基于文本的内容。有关模板引擎功能的完整描述可以在 Groovy 文档中找到,使用相同技术的替代应用程序可以在 Spring Boot 示例中找到。

最后但同样重要的是,对这个模板引擎的原生支持将进入 Spring 4.1!所以期待未来 Groovy 带来更多的惊喜!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有