• FreeMarker开发-数据模型


      FreeMarker用于处理模板的数据模型是哈希表,也就是一个树状结构的name-value对。如下:

    (root)
    |
    +- string="string"

    +- map
    | |
    | +- map1 = "map1"
    | |
    | +- map2 = "map2"

    +- object
    | |
    | +- field1= "field1"
    | |
    | +- field2 = "field1"

    | |

    | +- method= "method"

    ......

      在Java代码中(下面第15行),我们提供给模板引擎的数据(process方法的第一个参数),可以是Map,也可以是自定义的Java对象。但是,模板引擎在处理时,并不是直接使用我们提供的类型。它会将其转换为自己内部定义的类型,转换工作由第8行的ObjectWrapper去完成,这种特性被称作“对象包装(Object Swapping)”。

      查看源码,关于Template#process(Object dataModel, Writer out)方法中dataModel参数的解释如下:

      dataModel是从模板中可以获取的变量的容器(name-value对);

      dataModel通常是一个Map<String, Object>或者是一个JavaBean(JavaBean的属性将成为变量);

      可以是BeanWrapper在执行时可以转换为TemplateHashModel的任意对象;

      也可以是实现了TemplateHashModel接口的对象,这种情况在执行时将直接使用,不再进行包装;

      如果是null,则使用一个空的数据模型。

     1 public class FreeMarkerTest {
     2     public static void main(String[] args) throws IOException,
     3             TemplateException {
     4         Configuration conf = new Configuration();
     5         // 设置加载模板文件的目录
     6         conf.setDirectoryForTemplateLoading(new File("src/templates"));
     7         // 设置模板检索数据模型的方式
     8         conf.setObjectWrapper(new DefaultObjectWrapper());
     9         // 创建、解析模板并缓存
    10         Template template = conf.getTemplate("example.flt");
    11         // 准备数据
    12         Map<String, Object> root = new HashMap<String, Object>();
    13         root.put("example", "Hello World!");
    14         // 将数据与模板合并
    15         template.process(root, new OutputStreamWriter(System.out));
    16     }
    17 }

    一、对象包装(Object Swapping)

      FreeMarker数据模型的哈希表中的name为字符串,value可以为标量、容器、子程序和节点等。这也是FreeMarker内部使用的变量的类型。这些类型都实现了freemarker.template.TemplateModel接口。

    1. 标量:包括数值、字符串、日期、布尔。

    2. 容器:包括哈希表(Map,类似于Java中的Map)、序列(Sequence,类似于Java中的数组和有序集合)、集合(Collection,类似于Java中的集合)。

    3. 子程序:包括方法(Method)和指令(Directive)。

    4. 节点:主要是为了帮助用户在数据模型中处理XML文档。

      在模板处理时,会将Java类型包装为对应的TemplateModel实现。比如将一个String包装为SimpleScalar来存储同样的值。对于每个Java类型,具体选择什么TemplateModel实现去包装,取决于对象包装器(ObjectWrapper)的实现策略,可通过上面代码第8行设置。ObjectWrapper是一个接口,FreeMarker核心包提供了两个基本的实现:ObjectWrapper.DEFAULT_WRAPPER、ObjectWrapper.BEANS_WRAPPER。

    1. ObjectWrapper.DEFAULT_WRAPPER: 按照下表对应关系包装,Jython类型包装为freemarker.ext.jython.JythonWrapper,其它类型调用BEANS_WRAPPER。

    2. ObjectWrapper.BEANS_WRAPPER:利用Java反射来获取对象的成员属性。

    类型 FreeMarker接口 FreeMarker实现
    字符串 TemplateScalarModel SimpleScalar
    数值 TemplateNumberModel SimpleNumber
    日期 TemplateDateModel SimpleDate
    布尔 TemplateBooleanModel TemplateBooleanModel.TRUE
    哈希 TemplateHashModel SimpleHash
    序列 TemplateSequenceModel SimpleSequence
    集合 TemplateCollectionModel SimpleCollection
    节点 TemplateNodeModel NodeModel

     ObjectWrapper的这两个属性现已经被标注为@deprecated,并建议用DefaultObjectWrapperBuilder#build()和BeansWrapperBuilder#build()方式获取实例。如下:

    // conf.setObjectWrapper(new DefaultObjectWrapperBuilder(new Version("2.3.22")).build());
    conf.setObjectWrapper(new BeansWrapperBuilder(new Version("2.3.22")).build());

     

    二、自定义方法

      自定义方法需要实现freemarker.template.TemplateMethodModel接口(当前已@deprecated),建议替换为TemplateMethodModelEx。

      例:实现累加方法sum(int...num)。参数可以有多个整数。

    模板如下:

    ${sum(1, 2, 3, 4)}

    代码如下:

    package org.genein.freemark.templateModel;
    
    import java.util.List;
    
    import freemarker.template.SimpleNumber;
    import freemarker.template.TemplateMethodModelEx;
    import freemarker.template.TemplateModelException;
    
    public class SumMethod implements TemplateMethodModelEx {
    
        @SuppressWarnings("rawtypes")
        @Override
        public Object exec(List arg0) throws TemplateModelException {
            if (arg0 == null || arg0.size() == 0) {
                return new SimpleNumber(0);
            }
            
            double sum = 0l;
            double tmp;
            for (int i = 0; i < arg0.size(); i++) {
                tmp = Double.valueOf(arg0.get(i).toString());
                sum += tmp;
            }
            return new SimpleNumber(sum);
        }
    }

    FreeMarkerTest.main方法添加以下代码:

    1 // 添加方法工具
    2 root.put("sum", new SumMethod());
    3 // 将数据与模板合并
    4 template.process(root, new OutputStreamWriter(System.out));

    输出如下:

    10

     

    、自定义指令

    自定义指令需要实现freemarker.template.TemplateDirectiveModel接口。

    例:自定义一个指令,循环输出内嵌内容,count参数决定循环次数(必填),hr参数决定是否添加分隔符“<hr>”(可选,默认false),step参数表示当前循环次数(可选)。

    模板如下:

    <@repeat count=5 hr=false; step>
    ${step}. ${name}
    </@repeat>

    代码如下:

    package org.genein.freemark.templateModel;
    
    import java.io.IOException;
    import java.util.Map;
    
    import freemarker.core.Environment;
    import freemarker.template.SimpleNumber;
    import freemarker.template.TemplateBooleanModel;
    import freemarker.template.TemplateDirectiveBody;
    import freemarker.template.TemplateDirectiveModel;
    import freemarker.template.TemplateException;
    import freemarker.template.TemplateModel;
    import freemarker.template.TemplateModelException;
    import freemarker.template.TemplateNumberModel;
    
    public class RepeatDirective implements TemplateDirectiveModel {
        /**
         * 循环次数
         */
        private static final String COUNT = "count";
        /**
         * 是否需要用hr标签间隔
         */
        private static final String HR = "hr";
    
        @SuppressWarnings("rawtypes")
        @Override
        public void execute(Environment env, Map params, TemplateModel[] loopVars,
                TemplateDirectiveBody body) throws TemplateException, IOException {
            // 获取count参数,并校验是否合法
            TemplateModel countModel = (TemplateModel) params.get(COUNT);
            if (countModel == null) {
                throw new TemplateModelException("缺少必须参数count!");
            }
            if (!(countModel instanceof TemplateNumberModel)) {
                throw new TemplateModelException("count参数必须为数值型!");
            }
            int count = ((TemplateNumberModel) countModel).getAsNumber().intValue();
            if (count < 0) {
                throw new TemplateModelException("count参数值必须为正整数!");
            }
    
            // 获取hr参数,并校验是否合法
            boolean hr = false;
            TemplateModel hrModel = (TemplateModel) params.get(HR);
            if (hrModel != null) {
                if (!(hrModel instanceof TemplateBooleanModel)) {
                    throw new TemplateModelException("hr参数值必须为布尔型!");
                }
                hr = ((TemplateBooleanModel) hrModel).getAsBoolean();
            }
    
            // 检验内嵌内容是否为空
            if (body == null) {
                throw new RuntimeException("内嵌内容不能为空!");
            }
    
            // 最多只允许一个循环变量
            if (loopVars.length > 1) {
                throw new TemplateModelException("最多只允许一个循环变量!");
            }
    
            // 循环渲染内嵌内容
            for (int i = 0; i < count; i++) {
                // 用第一个循环变量记录循环次数
                if (loopVars.length == 1) {
                    loopVars[0] = new SimpleNumber(i + 1);
                }
                // 上面设置循环变量的操作必须在该render前面,因为内嵌内容中使用到了该循环变量。
                // body.render(new UpperCaseFilterWriter(env.getOut()));
                body.render(env.getOut());
                if (hr) {
                    env.getOut().write("<hr>");
                }
            }
        }
    }

    FreeMarkerTest.main增加以下代码:

    root.put("name", "Genein");
    // 添加自定义指令
    root.put("repeat", new RepeatDirective());
    // 将数据与模板合并
    template.process(root, new OutputStreamWriter(System.out));

    输出如下:

    1. Genein
    2. Genein
    3. Genein
    4. Genein
    5. Genein

     

  • 相关阅读:
    删除 node_modules文件夹cmd指令
    vue 限制输入字符长度
    vertical-align和text-align属性实现垂直水平居中
    二分查找法
    MySQL实现分页查询
    数据库连接
    AOP编程的常用实现方式
    链表中环的入口
    AQS同步组件及ReentrantLock和synchronized的区别
    快速排序的递归和非递归
  • 原文地址:https://www.cnblogs.com/genein/p/5271113.html
Copyright © 2020-2023  润新知