Mustache模板技术,一个比freemarker轻量级的模板引擎

时间:2022-11-02 20:15:40

一、初识Mustache

同样也是看Dropwizard才知道这个东西的,以前一直是知道诸如freemarker这样的模板引擎,这个是头一次听说,但是听周围的朋友说最早这个东西是出自于JS的,Dropwizard推荐使用这个东西,而且到mustache官网看了一下,发现十几种语言已经支持这个模板引擎技术,火热程度甚至超过了freemarker,看来到了不得不学的地步了,先来看看mustache是什么意思,我们都有一个不好的缺点,就是每次看到一个新鲜的东西就想知道他的中文名字叫什么,那么mustache的中文意思是什么?一定头疼?Mustache模板技术,一个比freemarker轻量级的模板引擎,它的英文是“胡子”,“胡须”的意思,好了不纠结这些东西了,我们先来了解一下mustache的基本知识,以及如何使用吧。

二、如何使用Mustache

2.1、名字

mustache官方给出的是Logic-less templates.翻译过来就是逻辑很少的模板,到底是不是呢?我们在学习的过程中慢慢去体会吧.

2.2、语法概述

2.2.1、一个比较典型的mustache模板如下所示

Hello {{name}}
You have just won {{value}} dollars!
{{#in_ca}}
Well, {{taxed_value}} dollars, after taxes.
{{/in_ca}}

2.2.2、给出的条件如下所示:

{
"name": "wangwenjun",
"value": 10000,
"taxed_value": 10000 - (10000 * 0.4),
"in_ca": true
}

2.2.3、那么得到的结果又会是怎样的呢?

<span style="background-color: rgb(0, 0, 0);"><span style="color:#006600;">Hello wangwenjun
You have just won 10000 dollars!
Well, 6000.0 dollars, after taxes.</span></span>

2.2.4、简单说明

Mustcache可以被用于html文件,配置文件,源代码等等很多场景,它的运行得益于扩展一些标签在模板文件中,然后使用一个hash字典或者对象对其进行替换渲染。我们之所以称之为“logic-less”是因为他摒弃了if else 以及for loop 这样的语句,取而代之的是只有标签,有些标签被替换成一个值,或者不作为,或者是一系列的值,比如数组或者一个列表,标签有几种不同的类型,自接下来我们会逐个介绍,但是你要相信他非常简单,因为他是“logic-less”的

2.3、Mustache的Tag类型详细介绍

Tag本地两对花括号标示出来,例如{{person}},当然{{#person}}也是一个Tag,这两个简单的例子中,我们使用了person作为tag的关键字,至于两种写法的区别在哪里,我们接下来慢慢的讨论。

2.3.1、变量

标签最主要的作用就是当作一个变量来使用,{{name}}标签在模板中会尝试查找name这个关键字在当前的上下文中,如果上下文中不存在name,父上下文将会通过递归的方式去查找,如果最*的上下文中依然找不到,name标签将不会被渲染,否则name标签会被替换渲染。

所有的变量在HTML中将会被过滤掉,如果你想返回没有经过转义的HTML元素,你可以使用三个花括号{{{name}}}.

当然你也可以使用&告诉上下文不要进行转义,如:{{&name}},这种方式非常有用,可以在下文中的设置分隔符中看到它的进一步用法,好了看一个简单的例子吧

mustache模板:

* {{name}}
* {{age}}
* {{company}}
* {{{company}}}

Hash 上下文:

{
"name": "Chris",
"company": "<b>GitHub</b>"
}

输出:

* Chris
*
* &lt;b&gt;GitHub&lt;/b&gt;
* <b>GitHub</b>
我们一起分析一下模板的输出过程,其中第一个标签被替换为Chris,第二个标签中在整个上下文中不存在,因此不作渲染,第三个我们说过了会被转义,因为有html元素,最后一个如果你想保留html元素就使用三个花括号的形式,这样会被完整的输出。

2.3.2、块

{{#person}}同样也是一个标签,但是他的作用是块的意思,如果写成{{person}}那么就是一个变量,像前文所说的那样,在本节中,我们来学习一下块的用法,也就是标签的第二个类型

所谓块就是渲染一个区域的文本一次或者多次,当然需要依赖当前上下文中person所代表的内容,块的开始和结束是这样的形式

<span style="white-space:pre">	</span>{{#person}}
<span style="white-space:pre"> </span>balabalabala.
<span style="white-space:pre"> </span>{{/person}}

同样一个块的所有行为也取决于person这个关键字在上下文中的值。

2.3.2.1 False和空的list

在上面的例子中,如果person这个key存在,并且有一个值是false或者一个空的列表,包含在块之间的元素不会做任何显示的.

模板代码:

Shown.
{{#person}}
Never shown!
{{/person}}

hash 上下文数据:

{
"person": false
}

输出:

Shown.

同样如果person是一个列表,如果它为空,标签内部的内容也是不会被显示出来的.

2.3.2.2 非空的列表和True

如果person这个key存在并且是一个非false的值,或者他是一个非空的列表元素,那么包括在标签内部的元素会被显示出来,当person只是一个boolean值的时候会显示一行数据,当person是一个列表数据类型的时候会循环显示,看看下面的例子吧

模板文件:

{{#repo}}
<b>{{name}}</b>
{{/repo}}

Hash 上下文数据:

{
"repo": [
{ "name": "resque" },
{ "name": "hub" },
{ "name": "rip" }
]
}

输出信息:

<b>resque</b>
<b>hub</b>
<b>rip</b>

2.3.2.3 Lambdas的使用

当Key的值是一个可以被调用的对象,譬如是一个函数或者一个lambda,该对象将会被调用并且传递标签包含的文本进去,我们来看看下面的这个例子

模板:wrapped是一个标签,准确的讲是一个section标签.

{{#wrapped}}
{{name}} is awesome.
{{/wrapped}}

Hash 上下文数据:

{
"name": "Willy",
"wrapped": function() {
return function(text) {
return "<b>" + text + "</b>"
}
}
}

输出:

<b>Willy is awesome.</b>

在这个例子中我们看到wrapped是一个可以被调用的函数他当标签使用的时候会被再次调用,并且包在其中的其他标签也会被转义执行,这个特性其实非常酷,可以用来做很多很多的事情.

2.3.2.4、非False的值


其实就是判断该key是否存在,如果存在则会执行section中的内容,不存在则不会执行.看一下下面的例子吧.

模板:

{{#person?}}
Hi {{name}}!
{{/person?}}

Hash 数据上下文:

{
"person?": { "name": "Jon" }
}

输出结果:

Hi Jon!

2.3.2.5、Inverted的块(section)

Inverted sections使用这样的格式 {{^person}}balabalabala{{/person}}有点类似于if
else这样的逻辑语句.

模板文件内容:

{{#repo}}
<b>{{name}}</b>
{{/repo}}
{{^repo}}
No repos :(
{{/repo}}

Hash 上下文:

{
"repo": []
}

输出结果为:

No repos :(

2.3.2.6、注释

良好的编码习惯,都会有言简意赅的注释作为辅佐,同样在mustache中存在注释的标签.我们来看看如何使用注释.
<h1>Today{{! ignore me }}.</h1>

可以看到在key的前面加一个!操作符号就可以将其过滤不作显示.

<h1>Today.</h1>

2.3.2.7、Partials的使用

Partials 标签开始是以一个大于号开始,像这样{{> box}}.

Partials在运行期被渲染 (相对于编译期渲染而言),因此可以使用它来做一些递归,可以避免无限的循环.

它也可以继承上下文的模板.你可以在wiki page [ERB](http://en.wikipedia.org/wiki/ERuby) 中看到如下的信息:

<%= partial :next_more, :start => start, :size => size %>

mustache则只需要如下短短几个字符就可以搞定:

{{> next_more}}

为什么呢? 因为 next_more.mustache 文件将会继承start和size.

这种方式你也许会联想到partials的作用相当于includes或者模板扩展,尽管这不是完全正确的,但是有时候的确可以这样认为.

举一个例子,教你如何使用mustache的特性(说真的,看官方文档,在这部分我起初没明白,最后仔细阅读,才理解它的意思):

base.mustache文件:
<h2>Names</h2>
{{#names}}
{{> user}}
{{/names}}

user.mustache文件:
<strong>{{name}}</strong>

上面是两个mustache的模板文件,在运行的过程中base.mustache使用了user.mustache,上面的效果等同于下面的一个模板文件,有时候我们这么写是为了让功能更集中,提高模板的重用率.

<h2>Names</h2>
{{#names}}
<strong>{{name}}</strong>
{{/names}}

2.3.2.8、设置分隔符号

有些时候我们的确是想修改一下mustache默认的标签分割符号{{}},但是值得庆幸的是,mustache允许我们这样做,而且方法非常简单,我们看一个例子吧

* {{default_tags}}
{{=<% %>=}}
* <% erb_style_tags %>
<%={{ }}=%>
* {{ default_tags_again }}

这里有三个标签,第一个采用默认的分隔符号,然后修改成<%%>这样的风格的风格符号,比关切输出标签内容,接下来又还原回{{}}这样的风格然后输出另外一个标签内容.

三、综合练习

好了,截止目前,关于mustache的用法基本上都已经介绍完毕了,最起码官方文档涵盖的东西我全部翻译了过来,应该没有什么遗漏的东西了,在本章节中我们来学习一下如何在Java中使用mustache,并且演示一个综合的应用.

3.1开发环境

IDEA:intellij IDEA

Maven :3.0.4

3.2 pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>websocket</artifactId>
<groupId>websocket</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>mustache</artifactId>

<dependencies>
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
</project>

3.3 模板文件

{{#items}}
Name: {{name}}
Price: {{price}}
{{#features}}
Feature: {{description}}
{{/features}}
{{/items}}

3.4 java代码

package com.wangwenjun.mustache;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;

public class MustacheExample {

public List<Item> items() {
return Arrays.asList(
new Item("Item 1", "$19.99", Arrays.asList(new Feature("New!"), new Feature("Awesome!"))),
new Item("Item 2", "$29.99", Arrays.asList(new Feature("Old."), new Feature("Ugly.")))
);
}

public static void main(String[] args) throws IOException {
MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile("template.mustache");
mustache.execute(new PrintWriter(System.out), new MustacheExample()).flush();
}

static class Feature {
private String description;

public Feature(String description) {
this.description = description;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}

public static class Item {

private String name, price;
private List<Feature> features;

public Item(String name, String price, List<Feature> features) {
this.name = name;
this.price = price;
this.features = features;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPrice() {
return price;
}

public void setPrice(String price) {
this.price = price;
}

public List<Feature> getFeatures() {
return features;
}

public void setFeatures(List<Feature> features) {
this.features = features;
}
}
}

3.5 输出结果

	Name:Item 1
Price:$19.99
New!
Awesome!
Name:Item 2
Price:$29.99
Old.
Ugly.

四、Mustache和Spring MVC的联合使用

很多热门的框架,Spring都会对其支持很好的支持,当然这不是spring官方发布的支持版本,而是在github上别人写的封装,我们知道spring mvc不仅提供了jsp,freemarker等视图技术,究其原因就是spring灵活的扩展性,可以很方便增多一种新的视图技术支持.

spring mustache github项目地址为:

https://github.com/sps/mustache-spring-view

pom

<dependency>
<groupId>com.github.sps.mustache</groupId>
<artifactId>mustache-spring-view</artifactId>
<version>1.3</version>
</dependency>

<!-- jmustache -->
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>${jmustache.version}</version>
</dependency>

<!-- mustache.java -->
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>${mustache.java.version}</version>
</dependency>

Spring 配置文件

<!-- jmustache -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.mustache.MustacheViewResolver">
<property name="suffix" value=""/>
<property name="cache" value="${TEMPLATE_CACHE_ENABLED}" />
<property name="templateFactory">
<bean class="org.springframework.web.servlet.view.mustache.jmustache.JMustacheTemplateFactory">
<property name="escapeHTML" value="true"/>
<property name="standardsMode" value="false"/>
<property name="templateLoader">
<bean class="org.springframework.web.servlet.view.mustache.jmustache.JMustacheTemplateLoader"/>
</property>
</bean>
</property>
</bean>

<!-- mustache.java -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.mustache.MustacheViewResolver">
<property name="suffix" value=""/>
<property name="cache" value="${TEMPLATE_CACHE_ENABLED}"/>
<property name="templateFactory">
<bean class="org.springframework.web.servlet.view.mustache.java.MustacheJTemplateFactory" />
</property>
</bean>