Groovy 模版引擎
1. Introduction
Groovy supports multiple ways to generate text dynamically including GStrings, printf and MarkupBuilder just to name a few. In addition to these, there is a dedicated template framework which is well-suited to applications where the text to be generated follows the form of a static template.
2. Template framework
The template framework in Groovy consists of a TemplateEngine abstract base class that engines must implement and a Template interface that the resulting templates they generate must implement.
Included with Groovy are several template engines:
- SimpleTemplateEngine - for basic templates
- StreamingTemplateEngine - functionally equivalent to SimpleTemplateEngine, but can handle strings larger than 64k
- GStringTemplateEngine - stores the template as writeable closures (useful for streaming scenarios)
- XmlTemplateEngine - works well when the template and output are valid XML
- MarkupTemplateEngine - a very complete, optimized, template engine
3. SimpleTemplateEngine
Shown here is the SimpleTemplateEngine that allows you to use JSP-like scriptlets (see example below), script, and EL expressions in your template in order to generate parametrized text. Here is an example of using the system:
def text = 'Dear "$firstname $lastname",\nSo nice to meet you in <% print city %>.\nSee you in ${month},\n${signed}'
def binding = ["firstname":"Sam", "lastname":"Pullara", "city":"San Francisco", "month":"December", "signed":"Groovy-Dev"]
def engine = new groovy.text.SimpleTemplateEngine()
def template = engine.createTemplate(text).make(binding)
def result = 'Dear "Sam Pullara",\nSo nice to meet you in San Francisco.\nSee you in December,\nGroovy-Dev'
assert result == template.toString()
While it is generally not deemed good practice to mix processing logic in your template (or view), sometimes very simple logic can be useful. E.g. in the example above, we could change this:
$firstname
to this (assuming we have set up a static import for capitalize inside the template):
${firstname.capitalize()}
or this:
<% print city %>
to this:
<% print city == "New York" ? "The Big Apple" : city %>
3.1. Advanced Usage Note
If you happen to be embedding your template directly in your script (as we did above) you have to be careful about backslash escaping. Because the template string itself will be parsed by Groovy before it is passed to the the templating framework, you have to escape any backslashes inside GString expressions or scriptlet 'code' that are entered as part of a Groovy program. E.g. if we wanted quotes around The Big Apple above, we would use:
<% print city == "New York" ? "\\"The Big Apple\\"" : city %>
Similarly, if we wanted a newline, we would use:
\\n
in any GString expression or scriptlet 'code' that appears inside a Groovy script. A normal "\n" is fine within the static template text itself or if the entire template itself is in an external template file. Similarly, to represent an actual backslash in your text you would need
\\\\
in an external file or
\\\\
in any GString expression or scriptlet 'code'. (Note: the necessity to have this extra slash may go away in a future version of Groovy if we can find an easy way to support such a change.)
4. StreamingTemplateEngine
The StreamingTemplateEngine engine is functionally equivalent to the SimpleTemplateEngine, but creates the template using writeable closures making it more scalable for large templates. Specifically this template engine can handle strings larger than 64k.
It uses JSP style <% %> script and <%= %> expression syntax or GString style expressions. The variable 'out' is bound to the writer that the template is being written to.
Frequently, the template source will be a file but here we show a simple example providing the template as a string:
def text = '''\
Dear <% out.print firstname %> ${lastname},
We <% if (accepted) out.print 'are pleased' else out.print 'regret' %> \
to inform you that your paper entitled
'$title' was ${ accepted ? 'accepted' : 'rejected' }.
The conference committee.'''
def template = new groovy.text.StreamingTemplateEngine().createTemplate(text)
def binding = [
firstname : "Grace",
lastname : "Hopper",
accepted : true,
title : 'Groovy for COBOL programmers'
]
String response = template.make(binding)
assert response == '''Dear Grace Hopper,
We are pleased to inform you that your paper entitled
'Groovy for COBOL programmers' was accepted.
The conference committee.'''
5. GStringTemplateEngine
As an example of using the GStringTemplateEngine, here is the example above done again (with a few changes to show some other options). First we will store the template in a file this time:
test.template
Dear "$firstname $lastname",
So nice to meet you in <% out << (city == "New York" ? "\\"The Big Apple\\"" : city) %>.
See you in ${month},
${signed}
Note that we used out instead of print to support the streaming nature of GStringTemplateEngine. Because we have the template in a separate file, there is no need to escape the backslashes. Here is how we call it:
def f = new File('test.template')
def engine = new groovy.text.GStringTemplateEngine()
def template = engine.createTemplate(f).make(binding)
println template.toString()
and here is the output:
Dear "Sam Pullara",
So nice to meet you in "The Big Apple".
See you in December,
Groovy-Dev
6. XmlTemplateEngine
XmlTemplateEngine for use in templating scenarios where both the template source and the expected output are intended to be XML. Templates may use the normal ${expression} and $variable notations to insert an arbitrary expression into the template. In addition, support is also provided for special tags: <gsp:scriptlet> (for inserting code fragments) and <gsp:expression> (for code fragments which produce output).
Comments and processing instructions will be removed as part of processing and special XML characters such as <, >, " and ' will be escaped using the respective XML notation. The output will also be indented using standard XML pretty printing.
The xmlns namespace definition for gsp: tags will be removed but other namespace definitions will be preserved (but may change to an equivalent position within the XML tree).
Normally, the template source will be in a file but here is a simple example providing the XML template as a string:
def binding = [firstname: 'Jochen', lastname: 'Theodorou', nickname: 'blackdrag', salutation: 'Dear']
def engine = new groovy.text.XmlTemplateEngine()
def text = '''\
<document xmlns:gsp='http://groovy.codehaus.org/2005/gsp' xmlns:foo='baz' type='letter'>
<gsp:scriptlet>def greeting = "${salutation}est"</gsp:scriptlet>
<gsp:expression>greeting</gsp:expression>
<foo:to>$firstname "$nickname" $lastname</foo:to>
How are you today?
</document>
'''
def template = engine.createTemplate(text).make(binding)
println template.toString()
This example will produce this output:
<document type='letter'>
Dearest
<foo:to xmlns:foo='baz'>
Jochen "blackdrag" Theodorou
</foo:to>
How are you today?
</document>
7. The MarkupTemplateEngine
This template engine is a template engine primarily aimed at generating XML-like markup (XML, XHTML, HTML5, …), but that can be used to generate any text based content. Unlike traditional template engines, this one relies on a DSL that uses the builder syntax. Here is a sample template:
xmlDeclaration()
cars {
cars.each {
car(make: it.make, model: it.model)
}
}
If you feed it with the following model:
model = [cars: [new Car(make: 'Peugeot', model: '508'), new Car(make: 'Toyota', model: 'Prius')]]
It would be rendered as:
<?xml version='1.0'?>
<cars><car make='Peugeot' model='508'/><car make='Toyota' model='Prius'/></cars>
The key features of this template engine are:
- a markup builder like syntax
- templates are compiled into bytecode
- fast rendering
- optional type checking of the model
- includes
- internationalization support
- fragments/layouts
7.1. The template format
7.1.1. Basics
Templates consist of Groovy code. Let's explore the first example more thoroughly:
xmlDeclaration()
cars {
cars.each {
car(make: it.make, model: it.model)
}
}
renders the XML declaration string. |
|
opens a cars tag |
|
cars is a variable found in the template model, which is a list of Car instances |
|
for each item, we create a car tag with the attributes from the Car instance |
|
closes the cars tag |
As you can see, regular Groovy code can be used in the template. Here, we are calling each on a list (retrieved from the model), allowing us to render one car tag per entry.
In a similar fashion, rendering HTML code is as simple as this:
yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
head {
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
title('My page')
}
body {
p('This is an example of HTML contents')
}
}
renders the HTML doctype special tag |
|
opens the html tag with an attribute |
|
opens the head tag |
|
renders a meta tag with one http-equiv attribute |
|
renders the title tag |
|
closes the head tag |
|
opens the body tag |
|
renders a p tag |
|
closes the body tag |
|
closes the html tag |
The output is straightforward:
<!DOCTYPE html><html lang='en'><head><meta http-equiv='"Content-Type" content="text/html; charset=utf-8"'/><title>My page</title></head><body><p>This is an example of HTML contents</p></body></html>
With some configuration, you can have the output pretty printed, with newlines and indent automatically added. |
7.1.2. Support methods
In the previous example, the doctype declaration was rendered using the yieldUnescaped method. We have also seen the xmlDeclaration method. The template engine provides several support methods that will help you render contents appropriately:
Method |
Description |
Example |
yield |
Renders contents, but escapes it before rendering |
Template: yield 'Some text with <angle brackets>' Output: Some text with <angle brackets> |
yieldUnescaped |
Renders raw contents. The argument is rendered as is, without escaping. |
Template: yieldUnescaped 'Some text with <angle brackets>' Output: Some text with <angle brackets> |
xmlDeclaration |
Renders an XML declaration String. If the encoding is specified in the configuration, it is written in the declaration. |
Template: xmlDeclaration() Output: <?xml version='1.0'?> If TemplateConfiguration#getDeclarationEncoding is not null: Output: <?xml version='1.0' encoding='UTF-8'?> |
comment |
Renders raw contents inside an XML comment |
Template: comment 'This is <a href='http://docs.groovy-lang.org/latest/html/documentation/foo.html'>commented out</a>' Output: <!--This is <a href='http://docs.groovy-lang.org/latest/html/documentation/foo.html'>commented out</a>--> |
newLine |
Renders a new line. See also TemplateConfiguration#setAutoNewLine and TemplateConfiguration#setNewLineString. |
Template: p('text') newLine() p('text on new line') Output: <p>text</p> <p>text on new line</p> |
pi |
Renders an XML processing instruction. |
Template: pi("xml-stylesheet":[href:"mystyle.css", type:"text/css"]) Output: <?xml-stylesheet href='mystyle.css' type='text/css'?> |
tryEscape |
Returns an escaped string for an object, if it is a String (or any type derived from CharSequence). Otherwise returns the object itself. |
Template: yieldUnescaped tryEscape('Some text with <angle brackets>') Output: Some text with <angle brackets> |
7.1.3. Includes
The MarkupTemplateEngine supports inclusion of contents from another file. Included contents may be:
- another template
- raw contents
- contents to be escaped
Including another template can be done using:
include template: 'other_template.tpl'
Including a file as raw contents, without escaping it, can be done like this:
include unescaped: 'raw.txt'
Eventually, inclusion of text that should be escaped before rendering can be done using:
include escaped: 'to_be_escaped.txt'
Alternatively, you can use the following helper methods instead:
- includeGroovy(<name>) to include another template
- includeEscaped(<name>) to include another file with escaping
- includeUnescaped(<name>) to include another file without escaping
Calling those methods instead of the include xxx: syntax can be useful if the name of the file to be included is dynamic (stored in a variable for example). Files to be included (independently of their type, template or text) are found on classpath. This is one of the reasons why the MarkupTemplateEngine takes an optional ClassLoader as constructor argument (the other reason being that you can include code referencing other classes in a template).
If you don't want your templates to be on classpath, the MarkupTemplateEngine accepts a convenient constructor that lets you define the directory where templates are to be found.
7.1.4. Fragments
Fragments are nested templates. They can be used to provide improved composition in a single template. A fragment consists of a string, the inner template, and a model, used to render this template. Consider the following template:
ul {
pages.each {
fragment "li(line)", line:it
}
}
The fragment element creates a nested template, and renders it with a model which is specific to this template. Here, we have the li(line) fragment, where line is bound to it. Since it corresponds to the iteration of pages, we will generate a single li element for each page in our model:
<ul><li>Page 1</li><li>Page 2</li></ul>
Fragments are interesting to factorize template elements. They come at the price of the compilation of a fragment per template, and they cannot be externalized.
7.1.5. Layouts
Layouts, unlike fragments, refer to other templates. They can be used to compose templates and share common structures. This is often interesting if you have, for example, a common HTML page setup, and that you only want to replace the body. This can be done easily with a layout. First of all, you need to create a layout template:
layout-main.tpl
html {
head {
title(title)
}
body {
bodyContents()
}
}
the title variable (inside the title tag) is a layout variable |
|
the bodyContents call will render the body |
Then what you need is a template that includes the layout:
layout 'layout-main.tpl',
title: 'Layout example',
bodyContents: contents { p('This is the body') }
use the main-layout.tpl layout file |
|
set the title variable |
|
set the bodyContents |
As you can see, bodyContents will be rendered inside the layout, thanks to the bodyContents() call in the layout file. As a result, the template will be rendered as this:
<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>
The call to the contents method is used to tell the template engine that the block of code is in fact a specification of a template, instead of a helper function to be rendered directly. If you don't add contents before your specification, then the contents would be rendered, but you would also see a random string generated, corresponding to the result value of the block.
Layouts are a powerful way to share common elements across multiple templates, without having to rewrite everything or use includes.
Layouts use, by default, a model which is independent from the model of the page where they are used. It is however possible to make them inherit from the parent model. Imagine that the model is defined like this:
model = new HashMap<String,Object>();
model.put('title','Title from main model');
and the following template:
layout 'layout-main.tpl', true,
bodyContents: contents { p('This is the body') }
note the use of true to enable model inheritance |
then it is not necessary to pass the title value to the layout as in the previous example. The result will be:
<html><head><title>Title from main model</title></head><body><p>This is the body</p></body></html>
But it is also possible to override a value from the parent model:
layout 'layout-main.tpl', true,
title: 'Overriden title',
bodyContents: contents { p('This is the body') }
true means inherit from the parent model |
|
but title is overriden |
then the output will be:
<html><head><title>Overriden title</title></head><body><p>This is the body</p></body></html>
7.2. Rendering contents
7.2.1. Creation of a template engine
On the server side, rendering templates require an instance of groovy.text.markup.MarkupTemplateEngine and a groovy.text.markup.TemplateConfiguration:
TemplateConfiguration config = new TemplateConfiguration();
MarkupTemplateEngine engine = new MarkupTemplateEngine(config);
Template template = engine.createTemplate("p('test template')");
Map<String, Object> model = new HashMap<>();
Writable output = template.make(model);
output.writeTo(writer);
creates a template configuration |
|
creates a template engine with this configuration |
|
creates a template instance from a String |
|
creates a model to be used in the template |
|
bind the model to the template instance |
|
render output |
There are several possible options to parse templates:
- from a String, using createTemplate(String)
- from a Reader, using createTemplate(Reader)
- from a URL, using createTemplate(URL)
- given a template name, using createTemplateByPath(String)
The last version should in general be preferred:
Template template = engine.createTemplateByPath("main.tpl");
Writable output = template.make(model);
output.writeTo(writer);
7.2.2. Configuration options
The behavior of the engine can be tweaked with several configuration options accessible through the TemplateConfiguration class:
Option |
Default value |
Description |
Example |
declarationEncoding |
null |
Determines the value of the encoding to be written when xmlDeclaration is called. It does not influence the writer you are using as output. |
Template: xmlDeclaration() Output: <?xml version='1.0'?> If TemplateConfiguration#getDeclarationEncoding is not null: Output: <?xml version='1.0' encoding='UTF-8'?> |
expandEmptyElements |
false |
If true, empty tags are rendered in their expanded form. |
Template: p() Output: <p/> If expandEmptyElements is true: Output: <p></p> |
useDoubleQuotes |
false |
If true, use double quotes for attributes instead of simple quotes |
Template: tag(attr:'value') Output: <tag attr='value'/> If useDoubleQuotes is true: Output: <tag attr="value"/> |
newLineString |
System default (system property line.separator) |
Allows to choose what string is used when a new line is rendered |
Template: p('foo') newLine() p('baz') If newLineString='BAR': Output: <p>foo</p>BAR<p>baz</p> |
autoEscape |
false |
If true, variables from models are automatically escaped before rendering. |
|
autoIndent |
false |
If true, performs automatic indentation after new lines |
|
autoIndentString |
four (4) spaces |
The string to be used as indent. |
|
autoNewLine |
false |
If true, performs automatic insertion of new lines |
|
baseTemplateClass |
groovy.text.markup.BaseTemplate |
Sets the super class of compiled templates. This can be used to provide application specific templates. |
|
locale |
Default locale |
Sets the default locale for templates. |
Once the template engine has been created, it is unsafe to change the configuration. |
7.2.3. Automatic formatting
By default, the template engine will render output without any specific formatting. Some configuration options can improve the situation:
- autoIndent is responsible for auto-indenting after a new line is inserted
- autoNewLine is responsible for automatically inserting new lines based on the original formatting of the template source
In general, it is recommended to set both autoIndent and autoNewLine to true if you want human-readable, pretty printed, output:
config.setAutoNewLine(true);
config.setAutoIndent(true);
Using the following template:
html {
head {
title('Title')
}
}
The output will now be:
<html>
<head>
<title>Title</title>
</head>
</html>
We can slightly change the template so that the title intruction is found on the same line as the head one:
html {
head { title('Title')
}
}
And the output will reflect that:
<html>
<head><title>Title</title>
</head>
</html>
New lines are only inserted where curly braces for tags are found, and the insertion corresponds to where the nested content is found. This means that tags in the body of another tag will not trigger new lines unless they use curly braces themselves:
html {
head {
meta(attr:'value')
title('Title')
newLine()
meta(attr:'value2')
}
}
a new line is inserted because meta is not on the same line as head |
|
no new line is inserted, because we're on the same depth as the previous tag |
|
we can force rendering of a new line by explicitly calling newLine |
|
and this tag will be rendered on a separate line |
This time, the output will be:
<html>
<head>
<meta attr='value'/><title>Title</title>
<meta attr='value2'/>
</head>
</html>
By default, the renderer uses four(4) spaces as indent, but you can change it by setting the TemplateConfiguration#autoIndentString property.
7.2.4. Automatic escaping
By default, contents which is read from the model is rendered as is. If this contents comes from user input, it can be sensible, and you might want to escape it by default, for example to avoid XSS injection. For that, the template configuration provides an option which will automatically escape objects from the model, as long as they inherit from CharSequence (typically, `String`s).
Let's imagine the following setup:
config.setAutoEscape(false);
model = new HashMap<String,Object>();
model.put("unsafeContents", "I am an <html> hacker.");
and the following template:
html {
body {
div(unsafeContents)
}
}
Then you wouldn't want the HTML from unsafeContents to be rendered as is, because of potential security issues:
<html><body><div>I am an <html> hacker.</div></body></html>
Automatic escaping will fix this:
config.setAutoEscape(true);
And now the output is properly escaped:
<html><body><div>I am an <html> hacker.</div></body></html>
Note that using automatic escaping doesn't prevent you from including unescaped contents from the model. To do this, your template should then explicitly mention that a model variable should not be escaped by prefixing it with unescaped., like in this example:
Explicit bypass of automatic escaping
html {
body {
div(unescaped.unsafeContents)
}
}
7.2.5. Common gotchas
Strings containing markup
Say that you want to generate a <p> tag which contains a string containing markup:
p {
yield "This is a "
a(href:'target.html', "link")
yield " to another page"
}
and generates:
<p>This is a <a href='target.html'>link</a> to another page</p>
Can't this be written shorter? A naive alternative would be:
p {
yield "This is a ${a(href:'target.html', "link")} to another page"
}
but the result will not look as expected:
<p><a href='target.html'>link</a>This is a to another page</p>
The reason is that the markup template engine is a streaming engine. In the original version, the first yield call generates a string which is streamed to the output, then the a link is generated and streamed, and then the last yield call is streamed, leading in an execution in order. But with the string version above, the order of execution is different:
- the yield call requires an argument, a string
- that arguments needs to be evaluated before the yield call is generated
so evaluating the string leads to an execution of the a(href:…) call before yield is itself called. This is not what you want to do. Instead, you want to generate a string which contains markup, which is then passed to the yield call. This can be done this way:
p("This is a ${stringOf {a(href:'target.html', "link")}} to another page")
Note the stringOf call, which basically tells the markup template engine that the underlying markup needs to be rendered separately and exported as a string. Note that for simple expressions, stringOf can be replaced by an alternate tag notation that starts with a dollar sign:
p("This is a ${$a(href:'target.html', "link")} to another page")
It is worth noting that using stringOf or the special $tag notation triggers the creation of a distinct string writer which is then used to render the markup. It is slower than using the version with calls to yield which perform direct streaming of the markup instead. |
7.2.6. Internationalization
The template engine has native support for internationalization. For that, when you create the TemplateConfiguration, you can provide a Locale which is the default locale to be used for templates. Each template may have different versions, one for each locale. The name of the template makes the difference:
- file.tpl: default template file
- file_fr_FR.tpl: french version of the template
- file_en_US.tpl: american english version of the template
- …
When a template is rendered or included, then:
- if the template name or include name explicitly sets a locale, the specific version is included, or the default version if not found
- if the template name doesn't include a locale, the version for the TemplateConfiguration locale is used, or the default version if not found
For example, imagine the default locale is set to Locale.ENGLISH and that the main template includes:
Use an explicit locale in include
include template: 'locale_include_fr_FR.tpl'
then the template is rendered using the specific template:
Bypass the template configuration
Texte en français
Using an include without specifying a locale will make the template engine look for a template with the configured locale, and if not, fallback to the default, like here:
Don't use a locale in include
include template: 'locale_include.tpl'
Fallback to the default template
Default text
However, changing the default locale of the template engine to Locale.FRANCE will change the output, because the template engine will now look for a file with the fr_FR locale:
Don't fallback to the default template because a locale specific template was found
Texte en français
This strategy lets you translate your templates one by one, by relying on default templates, for which no locale is set in the file name.
7.2.7. Custom template classes
By default, templates created inherit the groovy.text.markup.BaseTemplate class. It may be interesting for an application to provide a different template class, for example to provide additional helper methods which are aware of the application, or customized rendering primitives (for HTML, for example).
The template engine provides this ability by setting an alternative baseTemplateClass in the TemplateConfiguration:
config.setBaseTemplateClass(MyTemplate.class);
The custom base class has to extend BaseClass like in this example:
public abstract class MyTemplate extends BaseTemplate {
private List<Module> modules
public MyTemplate(
final MarkupTemplateEngine templateEngine,
final Map model,
final Map<String, String> modelTypes,
final TemplateConfiguration configuration) {
super(templateEngine, model, modelTypes, configuration)
}
List<Module> getModules() {
return modules
}
void setModules(final List<Module> modules) {
this.modules = modules
}
boolean hasModule(String name) {
modules?.any { it.name == name }
}
}
This example shows a class which provides an additional method named hasModule, which can then be used directly in the template:
if (hasModule('foo')) {
p 'Found module [foo]'
} else {
p 'Module [foo] not found'
}
7.3. Type checked templates
7.3.1. Optional type checking
Even if templates are not type checked, they are statically compiled. This means that once the templates are compiled, performance should be very good. For some applications, it might be good to make sure that templates are valid before they are actually rendered. This means failing template compilation, for example, if a method on a model variable doesn't exist.
The MarkupTemplateEngine provides such a facility. Templates can be optionally type checked. For that, the developer must provide additional information at template creation time, which is the types of the variables found in the model. Imagine a model exposing a list of pages, where a page is defined as:
Page.groovy
public class Page {
Long id
String title
String body
}
Then a list of pages can be exposed in the model, like this:
Page p = new Page();
p.setTitle("Sample page");
p.setBody("Page body");
List<Page> pages = new LinkedList<>();
pages.add(p);
model = new HashMap<String,Object>();
model.put("pages", pages);
A template can use it easily:
pages.each { page ->
p("Page title: $page.title")
p(page.text)
}
iterate on pages from the model |
|
page.title is valid |
|
page.text is not (should be page.body) |
Without type checking, the compilation of the template succeeds, because the template engine doesn't know about the model until a page is actually rendered. This means that the problem would only surface at runtime, once the page is rendered:
Runtime error
No such property: text
In some situations, this can be complicated to sort out or even notice. By declaring the type of the pages to the template engine, we're now capable of failing at compile time:
modelTypes = new HashMap<String,String>();
modelTypes.put("pages", "List<Page>");
Template template = engine.createTypeCheckedModelTemplate("main.tpl", modelTypes)
create a map which will hold the model types |
|
declare the type of the pages variables (note the use of a string for the type) |
|
use createTypeCheckedModelTemplate instead of createTemplate |
This time, when the template is compiled at the last line, an error occurs:
Template compilation time error
[Static type checking] - No such property: text for class: Page
This means that you don't need to wait for the page to be rendered to see an error. The use of createTypeCheckedModelTemplate is mandatory.
7.3.2. Alternative declaration of types
Alternatively, if the developer is also the one who writes the templates, it is possible to declare the types of the expected variables directly in the template. In this case, even if you call createTemplate, it will be type checked:
Inline declaration of types
modelTypes = {
List<Page> pages
}
pages.each { page ->
p("Page title: $page.title")
p(page.text)
}
types need to be declared in the modelTypes header |
|
declare one variable per object in the model |
7.3.3. Performance of type checked templates
An additional interest of using type checked models is that performance should improve. By telling the type checker what are the expected types, you also let the compiler generate optimized code for that, so if you are looking for the best performance, consider using type checked templates.
8. Other solutions
Also, there are other templating solutions that can be used along with Groovy, such as FreeMarker, Velocity, StringTemplate and others.
Groovy 模版引擎的更多相关文章
- Spring Boot (二):模版引擎 Thymeleaf 渲染 Web 页面
Spring Boot (二):模版引擎 Thymeleaf 渲染 Web 页面 在<Spring Boot(一):快速开始>中介绍了如何使用 Spring Boot 构建一个工程,并且提 ...
- 构建自己的PHP框架--构建模版引擎(1)
前段时间太忙,导致好久都没有更新博客了,今天抽出点时间来写一篇. 其实这个系列的博客很久没有更新了,之前想好好规划一下,再继续写,然后就放下了,今天再捡起来继续更新. 今天我们来说一下,如何构建自己的 ...
- Smarty模版引擎的原理
Smarty是一个使用php写出来的模版引擎,用来将原本与html代码混杂在一起PHP代码逻辑分离,实现前后端分离. Smarty模板优点: 1. 速度:采用Smarty编写的程序可以获得最大速度的提 ...
- js模版引擎handlebars.js实用教程——为什么选择Handlebars.js
返回目录 据小菜了解,对于java开发,涉及到页面展示时,比较主流的有两种解决方案: 1. struts2+vo+el表达式. 这种方式,重点不在于struts2,而是vo和el表达式,其基本思想是: ...
- 简单JavaScript模版引擎优化
在上篇博客最简单的JavaScript模板引擎 说了一下一个最简单的JavaScript模版引擎的原理与实现,作出了一个简陋的版本,今天优化一下,使之能够胜任日常拼接html工作,先把上次写的模版函数 ...
- Symfony2模版引擎使用说明手册
一.基本使用 {{ demo }}输出一个demo变量; {% func %}通常是包含一个twig函数例如 for; 举个for循环的例子: {% for i in 0..10 %} <em& ...
- Asp.net NVelocity 模版引擎
NVelocity.dll是Java中常用的一个模版,下面是常用的模版引擎 1,返回string类型的html代码 /// <summary> /// 获取html模版 /// </ ...
- PHP模版引擎 – Twig
在网站开发过程中模版引擎是必不可少的,PHP中用的最多的当属Smarty了.目前公司系统也是用的Smarty,如果要新增一个页面只需把网站的头.尾和左侧公共部分通过Smarty的include方式引入 ...
- Nodejs学习笔记(五)--- Express安装入门与模版引擎ejs
目录 前言 Express简介和安装 运行第一个基于express框架的Web 模版引擎 ejs express项目结构 express项目分析 app.set(name,value) app.use ...
随机推荐
- Python: 字典的基本操作
字典是Python里唯一的映射类型.字典是可变的.无序的.大小可变的键值映射,有时候也称为散列表或关联数组. 例子在下面: dic = {"apple":2, "oran ...
- 为控件Button设置快捷键(组合键)
控件MenuStrip和ContextMenuStrip可通过ShortCcutKeys属性设置快捷键,而控件Button没有ShortcutKey属性,如何为控件Button设置快捷键呢(组合件键) ...
- solrCloud的两种部署方式
solrcloud 的部署其实有两种方式可选,那么我们在实践开发中应该怎样选择呢? 第一种:当启动solr服务器时,内嵌的启动一个Zookeeper服务器,然后将这些内嵌的Zookeeper服务器组成 ...
- Java编写最大公约数和最小公倍数
package javaapplication24; class NegativeIntegerException extends Exception{ String message; public ...
- vs2012中将图片放到resource中进行调用
1.在项目中新建一个名叫resource的文件夹,然后将所需图片信息放入该文件夹,如图 2.右击该项目,选择属性->资源选项卡,步骤如图所示 点击添加现有文件,然后找到你刚刚添加的resourc ...
- AJAX原生JS代码
var http_request = false;function send_request(method,url,content,responseType,callback){ http_reque ...
- 国内npm镜像源推荐及使用
NPM(Node Package Manager),是NodeJs的模块依赖管理工具.由于Npm源在国外,使用起来不方便, 故需要国内可靠的npm源可以使用,现整理如下: 一.国内镜像 1.淘宝NPM ...
- SVN-修改已提交的日志
前提:服务器是Windows下的VisualSVN Server 1.在库的属性页面的Hooks选项卡下找到Pre-revision property change hook,2.双击修改其内容,具体 ...
- Unknown column 'a.root_id' in 'where clause'解决方法
最近遇到一个问题, update sy_user as a inner join sy_user_charge_log as b on b.username=a.username set recon ...
- StartFP
1.INODS执行完成时间为13:06:04分, 从日志信息无法知道STARTFP执行到哪一步 从INODS执行完成时间可知道startFp执行时间为13:06:05分开始, 执行StartFP中的e ...