jinjava模板引擎简单介绍

256 阅读3分钟

最近在思考构建一个服务编排(Service Orchestration)系统,考虑这个系统至少需要具备以下特征:

  1. 使用统一的方法定义服务功能单元
  2. 使用一种通用的方式将一个或多个服务的输出映射到下游服务的输入,映射时支持基础的数据转换与处理
  3. 支持以搭积木的方式将低层服务功能单元组织成更高层抽象的服务功能,直至一个完整的服务
  4. 用户编排服务时,具备较大的灵活性定制业务

1 jinjava简介

python有一个广受欢迎的jinja模板引擎,jinjava则是jinja模板引擎的java实现,让jinja模板语法实现了跨语言使用

2 jinjava基本使用

使用jinjava,只需要在pom文件中添加相应的依赖:

    <dependency>
        <groupId>com.hubspot.jinjava</groupId>
        <artifactId>jinjava</artifactId>
        <version>2.7.1</version>
    </dependency>

2.1 模板变量

jinja的语法简单易懂,模板变量名放在{{ }}里面,渲染时可以传入实际的值,如下所示:

    String template = """
            hello {{ name }}
            """;

    Jinjava jinjava = new Jinjava();
    Map<String, Object> context = new HashMap<>();
    context.put("name", "movee");
    
    // 输出 hello movee
    String renderedTemplate = jinjava.render(template, context);

模板变量可以是数组,模板里通过数组下标访问数组的元素

    String template = """
            hello {{ name[0] }}
            """;

    Jinjava jinjava = new Jinjava();
    Map<String, Object> context = new HashMap<>();
    context.put("name", new String[]{"movee", "world"});
    
    // 输出 hello movee
    String renderedTemplate = jinjava.render(template, context);

也可以是列表

    String template = """
            hello {{ name[0] }}
            """;

    Jinjava jinjava = new Jinjava();
    Map<String, Object> context = new HashMap<>();
    List<String> names = new ArrayLst<>();
    names.add("movee");
    names.add("world");
    context.put("name", names);
    String renderedTemplate = jinjava.render(template, context);

当然可以是一个java对象或map,通过.[]访问对象或map的属性,也可以调用对象的方法


    public static class Person {
        private String name;
        private Integer age;

        public String sayHello() {
            return "hello " + name;
        }
    }

    String template = """
            {# 这里面的内容是注释:person['name']与person.name等价 #}
            hello {{ person.name }}
            {# 调用方法 #}
            {{ person.sayHello() }}
            """;

    Jinjava jinjava = new Jinjava();
    Map<String, Object> context = new HashMap<>();
    context.put("person", new Person("movee", 25));
    String renderedTemplate = jinjava.render(template, context);
    log.info("{}", renderedTemplate);

2.2 基本控制结构

jinjava支持常用的if和for分支控制结构

if控制结构:

    String template = """
            {% if person.age <= 30 %}
            good {{ person.name }}
            {% elif person.age > 50 %}
            very good {{ person.name }}
            {% else %}
            hello {{ person.name }}
            {% endif %}
            """;

for控制结构:

    // 遍历列表或数组元素
    String template = """
            {% for v in params) %}
            value: {{ v }}
            {% endfor %}
            """;

    // 遍历map所有元素
    String template = """
            {% for k, v in params.entrySet() %}
            key: {{ k }}, value: {{ v }}
            {% endfor %}
            """;

2.3 过滤器

作为一个模板引擎,过滤器当然是免不了的,jinja的过滤器使用比较有特色,采用大家熟知的linux/unix的管道风格,如下所示的upper过滤器将字符串转化为大写(系统内置的更多的过滤器请参考文档)

    String template = """
            {{ "hello" | upper }}
            """;

过滤器也可以用在for循环的迭代元素上:

    String template = """
            {% for v in params) | upper %}
            value: {{ v }}
            {% endfor %}
            """;

jinja还有专门的filter语句块,可以作用于整个filter块内的内容,不仅仅是模板变量

{% filter upper %}
    translate these letters to uppercase.
{% endfilter %}

当然注册自定义过滤器也是要支持的:

// define a custom tag implementing com.hubspot.jinjava.lib.Tag
jinjava.getGlobalContext().registerTag(new MyCustomTag());
// define a custom filter implementing com.hubspot.jinjava.lib.Filter
jinjava.getGlobalContext().registerFilter(new MyAwesomeFilter());
// define a custom public static function (this one will bind to myfn:my_func('foo', 42))
jinjava.getGlobalContext().registerFunction(new ELFunctionDefinition("myfn", "my_func", 
    MyFuncsClass.class, "myFunc", String.class, Integer.class);

// define any number of classes which extend Importable
jinjava.getGlobalContext().registerClasses(Class<? extends Importable>... classes);

2.4 模板重用

2.4.1 宏

如果某部分模板内容可能在多个地方重复使用,可以将重用部分放到一个叫做macro的块中,宏也支持输入参数。

{% macro input(name) %}
    {{ name }}
{% endmacro %}

然后在需要使用的地方调用宏,就可以在调用位置插入渲染的内容了

<p>{{ input('movee') }}</p>

2.4.2 include

宏可以插入宏块内的内容,include可以插入整个模板文件的内容到指定位置

{% include 'example.html' %}
{# 如果文件不存在,不报错 #}
{% extends 'example.html' ignore missing %}

2.4.2 extends

extends(继承)也可以引入模板文件,但是如果被继承的模板文件中含有block块,引入后可以覆盖它。例如,被继承的模板文件example.html中包含下面内容:

{% block blockName %}
    block content in example.html
{% endblock %}

则可以如下继承example.html文件的内容,并覆盖block的内容

{% extends 'example.html' ignore missing %}
{% block blockName %}
    new block content
{% endblock %}