在 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 社群所有即將舉行的活動。

檢視所有