SpringBoot系列: Pebble模板引擎

===============================
Java 模板引擎选择
===============================
SpringBoot Starter项目向导中可选的Java模版引擎有:
1. Thymeleaf (百里香草叶子)
2. Freemarker
3. Mustache
4. Groovy Templates

简单评价这些模版引擎吧,
1. Thymeleaf: SpringBoot 看起来更推荐 Thymeleaf, 但我不喜欢它自作聪明的标签定义, 满眼 th 开头的黑魔法标签, 表示看不懂, 另外 Thymeleaf 也是公认性能最差的引擎, https://github.com/mbosecke/template-benchmark
2. Freemarker: 放弃了 Thymeleaf 后, 重点考察了 Freemarker, 断断续续学了几天, 要做一个小应用时, 还是碰到不少问题, 另外布局复用这块搞的也太复杂了.
3. Mustache: 语法比较奇怪.
4. Groovy Templates: 完全不是 html 语法, 完全不懂它的模版语法.

我对后端模版引擎选择标准:
在一个普通的web项目中, 后端模块引擎技术往往是最不被看重的领域, 一般在上面投入的时间也不多, 如果一个模版引擎一两天还不能上手, 说明这个模版引擎设计的太复杂了, 或者太另类了.

我的最终选择:
对benchmark (https://github.com/mbosecke/template-benchmark) 上的模版引擎都做了些研究, 综合考虑了项目成熟度/学习难度/项目活跃程度/, 最终选定 Pebble templates.


===============================
关于 Pebble templates
===============================
语法: Pebble templates 是 Django-like 模板引擎. 类似的引擎还有: Jinja2(Python), Twig(PHP), jtwig(Java)
学习成本: 花上一天学习就能应付日常开发, 官方文档也不长, 学习起来非常快.

目前稳定版: 3.0.5, 而且官方提供了 SpringBoot2 Starter 包.

主页: https://pebbletemplates.io/
github wiki: https://github.com/PebbleTemplates/pebble/wiki
https://github.com/PebbleTemplates/pebble/wiki/basic-usage
https://github.com/PebbleTemplates/pebble/wiki/installation

===============================
Pebble 重要的几个类
===============================
1. Loader 类: PebbleEngine 将依赖 Loader 加载原始的模板, Pebble 内置 FileLoader 和 ClasspathLoader 和 StringLoader  等几个实现类, 前者负责从文件系统中加载原始模板, 后者负责直接从字符串中加载原始模板. 引入 pebble-spring-boot-2-starter 后, 会在 Spring context 注册一个名为 pebbleLoader 的 Loader.
2. PebbleEngine 类: 引擎核心. 引入 pebble-spring-boot-2-starter 后, 会在 Spring context 注册一个名为 pebbleEngine 的引擎对象, pebbleEngine 使用 pebbleLoader 加载原始模板.
3. PebbleTemplate 类: 负责完成模板的渲染.
4. PebbleViewResolver 类, 以 ModelAndView 对象作为模板参数的输入, 然后使用 PebbleTemplate 对象完成渲染. 引入 pebble-spring-boot-2-starter 后, 会在 Spring context 注册一个名为 pebbleViewResolver 的ViewResolver对象.
一个 PebbleEngine 对象依赖一个特定类型的 Loader 对象, 如果我们的应用既有字符串模板, 又有文件模板, 我们需要定义两个 Loader 和两个 PebbleEngine 对象.

===============================
Pebble 和 SpringBoot 的集成
===============================
<dependency>
<groupId>io.pebbletemplates</groupId>
<artifactId>pebble-spring-boot-2-starter</artifactId>
<version>3.0.5</version>
</dependency>

pebble-spring-boot-2-starter, 会读取一些配置属性, 完整的属性清单见 https://github.com/PebbleTemplates/pebble-spring-boot-starter
下面是一些常用的默认属性值, 我们可以在 application.properties 文件中按需设定, 比如禁用缓存或修改后缀为.html 等.
pebble.prefix=/templates/
pebble.suffix=.pebble
pebble.content-type=text/html
pebble.cache=true
pebble.encoding=UTF-8
pebble.defaultLocale=null
pebble.strictVariables=false

自定义 Pebble Configuration 类:
引入 pebble-spring-boot-2-starter 后, Pebble 会向 Spring context 注册一个名为 pebbleLoader 的 Loader, 以及一个名为 pebbleEngine 的引擎对象, 以及一个名为 pebbleViewResolver 的 PebbleViewResolver 对象. pebbleLoader 是一个 FileLoader, pebbleEngine 使用 pebbleLoader 加载原始模板, pebbleViewResolver 负责组装参数和原始模板.
如果我们有特别的需求, 可以自定义自己的 Pebble Configuration, 按需注入我们自己的 pebbleLoader 和 pebbleEngine 和 pebbleViewResolver. 

//@formatter:off
/**
 * Pebble Config 类
 * 1. 为了处理文本模板, 增加 pebbleStringLoader 和 pebbleStringEngine bean.
 * 2. 为了处理其他目录的模板文件, 增加 pebbleFileLoader 和 pebbleFileEngine bean.
 * 3. 未对 pebble-spring-boot-2-starter 注册的 pebbleLoader pebbleEngine pebbleViewResolver  bean做任何修改.
 * @author Administrator
 *
 */
//@formatter:on
@Configuration
class PebbleConfig {
    @Bean("pebbleStringLoader")
    StringLoader pebbleStringLoader() {
        return new StringLoader();
    }

    @Autowired
    @Bean("pebbleStringEngine")
    PebbleEngine pebbleStringEngine(@Qualifier("pebbleStringLoader") StringLoader pebbleStringLoader) {
        PebbleEngine stringEngine = new PebbleEngine.Builder().loader(pebbleStringLoader)
                .build();
        return stringEngine;
    }

    @Bean
    FileLoader pebbleFileLoader() {
        FileLoader loader = new FileLoader();
        loader.setPrefix("/path/to/resources");
        loader.setSuffix(".html");
        return loader;
    }

    @Bean
    @Autowired
    PebbleEngine pebbleFileEngine(@Qualifier("pebbleFileLoader") FileLoader pebbleFileLoader) {
        PebbleEngine fileEngine = new PebbleEngine.Builder().loader(pebbleFileLoader)
                .build();
        return fileEngine;
    }
}

@Component
class PebbleConverter {
    @Autowired
    @Qualifier("pebbleStringEngine")
    PebbleEngine pebbleStringEngine;

    public String render(String templateRawText, Map<String, Object> context) throws IOException {
        PebbleTemplate compinedTempate = pebbleStringEngine.getTemplate(templateRawText);
        StringWriter writer = new StringWriter();
        compinedTempate.evaluate(writer, context);
        return writer.toString();
    }
}

===============================
Pebble Layout 模板设计
===============================
这里简单展现一下使用Pebble如何模块化各个页面, 假设有下面文件:
home.pebble 文件: 主页模板
base.pebble 文件: 所有页面的母版
navbar.pebble 文件: 头部导航条
footer.pebble 文件: 尾部条
sidebar.pebble 文件: 左边导航条

这几个文件的关系是:
base.pebble 包含 navbar.pebble 和 footer.pebble 和 sidebar.pebble 文件, home.pebble 继承自 base.pebble 文件, 并 override 一些指定的元素.
包含指令为 {% include %}, 继承指令为 {% extends %}, override 是通过复用同名的block 块实现.

footer.pebble 文件内容: 

    <div>
    </div> <!-- end of footer -->

navbar.pebble 文件内容: 

    <div>
    </div> <!-- end of navbar -->    

base.pebble 文件内容:
使用 {% include %} 指令将 footer/sidebar/navbar 几个 pebble 文件嵌入进来, 并加入了一个 content block, base.pebble 子文件可以扩展该block.

<!DOCTYPE html>
<html lang="en">
    <head>
            <link rel="stylesheet" href="/css/bootstrap.min.css" />
            <link rel="stylesheet" href="/css/style.css" />
            <title>Pebble Spring Example</title>
    </head>
    <body>
        <div id="wrap">
            {% include 'navbar' %}   
            
            <div class="container">
                <div class="row">
                    <div class="col-md-3">
                        {% include 'sidebar' %}
                    </div>
                    <div class="col-md-9">
                        {% block content %}
                            {# This section is to be overriden by child templates #}
                        {% endblock content %}
                    </div>
                </div>            
            </div><!-- end container -->
            
        </div><!-- end wrap -->

        {% include 'footer' %}
    </body>
</html>

home.pebble 文件内容:
使用 {% extends %} 指令继承了 base.pebble 页面, 同时使用了 {% block content %} 将真实的数据嵌入到 base.pebble 文件的 content block 中.

{% extends 'base' %}
{% block content %} 
  Home page
{% endblock content%}

===============================
我找到几个不错的Freemark链接
===============================
https://o7planning.org/en/11547/spring-boot-and-freemarker-tutorial
https://www.hawu.me/coding/733
http://www.tianshouzhi.com/api/tutorials/springboot/115
https://www.cnblogs.com/ChenD/p/6908246.html

原文地址:https://www.cnblogs.com/harrychinese/p/SpringBoot_pebble_intro.html